You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

756 lines
26 KiB

// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef V8_PARSER_H_
#define V8_PARSER_H_
#include "allocation.h"
#include "ast.h"
#include "scanner.h"
#include "scopes.h"
#include "preparse-data-format.h"
#include "preparse-data.h"
namespace v8 {
namespace internal {
class CompilationInfo;
class FuncNameInferrer;
class ParserLog;
class PositionStack;
class Target;
class LexicalScope;
template <typename T> class ZoneListWrapper;
class ParserMessage : public Malloced {
public:
ParserMessage(Scanner::Location loc, const char* message,
Vector<const char*> args)
: loc_(loc),
message_(message),
args_(args) { }
~ParserMessage();
Scanner::Location location() { return loc_; }
const char* message() { return message_; }
Vector<const char*> args() { return args_; }
private:
Scanner::Location loc_;
const char* message_;
Vector<const char*> args_;
};
class FunctionEntry BASE_EMBEDDED {
public:
explicit FunctionEntry(Vector<unsigned> backing) : backing_(backing) { }
FunctionEntry() : backing_(Vector<unsigned>::empty()) { }
int start_pos() { return backing_[kStartPosOffset]; }
int end_pos() { return backing_[kEndPosOffset]; }
int literal_count() { return backing_[kLiteralCountOffset]; }
int property_count() { return backing_[kPropertyCountOffset]; }
bool strict_mode() { return backing_[kStrictModeOffset] != 0; }
bool is_valid() { return backing_.length() > 0; }
static const int kSize = 5;
private:
Vector<unsigned> backing_;
static const int kStartPosOffset = 0;
static const int kEndPosOffset = 1;
static const int kLiteralCountOffset = 2;
static const int kPropertyCountOffset = 3;
static const int kStrictModeOffset = 4;
};
class ScriptDataImpl : public ScriptData {
public:
explicit ScriptDataImpl(Vector<unsigned> store)
: store_(store),
owns_store_(true) { }
// Create an empty ScriptDataImpl that is guaranteed to not satisfy
// a SanityCheck.
ScriptDataImpl() : store_(Vector<unsigned>()), owns_store_(false) { }
virtual ~ScriptDataImpl();
virtual int Length();
virtual const char* Data();
virtual bool HasError();
void Initialize();
void ReadNextSymbolPosition();
FunctionEntry GetFunctionEntry(int start);
int GetSymbolIdentifier();
bool SanityCheck();
Scanner::Location MessageLocation();
const char* BuildMessage();
Vector<const char*> BuildArgs();
int symbol_count() {
return (store_.length() > PreparseDataConstants::kHeaderSize)
? store_[PreparseDataConstants::kSymbolCountOffset]
: 0;
}
// The following functions should only be called if SanityCheck has
// returned true.
bool has_error() { return store_[PreparseDataConstants::kHasErrorOffset]; }
unsigned magic() { return store_[PreparseDataConstants::kMagicOffset]; }
unsigned version() { return store_[PreparseDataConstants::kVersionOffset]; }
private:
Vector<unsigned> store_;
unsigned char* symbol_data_;
unsigned char* symbol_data_end_;
int function_index_;
bool owns_store_;
unsigned Read(int position);
unsigned* ReadAddress(int position);
// Reads a number from the current symbols
int ReadNumber(byte** source);
ScriptDataImpl(const char* backing_store, int length)
: store_(reinterpret_cast<unsigned*>(const_cast<char*>(backing_store)),
length / static_cast<int>(sizeof(unsigned))),
owns_store_(false) {
ASSERT_EQ(0, static_cast<int>(
reinterpret_cast<intptr_t>(backing_store) % sizeof(unsigned)));
}
// Read strings written by ParserRecorder::WriteString.
static const char* ReadString(unsigned* start, int* chars);
friend class ScriptData;
};
class ParserApi {
public:
// Parses the source code represented by the compilation info and sets its
// function literal. Returns false (and deallocates any allocated AST
// nodes) if parsing failed.
static bool Parse(CompilationInfo* info);
// Generic preparser generating full preparse data.
static ScriptDataImpl* PreParse(UC16CharacterStream* source,
v8::Extension* extension);
// Preparser that only does preprocessing that makes sense if only used
// immediately after.
static ScriptDataImpl* PartialPreParse(UC16CharacterStream* source,
v8::Extension* extension);
};
// ----------------------------------------------------------------------------
// REGEXP PARSING
// A BuffferedZoneList is an automatically growing list, just like (and backed
// by) a ZoneList, that is optimized for the case of adding and removing
// a single element. The last element added is stored outside the backing list,
// and if no more than one element is ever added, the ZoneList isn't even
// allocated.
// Elements must not be NULL pointers.
template <typename T, int initial_size>
class BufferedZoneList {
public:
BufferedZoneList() : list_(NULL), last_(NULL) {}
// Adds element at end of list. This element is buffered and can
// be read using last() or removed using RemoveLast until a new Add or until
// RemoveLast or GetList has been called.
void Add(T* value) {
if (last_ != NULL) {
if (list_ == NULL) {
list_ = new ZoneList<T*>(initial_size);
}
list_->Add(last_);
}
last_ = value;
}
T* last() {
ASSERT(last_ != NULL);
return last_;
}
T* RemoveLast() {
ASSERT(last_ != NULL);
T* result = last_;
if ((list_ != NULL) && (list_->length() > 0))
last_ = list_->RemoveLast();
else
last_ = NULL;
return result;
}
T* Get(int i) {
ASSERT((0 <= i) && (i < length()));
if (list_ == NULL) {
ASSERT_EQ(0, i);
return last_;
} else {
if (i == list_->length()) {
ASSERT(last_ != NULL);
return last_;
} else {
return list_->at(i);
}
}
}
void Clear() {
list_ = NULL;
last_ = NULL;
}
int length() {
int length = (list_ == NULL) ? 0 : list_->length();
return length + ((last_ == NULL) ? 0 : 1);
}
ZoneList<T*>* GetList() {
if (list_ == NULL) {
list_ = new ZoneList<T*>(initial_size);
}
if (last_ != NULL) {
list_->Add(last_);
last_ = NULL;
}
return list_;
}
private:
ZoneList<T*>* list_;
T* last_;
};
// Accumulates RegExp atoms and assertions into lists of terms and alternatives.
class RegExpBuilder: public ZoneObject {
public:
RegExpBuilder();
void AddCharacter(uc16 character);
// "Adds" an empty expression. Does nothing except consume a
// following quantifier
void AddEmpty();
void AddAtom(RegExpTree* tree);
void AddAssertion(RegExpTree* tree);
void NewAlternative(); // '|'
void AddQuantifierToAtom(int min, int max, RegExpQuantifier::Type type);
RegExpTree* ToRegExp();
private:
void FlushCharacters();
void FlushText();
void FlushTerms();
Zone* zone() { return zone_; }
Zone* zone_;
bool pending_empty_;
ZoneList<uc16>* characters_;
BufferedZoneList<RegExpTree, 2> terms_;
BufferedZoneList<RegExpTree, 2> text_;
BufferedZoneList<RegExpTree, 2> alternatives_;
#ifdef DEBUG
enum {ADD_NONE, ADD_CHAR, ADD_TERM, ADD_ASSERT, ADD_ATOM} last_added_;
#define LAST(x) last_added_ = x;
#else
#define LAST(x)
#endif
};
class RegExpParser {
public:
RegExpParser(FlatStringReader* in,
Handle<String>* error,
bool multiline_mode);
static bool ParseRegExp(FlatStringReader* input,
bool multiline,
RegExpCompileData* result);
RegExpTree* ParsePattern();
RegExpTree* ParseDisjunction();
RegExpTree* ParseGroup();
RegExpTree* ParseCharacterClass();
// Parses a {...,...} quantifier and stores the range in the given
// out parameters.
bool ParseIntervalQuantifier(int* min_out, int* max_out);
// Parses and returns a single escaped character. The character
// must not be 'b' or 'B' since they are usually handle specially.
uc32 ParseClassCharacterEscape();
// Checks whether the following is a length-digit hexadecimal number,
// and sets the value if it is.
bool ParseHexEscape(int length, uc32* value);
uc32 ParseOctalLiteral();
// Tries to parse the input as a back reference. If successful it
// stores the result in the output parameter and returns true. If
// it fails it will push back the characters read so the same characters
// can be reparsed.
bool ParseBackReferenceIndex(int* index_out);
CharacterRange ParseClassAtom(uc16* char_class);
RegExpTree* ReportError(Vector<const char> message);
void Advance();
void Advance(int dist);
void Reset(int pos);
// Reports whether the pattern might be used as a literal search string.
// Only use if the result of the parse is a single atom node.
bool simple();
bool contains_anchor() { return contains_anchor_; }
void set_contains_anchor() { contains_anchor_ = true; }
int captures_started() { return captures_ == NULL ? 0 : captures_->length(); }
int position() { return next_pos_ - 1; }
bool failed() { return failed_; }
static const int kMaxCaptures = 1 << 16;
static const uc32 kEndMarker = (1 << 21);
private:
enum SubexpressionType {
INITIAL,
CAPTURE, // All positive values represent captures.
POSITIVE_LOOKAHEAD,
NEGATIVE_LOOKAHEAD,
GROUPING
};
class RegExpParserState : public ZoneObject {
public:
RegExpParserState(RegExpParserState* previous_state,
SubexpressionType group_type,
int disjunction_capture_index)
: previous_state_(previous_state),
builder_(new RegExpBuilder()),
group_type_(group_type),
disjunction_capture_index_(disjunction_capture_index) {}
// Parser state of containing expression, if any.
RegExpParserState* previous_state() { return previous_state_; }
bool IsSubexpression() { return previous_state_ != NULL; }
// RegExpBuilder building this regexp's AST.
RegExpBuilder* builder() { return builder_; }
// Type of regexp being parsed (parenthesized group or entire regexp).
SubexpressionType group_type() { return group_type_; }
// Index in captures array of first capture in this sub-expression, if any.
// Also the capture index of this sub-expression itself, if group_type
// is CAPTURE.
int capture_index() { return disjunction_capture_index_; }
private:
// Linked list implementation of stack of states.
RegExpParserState* previous_state_;
// Builder for the stored disjunction.
RegExpBuilder* builder_;
// Stored disjunction type (capture, look-ahead or grouping), if any.
SubexpressionType group_type_;
// Stored disjunction's capture index (if any).
int disjunction_capture_index_;
};
Isolate* isolate() { return isolate_; }
Zone* zone() { return isolate_->zone(); }
uc32 current() { return current_; }
bool has_more() { return has_more_; }
bool has_next() { return next_pos_ < in()->length(); }
uc32 Next();
FlatStringReader* in() { return in_; }
void ScanForCaptures();
Isolate* isolate_;
Handle<String>* error_;
ZoneList<RegExpCapture*>* captures_;
FlatStringReader* in_;
uc32 current_;
int next_pos_;
// The capture count is only valid after we have scanned for captures.
int capture_count_;
bool has_more_;
bool multiline_;
bool simple_;
bool contains_anchor_;
bool is_scanned_for_captures_;
bool failed_;
};
// ----------------------------------------------------------------------------
// JAVASCRIPT PARSING
class Parser {
public:
Parser(Handle<Script> script,
bool allow_natives_syntax,
v8::Extension* extension,
ScriptDataImpl* pre_data);
virtual ~Parser() { }
// Returns NULL if parsing failed.
FunctionLiteral* ParseProgram(Handle<String> source,
bool in_global_context,
StrictModeFlag strict_mode);
FunctionLiteral* ParseLazy(CompilationInfo* info);
void ReportMessageAt(Scanner::Location loc,
const char* message,
Vector<const char*> args);
void ReportMessageAt(Scanner::Location loc,
const char* message,
Vector<Handle<String> > args);
private:
// Limit on number of function parameters is chosen arbitrarily.
// Code::Flags uses only the low 17 bits of num-parameters to
// construct a hashable id, so if more than 2^17 are allowed, this
// should be checked.
static const int kMaxNumFunctionParameters = 32766;
static const int kMaxNumFunctionLocals = 32767;
FunctionLiteral* ParseLazy(CompilationInfo* info,
UC16CharacterStream* source,
ZoneScope* zone_scope);
enum Mode {
PARSE_LAZILY,
PARSE_EAGERLY
};
Isolate* isolate() { return isolate_; }
Zone* zone() { return isolate_->zone(); }
// Called by ParseProgram after setting up the scanner.
FunctionLiteral* DoParseProgram(Handle<String> source,
bool in_global_context,
StrictModeFlag strict_mode,
ZoneScope* zone_scope);
// Report syntax error
void ReportUnexpectedToken(Token::Value token);
void ReportInvalidPreparseData(Handle<String> name, bool* ok);
void ReportMessage(const char* message, Vector<const char*> args);
bool inside_with() const { return with_nesting_level_ > 0; }
JavaScriptScanner& scanner() { return scanner_; }
Mode mode() const { return mode_; }
ScriptDataImpl* pre_data() const { return pre_data_; }
// Check if the given string is 'eval' or 'arguments'.
bool IsEvalOrArguments(Handle<String> string);
// All ParseXXX functions take as the last argument an *ok parameter
// which is set to false if parsing failed; it is unchanged otherwise.
// By making the 'exception handling' explicit, we are forced to check
// for failure at the call sites.
void* ParseSourceElements(ZoneList<Statement*>* processor,
int end_token, bool* ok);
Statement* ParseStatement(ZoneStringList* labels, bool* ok);
Statement* ParseFunctionDeclaration(bool* ok);
Statement* ParseNativeDeclaration(bool* ok);
Block* ParseBlock(ZoneStringList* labels, bool* ok);
Block* ParseVariableStatement(bool* ok);
Block* ParseVariableDeclarations(bool accept_IN,
Handle<String>* out,
bool* ok);
Statement* ParseExpressionOrLabelledStatement(ZoneStringList* labels,
bool* ok);
IfStatement* ParseIfStatement(ZoneStringList* labels, bool* ok);
Statement* ParseContinueStatement(bool* ok);
Statement* ParseBreakStatement(ZoneStringList* labels, bool* ok);
Statement* ParseReturnStatement(bool* ok);
Block* WithHelper(Expression* obj, ZoneStringList* labels, bool* ok);
Statement* ParseWithStatement(ZoneStringList* labels, bool* ok);
CaseClause* ParseCaseClause(bool* default_seen_ptr, bool* ok);
SwitchStatement* ParseSwitchStatement(ZoneStringList* labels, bool* ok);
DoWhileStatement* ParseDoWhileStatement(ZoneStringList* labels, bool* ok);
WhileStatement* ParseWhileStatement(ZoneStringList* labels, bool* ok);
Statement* ParseForStatement(ZoneStringList* labels, bool* ok);
Statement* ParseThrowStatement(bool* ok);
Expression* MakeCatchContext(Handle<String> id, VariableProxy* value);
TryStatement* ParseTryStatement(bool* ok);
DebuggerStatement* ParseDebuggerStatement(bool* ok);
Expression* ParseExpression(bool accept_IN, bool* ok);
Expression* ParseAssignmentExpression(bool accept_IN, bool* ok);
Expression* ParseConditionalExpression(bool accept_IN, bool* ok);
Expression* ParseBinaryExpression(int prec, bool accept_IN, bool* ok);
Expression* ParseUnaryExpression(bool* ok);
Expression* ParsePostfixExpression(bool* ok);
Expression* ParseLeftHandSideExpression(bool* ok);
Expression* ParseNewExpression(bool* ok);
Expression* ParseMemberExpression(bool* ok);
Expression* ParseNewPrefix(PositionStack* stack, bool* ok);
Expression* ParseMemberWithNewPrefixesExpression(PositionStack* stack,
bool* ok);
Expression* ParsePrimaryExpression(bool* ok);
Expression* ParseArrayLiteral(bool* ok);
Expression* ParseObjectLiteral(bool* ok);
ObjectLiteral::Property* ParseObjectLiteralGetSet(bool is_getter, bool* ok);
Expression* ParseRegExpLiteral(bool seen_equal, bool* ok);
Expression* NewCompareNode(Token::Value op,
Expression* x,
Expression* y,
int position);
// Populate the constant properties fixed array for a materialized object
// literal.
void BuildObjectLiteralConstantProperties(
ZoneList<ObjectLiteral::Property*>* properties,
Handle<FixedArray> constants,
bool* is_simple,
bool* fast_elements,
int* depth);
// Populate the literals fixed array for a materialized array literal.
void BuildArrayLiteralBoilerplateLiterals(ZoneList<Expression*>* properties,
Handle<FixedArray> constants,
bool* is_simple,
int* depth);
// Decide if a property should be in the object boilerplate.
bool IsBoilerplateProperty(ObjectLiteral::Property* property);
// If the expression is a literal, return the literal value;
// if the expression is a materialized literal and is simple return a
// compile time value as encoded by CompileTimeValue::GetValue().
// Otherwise, return undefined literal as the placeholder
// in the object literal boilerplate.
Handle<Object> GetBoilerplateValue(Expression* expression);
ZoneList<Expression*>* ParseArguments(bool* ok);
FunctionLiteral* ParseFunctionLiteral(Handle<String> var_name,
bool name_is_reserved,
int function_token_position,
FunctionLiteral::Type type,
bool* ok);
// Magical syntax support.
Expression* ParseV8Intrinsic(bool* ok);
INLINE(Token::Value peek()) {
if (stack_overflow_) return Token::ILLEGAL;
return scanner().peek();
}
INLINE(Token::Value Next()) {
// BUG 1215673: Find a thread safe way to set a stack limit in
// pre-parse mode. Otherwise, we cannot safely pre-parse from other
// threads.
if (stack_overflow_) {
return Token::ILLEGAL;
}
if (StackLimitCheck(isolate()).HasOverflowed()) {
// Any further calls to Next or peek will return the illegal token.
// The current call must return the next token, which might already
// have been peek'ed.
stack_overflow_ = true;
}
return scanner().Next();
}
bool peek_any_identifier();
INLINE(void Consume(Token::Value token));
void Expect(Token::Value token, bool* ok);
bool Check(Token::Value token);
void ExpectSemicolon(bool* ok);
Handle<String> LiteralString(PretenureFlag tenured) {
if (scanner().is_literal_ascii()) {
return isolate_->factory()->NewStringFromAscii(
scanner().literal_ascii_string(), tenured);
} else {
return isolate_->factory()->NewStringFromTwoByte(
scanner().literal_uc16_string(), tenured);
}
}
Handle<String> NextLiteralString(PretenureFlag tenured) {
if (scanner().is_next_literal_ascii()) {
return isolate_->factory()->NewStringFromAscii(
scanner().next_literal_ascii_string(), tenured);
} else {
return isolate_->factory()->NewStringFromTwoByte(
scanner().next_literal_uc16_string(), tenured);
}
}
Handle<String> GetSymbol(bool* ok);
// Get odd-ball literals.
Literal* GetLiteralUndefined();
Literal* GetLiteralTheHole();
Literal* GetLiteralNumber(double value);
Handle<String> ParseIdentifier(bool* ok);
Handle<String> ParseIdentifierOrStrictReservedWord(
bool* is_strict_reserved, bool* ok);
Handle<String> ParseIdentifierName(bool* ok);
Handle<String> ParseIdentifierNameOrGetOrSet(bool* is_get,
bool* is_set,
bool* ok);
// Strict mode validation of LValue expressions
void CheckStrictModeLValue(Expression* expression,
const char* error,
bool* ok);
// Strict mode octal literal validation.
void CheckOctalLiteral(int beg_pos, int end_pos, bool* ok);
// Parser support
VariableProxy* Declare(Handle<String> name, Variable::Mode mode,
FunctionLiteral* fun,
bool resolve,
bool* ok);
bool TargetStackContainsLabel(Handle<String> label);
BreakableStatement* LookupBreakTarget(Handle<String> label, bool* ok);
IterationStatement* LookupContinueTarget(Handle<String> label, bool* ok);
void RegisterTargetUse(Label* target, Target* stop);
// Factory methods.
Statement* EmptyStatement() {
static v8::internal::EmptyStatement empty;
return &empty;
}
Scope* NewScope(Scope* parent, Scope::Type type, bool inside_with);
Handle<String> LookupSymbol(int symbol_id);
Handle<String> LookupCachedSymbol(int symbol_id);
Expression* NewCall(Expression* expression,
ZoneList<Expression*>* arguments,
int pos) {
return new(zone()) Call(isolate(), expression, arguments, pos);
}
inline Literal* NewLiteral(Handle<Object> handle) {
return new(zone()) Literal(isolate(), handle);
}
// Create a number literal.
Literal* NewNumberLiteral(double value);
// Generate AST node that throw a ReferenceError with the given type.
Expression* NewThrowReferenceError(Handle<String> type);
// Generate AST node that throw a SyntaxError with the given
// type. The first argument may be null (in the handle sense) in
// which case no arguments are passed to the constructor.
Expression* NewThrowSyntaxError(Handle<String> type, Handle<Object> first);
// Generate AST node that throw a TypeError with the given
// type. Both arguments must be non-null (in the handle sense).
Expression* NewThrowTypeError(Handle<String> type,
Handle<Object> first,
Handle<Object> second);
// Generic AST generator for throwing errors from compiled code.
Expression* NewThrowError(Handle<String> constructor,
Handle<String> type,
Vector< Handle<Object> > arguments);
Isolate* isolate_;
ZoneList<Handle<String> > symbol_cache_;
Handle<Script> script_;
JavaScriptScanner scanner_;
Scope* top_scope_;
int with_nesting_level_;
LexicalScope* lexical_scope_;
Mode mode_;
Target* target_stack_; // for break, continue statements
bool allow_natives_syntax_;
v8::Extension* extension_;
bool is_pre_parsing_;
ScriptDataImpl* pre_data_;
FuncNameInferrer* fni_;
bool stack_overflow_;
// If true, the next (and immediately following) function literal is
// preceded by a parenthesis.
// Heuristically that means that the function will be called immediately,
// so never lazily compile it.
bool parenthesized_function_;
friend class LexicalScope;
};
// Support for handling complex values (array and object literals) that
// can be fully handled at compile time.
class CompileTimeValue: public AllStatic {
public:
enum Type {
OBJECT_LITERAL_FAST_ELEMENTS,
OBJECT_LITERAL_SLOW_ELEMENTS,
ARRAY_LITERAL
};
static bool IsCompileTimeValue(Expression* expression);
static bool ArrayLiteralElementNeedsInitialization(Expression* value);
// Get the value as a compile time value.
static Handle<FixedArray> GetValue(Expression* expression);
// Get the type of a compile time value returned by GetValue().
static Type GetType(Handle<FixedArray> value);
// Get the elements array of a compile time value returned by GetValue().
static Handle<FixedArray> GetElements(Handle<FixedArray> value);
private:
static const int kTypeSlot = 0;
static const int kElementsSlot = 1;
DISALLOW_IMPLICIT_CONSTRUCTORS(CompileTimeValue);
};
} } // namespace v8::internal
#endif // V8_PARSER_H_