mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 00:20:56 +00:00
e449cd0329
This patch does not provide a full async stack trace, it only ensures that the inner-most stack frame will be the point of the initial throw. BUG=http://dartbug.com/22009 R=iposva@google.com Review URL: https://codereview.chromium.org//893193004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@43463 260f80e4-7a28-3924-810f-c04153c831b5
12252 lines
457 KiB
C++
12252 lines
457 KiB
C++
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include "vm/parser.h"
|
|
|
|
#include "lib/invocation_mirror.h"
|
|
#include "platform/utils.h"
|
|
#include "vm/ast_transformer.h"
|
|
#include "vm/bootstrap.h"
|
|
#include "vm/class_finalizer.h"
|
|
#include "vm/compiler.h"
|
|
#include "vm/compiler_stats.h"
|
|
#include "vm/dart_api_impl.h"
|
|
#include "vm/dart_entry.h"
|
|
#include "vm/flags.h"
|
|
#include "vm/growable_array.h"
|
|
#include "vm/handles.h"
|
|
#include "vm/heap.h"
|
|
#include "vm/isolate.h"
|
|
#include "vm/longjump.h"
|
|
#include "vm/native_arguments.h"
|
|
#include "vm/native_entry.h"
|
|
#include "vm/object.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/os.h"
|
|
#include "vm/regexp_assembler.h"
|
|
#include "vm/report.h"
|
|
#include "vm/resolver.h"
|
|
#include "vm/scanner.h"
|
|
#include "vm/scopes.h"
|
|
#include "vm/stack_frame.h"
|
|
#include "vm/symbols.h"
|
|
#include "vm/tags.h"
|
|
#include "vm/timer.h"
|
|
#include "vm/zone.h"
|
|
|
|
namespace dart {
|
|
|
|
DEFINE_FLAG(bool, enable_asserts, false, "Enable assert statements.");
|
|
DEFINE_FLAG(bool, enable_type_checks, false, "Enable type checks.");
|
|
DEFINE_FLAG(bool, trace_parser, false, "Trace parser operations.");
|
|
DEFINE_FLAG(bool, warn_mixin_typedef, true, "Warning on legacy mixin typedef.");
|
|
DECLARE_FLAG(bool, error_on_bad_type);
|
|
DECLARE_FLAG(bool, throw_on_javascript_int_overflow);
|
|
DECLARE_FLAG(bool, warn_on_javascript_compatibility);
|
|
|
|
static void CheckedModeHandler(bool value) {
|
|
FLAG_enable_asserts = value;
|
|
FLAG_enable_type_checks = value;
|
|
}
|
|
|
|
// --enable-checked-mode and --checked both enable checked mode which is
|
|
// equivalent to setting --enable-asserts and --enable-type-checks.
|
|
DEFINE_FLAG_HANDLER(CheckedModeHandler,
|
|
enable_checked_mode,
|
|
"Enable checked mode.");
|
|
|
|
DEFINE_FLAG_HANDLER(CheckedModeHandler,
|
|
checked,
|
|
"Enable checked mode.");
|
|
|
|
|
|
// Quick access to the current isolate and zone.
|
|
#define I (thread()->isolate())
|
|
#define Z (zone())
|
|
|
|
|
|
#if defined(DEBUG)
|
|
class TraceParser : public ValueObject {
|
|
public:
|
|
TraceParser(intptr_t token_pos, const Script& script, const char* msg) {
|
|
if (FLAG_trace_parser) {
|
|
// Skips tracing of bootstrap libraries.
|
|
if (script.HasSource()) {
|
|
intptr_t line, column;
|
|
script.GetTokenLocation(token_pos, &line, &column);
|
|
PrintIndent();
|
|
OS::Print("%s (line %" Pd ", col %" Pd ", token %" Pd ")\n",
|
|
msg, line, column, token_pos);
|
|
}
|
|
indent_++;
|
|
}
|
|
}
|
|
~TraceParser() { indent_--; }
|
|
private:
|
|
void PrintIndent() {
|
|
for (int i = 0; i < indent_; i++) { OS::Print(". "); }
|
|
}
|
|
static int indent_;
|
|
};
|
|
|
|
int TraceParser::indent_ = 0;
|
|
|
|
#define TRACE_PARSER(s) \
|
|
TraceParser __p__(this->TokenPos(), this->script_, s)
|
|
|
|
#else // not DEBUG
|
|
#define TRACE_PARSER(s)
|
|
#endif // DEBUG
|
|
|
|
|
|
class BoolScope : public ValueObject {
|
|
public:
|
|
BoolScope(bool* addr, bool new_value) : _addr(addr), _saved_value(*addr) {
|
|
*_addr = new_value;
|
|
}
|
|
~BoolScope() {
|
|
*_addr = _saved_value;
|
|
}
|
|
|
|
private:
|
|
bool* _addr;
|
|
bool _saved_value;
|
|
};
|
|
|
|
|
|
static RawTypeArguments* NewTypeArguments(const GrowableObjectArray& objs) {
|
|
const TypeArguments& a =
|
|
TypeArguments::Handle(TypeArguments::New(objs.Length()));
|
|
AbstractType& type = AbstractType::Handle();
|
|
for (int i = 0; i < objs.Length(); i++) {
|
|
type ^= objs.At(i);
|
|
a.SetTypeAt(i, type);
|
|
}
|
|
// Cannot canonicalize TypeArgument yet as its types may not have been
|
|
// finalized yet.
|
|
return a.raw();
|
|
}
|
|
|
|
|
|
LocalVariable* ParsedFunction::EnsureExpressionTemp() {
|
|
if (!has_expression_temp_var()) {
|
|
LocalVariable* temp =
|
|
new (Z) LocalVariable(function_.token_pos(),
|
|
Symbols::ExprTemp(),
|
|
Type::ZoneHandle(Type::DynamicType()));
|
|
ASSERT(temp != NULL);
|
|
set_expression_temp_var(temp);
|
|
}
|
|
ASSERT(has_expression_temp_var());
|
|
return expression_temp_var();
|
|
}
|
|
|
|
|
|
void ParsedFunction::EnsureFinallyReturnTemp() {
|
|
if (!has_finally_return_temp_var()) {
|
|
LocalVariable* temp = new(Z) LocalVariable(
|
|
function_.token_pos(),
|
|
String::ZoneHandle(Z, Symbols::New(":finally_ret_val")),
|
|
Type::ZoneHandle(Z, Type::DynamicType()));
|
|
ASSERT(temp != NULL);
|
|
temp->set_is_final();
|
|
set_finally_return_temp_var(temp);
|
|
}
|
|
ASSERT(has_finally_return_temp_var());
|
|
}
|
|
|
|
|
|
void ParsedFunction::SetNodeSequence(SequenceNode* node_sequence) {
|
|
ASSERT(node_sequence_ == NULL);
|
|
ASSERT(node_sequence != NULL);
|
|
node_sequence_ = node_sequence;
|
|
}
|
|
|
|
|
|
void ParsedFunction::SetRegExpCompileData(
|
|
RegExpCompileData* regexp_compile_data) {
|
|
ASSERT(regexp_compile_data_ == NULL);
|
|
ASSERT(regexp_compile_data != NULL);
|
|
regexp_compile_data_ = regexp_compile_data;
|
|
}
|
|
|
|
|
|
void ParsedFunction::AddDeferredPrefix(const LibraryPrefix& prefix) {
|
|
ASSERT(prefix.is_deferred_load());
|
|
ASSERT(!prefix.is_loaded());
|
|
for (intptr_t i = 0; i < deferred_prefixes_->length(); i++) {
|
|
if ((*deferred_prefixes_)[i]->raw() == prefix.raw()) {
|
|
return;
|
|
}
|
|
}
|
|
deferred_prefixes_->Add(&LibraryPrefix::ZoneHandle(Z, prefix.raw()));
|
|
}
|
|
|
|
|
|
void ParsedFunction::AllocateVariables() {
|
|
ASSERT(!function().IsIrregexpFunction());
|
|
LocalScope* scope = node_sequence()->scope();
|
|
const intptr_t num_fixed_params = function().num_fixed_parameters();
|
|
const intptr_t num_opt_params = function().NumOptionalParameters();
|
|
const intptr_t num_params = num_fixed_params + num_opt_params;
|
|
// Compute start indices to parameters and locals, and the number of
|
|
// parameters to copy.
|
|
if (num_opt_params == 0) {
|
|
// Parameter i will be at fp[kParamEndSlotFromFp + num_params - i] and
|
|
// local variable j will be at fp[kFirstLocalSlotFromFp - j].
|
|
first_parameter_index_ = kParamEndSlotFromFp + num_params;
|
|
first_stack_local_index_ = kFirstLocalSlotFromFp;
|
|
num_copied_params_ = 0;
|
|
} else {
|
|
// Parameter i will be at fp[kFirstLocalSlotFromFp - i] and local variable
|
|
// j will be at fp[kFirstLocalSlotFromFp - num_params - j].
|
|
first_parameter_index_ = kFirstLocalSlotFromFp;
|
|
first_stack_local_index_ = first_parameter_index_ - num_params;
|
|
num_copied_params_ = num_params;
|
|
}
|
|
|
|
// Allocate parameters and local variables, either in the local frame or
|
|
// in the context(s).
|
|
LocalScope* context_owner = NULL; // No context needed yet.
|
|
bool found_captured_variables = false;
|
|
int next_free_frame_index =
|
|
scope->AllocateVariables(first_parameter_index_,
|
|
num_params,
|
|
first_stack_local_index_,
|
|
scope,
|
|
&context_owner,
|
|
&found_captured_variables);
|
|
|
|
// Frame indices are relative to the frame pointer and are decreasing.
|
|
ASSERT(next_free_frame_index <= first_stack_local_index_);
|
|
num_stack_locals_ = first_stack_local_index_ - next_free_frame_index;
|
|
}
|
|
|
|
|
|
struct CatchParamDesc {
|
|
CatchParamDesc()
|
|
: token_pos(0), type(NULL), name(NULL), var(NULL) { }
|
|
intptr_t token_pos;
|
|
const AbstractType* type;
|
|
const String* name;
|
|
LocalVariable* var;
|
|
};
|
|
|
|
|
|
void ParsedFunction::AllocateIrregexpVariables(intptr_t num_stack_locals) {
|
|
ASSERT(function().IsIrregexpFunction());
|
|
ASSERT(function().NumOptionalParameters() == 0);
|
|
const intptr_t num_params = function().num_fixed_parameters();
|
|
ASSERT(num_params == RegExpMacroAssembler::kParamCount);
|
|
// Compute start indices to parameters and locals, and the number of
|
|
// parameters to copy.
|
|
// Parameter i will be at fp[kParamEndSlotFromFp + num_params - i] and
|
|
// local variable j will be at fp[kFirstLocalSlotFromFp - j].
|
|
first_parameter_index_ = kParamEndSlotFromFp + num_params;
|
|
first_stack_local_index_ = kFirstLocalSlotFromFp;
|
|
num_copied_params_ = 0;
|
|
|
|
// Frame indices are relative to the frame pointer and are decreasing.
|
|
num_stack_locals_ = num_stack_locals;
|
|
}
|
|
|
|
|
|
struct Parser::Block : public ZoneAllocated {
|
|
Block(Block* outer_block, LocalScope* local_scope, SequenceNode* seq)
|
|
: parent(outer_block), scope(local_scope), statements(seq) {
|
|
ASSERT(scope != NULL);
|
|
ASSERT(statements != NULL);
|
|
}
|
|
Block* parent; // Enclosing block, or NULL if outermost.
|
|
LocalScope* scope;
|
|
SequenceNode* statements;
|
|
};
|
|
|
|
|
|
// Class which describes an inlined finally block which is used to generate
|
|
// inlined code for the finally blocks when there is an exit from a try
|
|
// block using 'return', 'break' or 'continue'.
|
|
class Parser::TryBlocks : public ZoneAllocated {
|
|
public:
|
|
TryBlocks(Block* try_block, TryBlocks* outer_try_block, intptr_t try_index)
|
|
: try_block_(try_block),
|
|
inlined_finally_nodes_(),
|
|
outer_try_block_(outer_try_block),
|
|
try_index_(try_index),
|
|
inside_catch_(false) { }
|
|
|
|
TryBlocks* outer_try_block() const { return outer_try_block_; }
|
|
Block* try_block() const { return try_block_; }
|
|
intptr_t try_index() const { return try_index_; }
|
|
bool inside_catch() const { return inside_catch_; }
|
|
void enter_catch() { inside_catch_ = true; }
|
|
|
|
void AddNodeForFinallyInlining(AstNode* node);
|
|
AstNode* GetNodeToInlineFinally(int index) {
|
|
if (0 <= index && index < inlined_finally_nodes_.length()) {
|
|
return inlined_finally_nodes_[index];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
private:
|
|
Block* try_block_;
|
|
GrowableArray<AstNode*> inlined_finally_nodes_;
|
|
TryBlocks* outer_try_block_;
|
|
const intptr_t try_index_;
|
|
bool inside_catch_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(TryBlocks);
|
|
};
|
|
|
|
|
|
void Parser::TryBlocks::AddNodeForFinallyInlining(AstNode* node) {
|
|
inlined_finally_nodes_.Add(node);
|
|
}
|
|
|
|
|
|
// For parsing a compilation unit.
|
|
Parser::Parser(const Script& script, const Library& library, intptr_t token_pos)
|
|
: thread_(Thread::Current()),
|
|
script_(Script::Handle(zone(), script.raw())),
|
|
tokens_iterator_(TokenStream::Handle(zone(), script.tokens()),
|
|
token_pos),
|
|
token_kind_(Token::kILLEGAL),
|
|
current_block_(NULL),
|
|
is_top_level_(false),
|
|
await_is_keyword_(false),
|
|
current_member_(NULL),
|
|
allow_function_literals_(true),
|
|
parsed_function_(NULL),
|
|
innermost_function_(Function::Handle(zone())),
|
|
literal_token_(LiteralToken::Handle(zone())),
|
|
current_class_(Class::Handle(zone())),
|
|
library_(Library::Handle(zone(), library.raw())),
|
|
try_blocks_list_(NULL),
|
|
last_used_try_index_(0),
|
|
unregister_pending_function_(false),
|
|
async_temp_scope_(NULL) {
|
|
ASSERT(tokens_iterator_.IsValid());
|
|
ASSERT(!library.IsNull());
|
|
}
|
|
|
|
|
|
// For parsing a function.
|
|
Parser::Parser(const Script& script,
|
|
ParsedFunction* parsed_function,
|
|
intptr_t token_position)
|
|
: thread_(Thread::Current()),
|
|
script_(Script::Handle(zone(), script.raw())),
|
|
tokens_iterator_(TokenStream::Handle(zone(), script.tokens()),
|
|
token_position),
|
|
token_kind_(Token::kILLEGAL),
|
|
current_block_(NULL),
|
|
is_top_level_(false),
|
|
await_is_keyword_(false),
|
|
current_member_(NULL),
|
|
allow_function_literals_(true),
|
|
parsed_function_(parsed_function),
|
|
innermost_function_(Function::Handle(zone(),
|
|
parsed_function->function().raw())),
|
|
literal_token_(LiteralToken::Handle(zone())),
|
|
current_class_(Class::Handle(zone(),
|
|
parsed_function->function().Owner())),
|
|
library_(Library::Handle(zone(), Class::Handle(
|
|
zone(),
|
|
parsed_function->function().origin()).library())),
|
|
try_blocks_list_(NULL),
|
|
last_used_try_index_(0),
|
|
unregister_pending_function_(false),
|
|
async_temp_scope_(NULL) {
|
|
ASSERT(tokens_iterator_.IsValid());
|
|
ASSERT(!current_function().IsNull());
|
|
EnsureExpressionTemp();
|
|
}
|
|
|
|
|
|
Parser::~Parser() {
|
|
if (unregister_pending_function_) {
|
|
const GrowableObjectArray& pending_functions =
|
|
GrowableObjectArray::Handle(I->object_store()->pending_functions());
|
|
ASSERT(pending_functions.Length() > 0);
|
|
ASSERT(pending_functions.At(pending_functions.Length()-1) ==
|
|
current_function().raw());
|
|
pending_functions.RemoveLast();
|
|
}
|
|
}
|
|
|
|
|
|
// Each try in this function gets its own try index.
|
|
// See definition of RawPcDescriptors::PcDescriptor.
|
|
int16_t Parser::AllocateTryIndex() {
|
|
if (!Utils::IsInt(16, last_used_try_index_ - 1)) {
|
|
ReportError("too many nested try statements");
|
|
}
|
|
return last_used_try_index_++;
|
|
}
|
|
|
|
|
|
void Parser::SetScript(const Script& script, intptr_t token_pos) {
|
|
script_ = script.raw();
|
|
tokens_iterator_.SetStream(
|
|
TokenStream::Handle(Z, script.tokens()), token_pos);
|
|
token_kind_ = Token::kILLEGAL;
|
|
}
|
|
|
|
|
|
bool Parser::SetAllowFunctionLiterals(bool value) {
|
|
bool current_value = allow_function_literals_;
|
|
allow_function_literals_ = value;
|
|
return current_value;
|
|
}
|
|
|
|
|
|
const Function& Parser::current_function() const {
|
|
ASSERT(parsed_function() != NULL);
|
|
return parsed_function()->function();
|
|
}
|
|
|
|
|
|
const Function& Parser::innermost_function() const {
|
|
return innermost_function_;
|
|
}
|
|
|
|
|
|
const Class& Parser::current_class() const {
|
|
return current_class_;
|
|
}
|
|
|
|
|
|
void Parser::set_current_class(const Class& value) {
|
|
current_class_ = value.raw();
|
|
}
|
|
|
|
|
|
void Parser::SetPosition(intptr_t position) {
|
|
if (position < TokenPos() && position != 0) {
|
|
CompilerStats::num_tokens_rewind += (TokenPos() - position);
|
|
}
|
|
tokens_iterator_.SetCurrentPosition(position);
|
|
token_kind_ = Token::kILLEGAL;
|
|
}
|
|
|
|
|
|
void Parser::ParseCompilationUnit(const Library& library,
|
|
const Script& script) {
|
|
Isolate* isolate = Isolate::Current();
|
|
ASSERT(isolate->long_jump_base()->IsSafeToJump());
|
|
TimerScope timer(FLAG_compiler_stats, &CompilerStats::parser_timer);
|
|
VMTagScope tagScope(isolate, VMTag::kCompileTopLevelTagId);
|
|
Parser parser(script, library, 0);
|
|
parser.ParseTopLevel();
|
|
}
|
|
|
|
|
|
void Parser::ComputeCurrentToken() {
|
|
ASSERT(token_kind_ == Token::kILLEGAL);
|
|
token_kind_ = tokens_iterator_.CurrentTokenKind();
|
|
if (token_kind_ == Token::kERROR) {
|
|
ReportError(TokenPos(), "%s", CurrentLiteral()->ToCString());
|
|
}
|
|
}
|
|
|
|
|
|
Token::Kind Parser::LookaheadToken(int num_tokens) {
|
|
CompilerStats::num_tokens_lookahead++;
|
|
CompilerStats::num_token_checks++;
|
|
return tokens_iterator_.LookaheadTokenKind(num_tokens);
|
|
}
|
|
|
|
|
|
String* Parser::CurrentLiteral() const {
|
|
String& result =
|
|
String::ZoneHandle(Z, tokens_iterator_.CurrentLiteral());
|
|
return &result;
|
|
}
|
|
|
|
|
|
RawDouble* Parser::CurrentDoubleLiteral() const {
|
|
literal_token_ ^= tokens_iterator_.CurrentToken();
|
|
ASSERT(literal_token_.kind() == Token::kDOUBLE);
|
|
return Double::RawCast(literal_token_.value());
|
|
}
|
|
|
|
|
|
RawInteger* Parser::CurrentIntegerLiteral() const {
|
|
literal_token_ ^= tokens_iterator_.CurrentToken();
|
|
ASSERT(literal_token_.kind() == Token::kINTEGER);
|
|
RawInteger* ri = Integer::RawCast(literal_token_.value());
|
|
if (FLAG_throw_on_javascript_int_overflow) {
|
|
const Integer& i = Integer::Handle(Z, ri);
|
|
if (i.CheckJavascriptIntegerOverflow()) {
|
|
ReportError(TokenPos(),
|
|
"Integer literal does not fit in a Javascript integer: %s.",
|
|
i.ToCString());
|
|
}
|
|
}
|
|
return ri;
|
|
}
|
|
|
|
|
|
struct ParamDesc {
|
|
ParamDesc()
|
|
: type(NULL),
|
|
name_pos(0),
|
|
name(NULL),
|
|
default_value(NULL),
|
|
metadata(NULL),
|
|
var(NULL),
|
|
is_final(false),
|
|
is_field_initializer(false),
|
|
has_explicit_type(false) { }
|
|
const AbstractType* type;
|
|
intptr_t name_pos;
|
|
const String* name;
|
|
const Object* default_value; // NULL if not an optional parameter.
|
|
const Object* metadata; // NULL if no metadata or metadata not evaluated.
|
|
LocalVariable* var; // Scope variable allocated for this parameter.
|
|
bool is_final;
|
|
bool is_field_initializer;
|
|
bool has_explicit_type;
|
|
};
|
|
|
|
|
|
struct ParamList {
|
|
ParamList() {
|
|
Clear();
|
|
}
|
|
|
|
void Clear() {
|
|
num_fixed_parameters = 0;
|
|
num_optional_parameters = 0;
|
|
has_optional_positional_parameters = false;
|
|
has_optional_named_parameters = false;
|
|
has_explicit_default_values = false;
|
|
has_field_initializer = false;
|
|
implicitly_final = false;
|
|
skipped = false;
|
|
this->parameters = new ZoneGrowableArray<ParamDesc>();
|
|
}
|
|
|
|
void AddFinalParameter(intptr_t name_pos,
|
|
const String* name,
|
|
const AbstractType* type) {
|
|
this->num_fixed_parameters++;
|
|
ParamDesc param;
|
|
param.name_pos = name_pos;
|
|
param.name = name;
|
|
param.is_final = true;
|
|
param.type = type;
|
|
this->parameters->Add(param);
|
|
}
|
|
|
|
void AddReceiver(const AbstractType* receiver_type, intptr_t token_pos) {
|
|
ASSERT(this->parameters->is_empty());
|
|
AddFinalParameter(token_pos, &Symbols::This(), receiver_type);
|
|
}
|
|
|
|
|
|
// Make the parameter variables visible/invisible.
|
|
// Field initializer parameters are always invisible.
|
|
void SetInvisible(bool invisible) {
|
|
const intptr_t num_params = parameters->length();
|
|
for (int i = 0; i < num_params; i++) {
|
|
ParamDesc& param = (*parameters)[i];
|
|
ASSERT(param.var != NULL);
|
|
if (!param.is_field_initializer) {
|
|
param.var->set_invisible(invisible);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetImplicitlyFinal() {
|
|
implicitly_final = true;
|
|
}
|
|
|
|
int num_fixed_parameters;
|
|
int num_optional_parameters;
|
|
bool has_optional_positional_parameters;
|
|
bool has_optional_named_parameters;
|
|
bool has_explicit_default_values;
|
|
bool has_field_initializer;
|
|
bool implicitly_final;
|
|
bool skipped;
|
|
ZoneGrowableArray<ParamDesc>* parameters;
|
|
};
|
|
|
|
|
|
struct MemberDesc {
|
|
MemberDesc() {
|
|
Clear();
|
|
}
|
|
void Clear() {
|
|
has_abstract = false;
|
|
has_external = false;
|
|
has_final = false;
|
|
has_const = false;
|
|
has_static = false;
|
|
has_var = false;
|
|
has_factory = false;
|
|
has_operator = false;
|
|
has_native = false;
|
|
metadata_pos = -1;
|
|
operator_token = Token::kILLEGAL;
|
|
type = NULL;
|
|
name_pos = 0;
|
|
name = NULL;
|
|
redirect_name = NULL;
|
|
dict_name = NULL;
|
|
params.Clear();
|
|
kind = RawFunction::kRegularFunction;
|
|
field_ = NULL;
|
|
}
|
|
bool IsConstructor() const {
|
|
return (kind == RawFunction::kConstructor) && !has_static;
|
|
}
|
|
bool IsFactory() const {
|
|
return (kind == RawFunction::kConstructor) && has_static;
|
|
}
|
|
bool IsFactoryOrConstructor() const {
|
|
return (kind == RawFunction::kConstructor);
|
|
}
|
|
bool IsGetter() const {
|
|
return kind == RawFunction::kGetterFunction;
|
|
}
|
|
bool IsSetter() const {
|
|
return kind == RawFunction::kSetterFunction;
|
|
}
|
|
const char* ToCString() const {
|
|
if (field_ != NULL) {
|
|
return "field";
|
|
} else if (IsConstructor()) {
|
|
return "constructor";
|
|
} else if (IsFactory()) {
|
|
return "factory";
|
|
} else if (IsGetter()) {
|
|
return "getter";
|
|
} else if (IsSetter()) {
|
|
return "setter";
|
|
}
|
|
return "method";
|
|
}
|
|
String* DictName() const {
|
|
return (dict_name != NULL) ? dict_name : name;
|
|
}
|
|
bool has_abstract;
|
|
bool has_external;
|
|
bool has_final;
|
|
bool has_const;
|
|
bool has_static;
|
|
bool has_var;
|
|
bool has_factory;
|
|
bool has_operator;
|
|
bool has_native;
|
|
intptr_t metadata_pos;
|
|
Token::Kind operator_token;
|
|
const AbstractType* type;
|
|
intptr_t name_pos;
|
|
intptr_t decl_begin_pos;
|
|
String* name;
|
|
// For constructors: NULL or name of redirected to constructor.
|
|
String* redirect_name;
|
|
// dict_name is the name used for the class namespace, if it
|
|
// differs from 'name'.
|
|
// For constructors: NULL for unnamed constructor,
|
|
// identifier after classname for named constructors.
|
|
// For getters and setters: unmangled name.
|
|
String* dict_name;
|
|
ParamList params;
|
|
RawFunction::Kind kind;
|
|
// NULL for functions, field object for static or instance fields.
|
|
Field* field_;
|
|
};
|
|
|
|
|
|
class ClassDesc : public ValueObject {
|
|
public:
|
|
ClassDesc(const Class& cls,
|
|
const String& cls_name,
|
|
bool is_interface,
|
|
intptr_t token_pos)
|
|
: clazz_(cls),
|
|
class_name_(cls_name),
|
|
token_pos_(token_pos),
|
|
functions_(GrowableObjectArray::Handle(GrowableObjectArray::New())),
|
|
fields_(GrowableObjectArray::Handle(GrowableObjectArray::New())) {
|
|
}
|
|
|
|
void AddFunction(const Function& function) {
|
|
functions_.Add(function);
|
|
}
|
|
|
|
const GrowableObjectArray& functions() const {
|
|
return functions_;
|
|
}
|
|
|
|
void AddField(const Field& field) {
|
|
fields_.Add(field);
|
|
}
|
|
|
|
const GrowableObjectArray& fields() const {
|
|
return fields_;
|
|
}
|
|
|
|
const Class& clazz() const {
|
|
return clazz_;
|
|
}
|
|
|
|
const String& class_name() const {
|
|
return class_name_;
|
|
}
|
|
|
|
bool has_constructor() const {
|
|
Function& func = Function::Handle();
|
|
for (int i = 0; i < functions_.Length(); i++) {
|
|
func ^= functions_.At(i);
|
|
if (func.kind() == RawFunction::kConstructor) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
intptr_t token_pos() const {
|
|
return token_pos_;
|
|
}
|
|
|
|
void AddMember(const MemberDesc& member) {
|
|
members_.Add(member);
|
|
}
|
|
|
|
const GrowableArray<MemberDesc>& members() const {
|
|
return members_;
|
|
}
|
|
|
|
MemberDesc* LookupMember(const String& name) const {
|
|
for (int i = 0; i < members_.length(); i++) {
|
|
if (name.Equals(*members_[i].name)) {
|
|
return &members_[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
private:
|
|
const Class& clazz_;
|
|
const String& class_name_;
|
|
intptr_t token_pos_; // Token index of "class" keyword.
|
|
GrowableObjectArray& functions_;
|
|
GrowableObjectArray& fields_;
|
|
GrowableArray<MemberDesc> members_;
|
|
};
|
|
|
|
|
|
struct TopLevel {
|
|
TopLevel() :
|
|
fields(GrowableObjectArray::Handle(GrowableObjectArray::New())),
|
|
functions(GrowableObjectArray::Handle(GrowableObjectArray::New())) { }
|
|
|
|
GrowableObjectArray& fields;
|
|
GrowableObjectArray& functions;
|
|
};
|
|
|
|
|
|
static bool HasReturnNode(SequenceNode* seq) {
|
|
if (seq->length() == 0) {
|
|
return false;
|
|
} else if ((seq->length()) == 1 &&
|
|
(seq->NodeAt(seq->length() - 1)->IsSequenceNode())) {
|
|
return HasReturnNode(seq->NodeAt(seq->length() - 1)->AsSequenceNode());
|
|
} else {
|
|
return seq->NodeAt(seq->length() - 1)->IsReturnNode();
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::ParseClass(const Class& cls) {
|
|
if (!cls.is_synthesized_class()) {
|
|
Isolate* isolate = Isolate::Current();
|
|
TimerScope timer(FLAG_compiler_stats, &CompilerStats::parser_timer);
|
|
ASSERT(isolate->long_jump_base()->IsSafeToJump());
|
|
const Script& script = Script::Handle(isolate, cls.script());
|
|
const Library& lib = Library::Handle(isolate, cls.library());
|
|
Parser parser(script, lib, cls.token_pos());
|
|
parser.ParseClassDefinition(cls);
|
|
} else if (cls.is_enum_class()) {
|
|
Isolate* isolate = Isolate::Current();
|
|
TimerScope timer(FLAG_compiler_stats, &CompilerStats::parser_timer);
|
|
ASSERT(isolate->long_jump_base()->IsSafeToJump());
|
|
const Script& script = Script::Handle(isolate, cls.script());
|
|
const Library& lib = Library::Handle(isolate, cls.library());
|
|
Parser parser(script, lib, cls.token_pos());
|
|
parser.ParseEnumDefinition(cls);
|
|
}
|
|
}
|
|
|
|
|
|
RawObject* Parser::ParseFunctionParameters(const Function& func) {
|
|
ASSERT(!func.IsNull());
|
|
Thread* thread = Thread::Current();
|
|
Isolate* isolate = thread->isolate();
|
|
StackZone stack_zone(isolate);
|
|
Zone* zone = stack_zone.GetZone();
|
|
LongJumpScope jump;
|
|
if (setjmp(*jump.Set()) == 0) {
|
|
const Script& script = Script::Handle(zone, func.script());
|
|
const Class& owner = Class::Handle(zone, func.Owner());
|
|
ASSERT(!owner.IsNull());
|
|
ParsedFunction* parsed_function = new ParsedFunction(
|
|
thread, Function::ZoneHandle(zone, func.raw()));
|
|
Parser parser(script, parsed_function, func.token_pos());
|
|
parser.SkipFunctionPreamble();
|
|
ParamList params;
|
|
parser.ParseFormalParameterList(true, true, ¶ms);
|
|
ParamDesc* param = params.parameters->data();
|
|
const int param_cnt = params.num_fixed_parameters +
|
|
params.num_optional_parameters;
|
|
const Array& param_descriptor =
|
|
Array::Handle(Array::New(param_cnt * kParameterEntrySize));
|
|
for (int i = 0, j = 0; i < param_cnt; i++, j += kParameterEntrySize) {
|
|
param_descriptor.SetAt(j + kParameterIsFinalOffset,
|
|
param[i].is_final ? Bool::True() : Bool::False());
|
|
param_descriptor.SetAt(j + kParameterDefaultValueOffset,
|
|
(param[i].default_value == NULL) ? Object::null_instance() :
|
|
*(param[i].default_value));
|
|
const Object* metadata = param[i].metadata;
|
|
if ((metadata != NULL) && (*metadata).IsError()) {
|
|
return metadata->raw(); // Error evaluating the metadata.
|
|
}
|
|
param_descriptor.SetAt(j + kParameterMetadataOffset,
|
|
(param[i].metadata == NULL) ? Object::null_instance() :
|
|
*(param[i].metadata));
|
|
}
|
|
return param_descriptor.raw();
|
|
} else {
|
|
Error& error = Error::Handle();
|
|
error = isolate->object_store()->sticky_error();
|
|
isolate->object_store()->clear_sticky_error();
|
|
return error.raw();
|
|
}
|
|
UNREACHABLE();
|
|
return Object::null();
|
|
}
|
|
|
|
|
|
void Parser::ParseFunction(ParsedFunction* parsed_function) {
|
|
Isolate* isolate = parsed_function->isolate();
|
|
Zone* zone = parsed_function->zone();
|
|
TimerScope timer(FLAG_compiler_stats, &CompilerStats::parser_timer);
|
|
CompilerStats::num_functions_compiled++;
|
|
ASSERT(isolate->long_jump_base()->IsSafeToJump());
|
|
ASSERT(parsed_function != NULL);
|
|
const Function& func = parsed_function->function();
|
|
const Script& script = Script::Handle(zone, func.script());
|
|
Parser parser(script, parsed_function, func.token_pos());
|
|
SequenceNode* node_sequence = NULL;
|
|
Array& default_parameter_values = Array::ZoneHandle(zone, Array::null());
|
|
switch (func.kind()) {
|
|
case RawFunction::kRegularFunction:
|
|
case RawFunction::kClosureFunction:
|
|
case RawFunction::kGetterFunction:
|
|
case RawFunction::kSetterFunction:
|
|
case RawFunction::kConstructor:
|
|
// The call to a redirecting factory is redirected.
|
|
ASSERT(!func.IsRedirectingFactory());
|
|
if (!func.IsImplicitConstructor()) {
|
|
parser.SkipFunctionPreamble();
|
|
}
|
|
node_sequence = parser.ParseFunc(func, &default_parameter_values);
|
|
break;
|
|
case RawFunction::kImplicitGetter:
|
|
ASSERT(!func.is_static());
|
|
node_sequence = parser.ParseInstanceGetter(func);
|
|
break;
|
|
case RawFunction::kImplicitSetter:
|
|
ASSERT(!func.is_static());
|
|
node_sequence = parser.ParseInstanceSetter(func);
|
|
break;
|
|
case RawFunction::kImplicitStaticFinalGetter:
|
|
node_sequence = parser.ParseStaticFinalGetter(func);
|
|
CompilerStats::num_implicit_final_getters++;
|
|
break;
|
|
case RawFunction::kMethodExtractor:
|
|
node_sequence = parser.ParseMethodExtractor(func);
|
|
break;
|
|
case RawFunction::kNoSuchMethodDispatcher:
|
|
node_sequence =
|
|
parser.ParseNoSuchMethodDispatcher(func, &default_parameter_values);
|
|
break;
|
|
case RawFunction::kInvokeFieldDispatcher:
|
|
node_sequence =
|
|
parser.ParseInvokeFieldDispatcher(func, &default_parameter_values);
|
|
break;
|
|
case RawFunction::kIrregexpFunction:
|
|
UNREACHABLE(); // Irregexp functions have their own parser.
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
|
|
if (!HasReturnNode(node_sequence)) {
|
|
// Add implicit return node.
|
|
node_sequence->Add(new ReturnNode(func.end_token_pos()));
|
|
}
|
|
if (parsed_function->has_expression_temp_var()) {
|
|
node_sequence->scope()->AddVariable(parsed_function->expression_temp_var());
|
|
}
|
|
node_sequence->scope()->AddVariable(
|
|
parsed_function->current_context_var());
|
|
if (parsed_function->has_finally_return_temp_var()) {
|
|
node_sequence->scope()->AddVariable(
|
|
parsed_function->finally_return_temp_var());
|
|
}
|
|
parsed_function->SetNodeSequence(node_sequence);
|
|
|
|
// The instantiator may be required at run time for generic type checks or
|
|
// allocation of generic types.
|
|
if (parser.IsInstantiatorRequired()) {
|
|
// In the case of a local function, only set the instantiator if the
|
|
// receiver (or type arguments parameter of a factory) was captured.
|
|
LocalVariable* instantiator = NULL;
|
|
const bool kTestOnly = true;
|
|
if (parser.current_function().IsInFactoryScope()) {
|
|
instantiator = parser.LookupTypeArgumentsParameter(node_sequence->scope(),
|
|
kTestOnly);
|
|
} else {
|
|
instantiator = parser.LookupReceiver(node_sequence->scope(), kTestOnly);
|
|
}
|
|
if (!parser.current_function().IsLocalFunction() ||
|
|
((instantiator != NULL) && instantiator->is_captured())) {
|
|
parsed_function->set_instantiator(instantiator);
|
|
}
|
|
}
|
|
|
|
parsed_function->set_default_parameter_values(default_parameter_values);
|
|
}
|
|
|
|
|
|
RawObject* Parser::ParseMetadata(const Class& cls, intptr_t token_pos) {
|
|
Thread* thread = Thread::Current();
|
|
Isolate* isolate = thread->isolate();
|
|
StackZone stack_zone(isolate);
|
|
Zone* zone = stack_zone.GetZone();
|
|
LongJumpScope jump;
|
|
if (setjmp(*jump.Set()) == 0) {
|
|
const Script& script = Script::Handle(zone, cls.script());
|
|
// Parsing metadata can involve following paths in the parser that are
|
|
// normally used for expressions and assume current_function is non-null,
|
|
// so we create a fake function to use as the current_function rather than
|
|
// scattering special cases throughout the parser.
|
|
const Function& fake_function = Function::ZoneHandle(zone, Function::New(
|
|
Symbols::At(),
|
|
RawFunction::kRegularFunction,
|
|
true, // is_static
|
|
false, // is_const
|
|
false, // is_abstract
|
|
false, // is_external
|
|
false, // is_native
|
|
cls,
|
|
token_pos));
|
|
fake_function.set_is_debuggable(false);
|
|
ParsedFunction* parsed_function =
|
|
new ParsedFunction(thread, fake_function);
|
|
Parser parser(script, parsed_function, token_pos);
|
|
parser.set_current_class(cls);
|
|
|
|
RawObject* metadata = parser.EvaluateMetadata();
|
|
return metadata;
|
|
} else {
|
|
Error& error = Error::Handle(zone);
|
|
error = isolate->object_store()->sticky_error();
|
|
isolate->object_store()->clear_sticky_error();
|
|
return error.raw();
|
|
}
|
|
UNREACHABLE();
|
|
return Object::null();
|
|
}
|
|
|
|
|
|
RawArray* Parser::EvaluateMetadata() {
|
|
CheckToken(Token::kAT, "Metadata character '@' expected");
|
|
GrowableObjectArray& meta_values =
|
|
GrowableObjectArray::Handle(Z, GrowableObjectArray::New());
|
|
while (CurrentToken() == Token::kAT) {
|
|
ConsumeToken();
|
|
intptr_t expr_pos = TokenPos();
|
|
if (!IsIdentifier()) {
|
|
ExpectIdentifier("identifier expected");
|
|
}
|
|
// Reject expressions with deferred library prefix eagerly.
|
|
Object& obj = Object::Handle(Z,
|
|
library_.LookupLocalObject(*CurrentLiteral()));
|
|
if (!obj.IsNull() && obj.IsLibraryPrefix()) {
|
|
if (LibraryPrefix::Cast(obj).is_deferred_load()) {
|
|
ReportError("Metadata must be compile-time constant");
|
|
}
|
|
}
|
|
AstNode* expr = NULL;
|
|
if ((LookaheadToken(1) == Token::kLPAREN) ||
|
|
((LookaheadToken(1) == Token::kPERIOD) &&
|
|
(LookaheadToken(3) == Token::kLPAREN)) ||
|
|
((LookaheadToken(1) == Token::kPERIOD) &&
|
|
(LookaheadToken(3) == Token::kPERIOD) &&
|
|
(LookaheadToken(5) == Token::kLPAREN))) {
|
|
expr = ParseNewOperator(Token::kCONST);
|
|
} else {
|
|
// Can be x, C.x, or L.C.x.
|
|
expr = ParsePrimary(); // Consumes x, C or L.C.
|
|
Class& cls = Class::Handle(Z);
|
|
if (expr->IsPrimaryNode()) {
|
|
PrimaryNode* primary_node = expr->AsPrimaryNode();
|
|
if (primary_node->primary().IsClass()) {
|
|
// If the primary node referred to a class we are loading a
|
|
// qualified static field.
|
|
cls ^= primary_node->primary().raw();
|
|
} else {
|
|
ReportError(expr_pos,
|
|
"Metadata expressions must refer to a const field "
|
|
"or constructor");
|
|
}
|
|
}
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
// C.x or L.C.X.
|
|
if (cls.IsNull()) {
|
|
ReportError(expr_pos,
|
|
"Metadata expressions must refer to a const field "
|
|
"or constructor");
|
|
}
|
|
ConsumeToken();
|
|
const intptr_t ident_pos = TokenPos();
|
|
String* ident = ExpectIdentifier("identifier expected");
|
|
const Field& field = Field::Handle(Z, cls.LookupStaticField(*ident));
|
|
if (field.IsNull()) {
|
|
ReportError(ident_pos,
|
|
"Class '%s' has no field '%s'",
|
|
cls.ToCString(),
|
|
ident->ToCString());
|
|
}
|
|
if (!field.is_const()) {
|
|
ReportError(ident_pos,
|
|
"Field '%s' of class '%s' is not const",
|
|
ident->ToCString(),
|
|
cls.ToCString());
|
|
}
|
|
expr = GenerateStaticFieldLookup(field, ident_pos);
|
|
}
|
|
}
|
|
if (expr->EvalConstExpr() == NULL) {
|
|
ReportError(expr_pos, "expression must be a compile-time constant");
|
|
}
|
|
const Instance& val = EvaluateConstExpr(expr_pos, expr);
|
|
meta_values.Add(val);
|
|
}
|
|
return Array::MakeArray(meta_values);
|
|
}
|
|
|
|
|
|
SequenceNode* Parser::ParseStaticInitializer() {
|
|
ExpectIdentifier("field name expected");
|
|
CheckToken(Token::kASSIGN, "field initialier expected");
|
|
ConsumeToken();
|
|
OpenFunctionBlock(parsed_function()->function());
|
|
intptr_t expr_pos = TokenPos();
|
|
AstNode* expr = ParseExpr(kAllowConst, kConsumeCascades);
|
|
ReturnNode* ret = new(Z) ReturnNode(expr_pos, expr);
|
|
current_block_->statements->Add(ret);
|
|
return CloseBlock();
|
|
}
|
|
|
|
|
|
ParsedFunction* Parser::ParseStaticFieldInitializer(const Field& field) {
|
|
ASSERT(field.is_static());
|
|
ASSERT(field.value() == Object::transition_sentinel().raw());
|
|
Thread* thread = Thread::Current();
|
|
// TODO(koda): Should there be a StackZone here?
|
|
Zone* zone = thread->zone();
|
|
|
|
const Class& script_cls = Class::Handle(zone, field.origin());
|
|
const Script& script = Script::Handle(zone, script_cls.script());
|
|
|
|
const String& field_name = String::Handle(zone, field.name());
|
|
String& init_name = String::Handle(zone,
|
|
String::Concat(Symbols::InitPrefix(), field_name));
|
|
init_name = Symbols::New(init_name);
|
|
|
|
const Function& initializer = Function::ZoneHandle(zone,
|
|
Function::New(init_name,
|
|
RawFunction::kRegularFunction,
|
|
true, // static
|
|
false, // !const
|
|
false, // !abstract
|
|
false, // !external
|
|
false, // !native
|
|
Class::Handle(field.owner()),
|
|
field.token_pos()));
|
|
initializer.set_result_type(AbstractType::Handle(zone, field.type()));
|
|
// Static initializer functions are hidden from the user.
|
|
// Since they are only executed once, we avoid optimizing
|
|
// and inlining them. After the field is initialized, the
|
|
// compiler can eliminate the call to the static initializer.
|
|
initializer.set_is_reflectable(false);
|
|
initializer.set_is_debuggable(false);
|
|
initializer.SetIsOptimizable(false);
|
|
initializer.set_is_inlinable(false);
|
|
|
|
ParsedFunction* parsed_function = new ParsedFunction(thread, initializer);
|
|
Parser parser(script, parsed_function, field.token_pos());
|
|
|
|
SequenceNode* body = parser.ParseStaticInitializer();
|
|
parsed_function->SetNodeSequence(body);
|
|
parsed_function->set_default_parameter_values(Object::null_array());
|
|
|
|
if (parsed_function->has_expression_temp_var()) {
|
|
body->scope()->AddVariable(parsed_function->expression_temp_var());
|
|
}
|
|
body->scope()->AddVariable(parsed_function->current_context_var());
|
|
if (parsed_function->has_finally_return_temp_var()) {
|
|
body->scope()->AddVariable(parsed_function->finally_return_temp_var());
|
|
}
|
|
// The instantiator is not required in a static expression.
|
|
ASSERT(!parser.IsInstantiatorRequired());
|
|
|
|
return parsed_function;
|
|
}
|
|
|
|
|
|
RawObject* Parser::ParseFunctionFromSource(const Class& owning_class,
|
|
const String& source) {
|
|
Thread* thread = Thread::Current();
|
|
Isolate* isolate = thread->isolate();
|
|
StackZone stack_zone(isolate);
|
|
LongJumpScope jump;
|
|
if (setjmp(*jump.Set()) == 0) {
|
|
const String& uri = String::Handle(Symbols::New("dynamically-added"));
|
|
const Script& script = Script::Handle(
|
|
Script::New(uri, source, RawScript::kSourceTag));
|
|
const Library& owning_library = Library::Handle(owning_class.library());
|
|
const String& private_key = String::Handle(owning_library.private_key());
|
|
script.Tokenize(private_key);
|
|
const intptr_t token_pos = 0;
|
|
Parser parser(script, owning_library, token_pos);
|
|
parser.is_top_level_ = true;
|
|
parser.set_current_class(owning_class);
|
|
const String& class_name = String::Handle(owning_class.Name());
|
|
ClassDesc members(owning_class, class_name, false, token_pos);
|
|
const intptr_t metadata_pos = parser.SkipMetadata();
|
|
parser.ParseClassMemberDefinition(&members, metadata_pos);
|
|
ASSERT(members.functions().Length() == 1);
|
|
const Function& func =
|
|
Function::ZoneHandle(Function::RawCast(members.functions().At(0)));
|
|
func.set_eval_script(script);
|
|
ParsedFunction* parsed_function = new ParsedFunction(thread, func);
|
|
Parser::ParseFunction(parsed_function);
|
|
return func.raw();
|
|
} else {
|
|
const Error& error = Error::Handle(isolate->object_store()->sticky_error());
|
|
isolate->object_store()->clear_sticky_error();
|
|
return error.raw();
|
|
}
|
|
UNREACHABLE();
|
|
return Object::null();
|
|
}
|
|
|
|
|
|
SequenceNode* Parser::ParseStaticFinalGetter(const Function& func) {
|
|
TRACE_PARSER("ParseStaticFinalGetter");
|
|
ParamList params;
|
|
ASSERT(func.num_fixed_parameters() == 0); // static.
|
|
ASSERT(!func.HasOptionalParameters());
|
|
ASSERT(AbstractType::Handle(Z, func.result_type()).IsResolved());
|
|
|
|
// Build local scope for function and populate with the formal parameters.
|
|
OpenFunctionBlock(func);
|
|
AddFormalParamsToScope(¶ms, current_block_->scope);
|
|
|
|
intptr_t ident_pos = TokenPos();
|
|
const String& field_name = *ExpectIdentifier("field name expected");
|
|
const Class& field_class = Class::Handle(Z, func.Owner());
|
|
const Field& field =
|
|
Field::ZoneHandle(Z, field_class.LookupStaticField(field_name));
|
|
|
|
// Static final fields must have an initializer.
|
|
ExpectToken(Token::kASSIGN);
|
|
|
|
const intptr_t expr_pos = TokenPos();
|
|
if (field.is_const()) {
|
|
// We don't want to use ParseConstExpr() here because we don't want
|
|
// the constant folding code to create, compile and execute a code
|
|
// fragment to evaluate the expression. Instead, we just make sure
|
|
// the static const field initializer is a constant expression and
|
|
// leave the evaluation to the getter function.
|
|
AstNode* expr = ParseExpr(kAllowConst, kConsumeCascades);
|
|
// This getter will only be called once at compile time.
|
|
if (expr->EvalConstExpr() == NULL) {
|
|
ReportError(expr_pos, "initializer is not a valid compile-time constant");
|
|
}
|
|
ReturnNode* return_node = new ReturnNode(ident_pos, expr);
|
|
current_block_->statements->Add(return_node);
|
|
} else {
|
|
// This getter may be called each time the static field is accessed.
|
|
// Call runtime support to parse and evaluate the initializer expression.
|
|
// The runtime function will detect circular dependencies in expressions
|
|
// and handle errors while evaluating the expression.
|
|
current_block_->statements->Add(
|
|
new (Z) InitStaticFieldNode(ident_pos, field));
|
|
ReturnNode* return_node =
|
|
new ReturnNode(ident_pos,
|
|
new LoadStaticFieldNode(ident_pos, field));
|
|
current_block_->statements->Add(return_node);
|
|
}
|
|
return CloseBlock();
|
|
}
|
|
|
|
|
|
// Create AstNodes for an implicit instance getter method:
|
|
// LoadLocalNode 0 ('this');
|
|
// LoadInstanceFieldNode (field_name);
|
|
// ReturnNode (field's value);
|
|
SequenceNode* Parser::ParseInstanceGetter(const Function& func) {
|
|
TRACE_PARSER("ParseInstanceGetter");
|
|
ParamList params;
|
|
// func.token_pos() points to the name of the field.
|
|
const intptr_t ident_pos = func.token_pos();
|
|
ASSERT(current_class().raw() == func.Owner());
|
|
params.AddReceiver(ReceiverType(current_class()), ident_pos);
|
|
ASSERT(func.num_fixed_parameters() == 1); // receiver.
|
|
ASSERT(!func.HasOptionalParameters());
|
|
ASSERT(AbstractType::Handle(Z, func.result_type()).IsResolved());
|
|
|
|
// Build local scope for function and populate with the formal parameters.
|
|
OpenFunctionBlock(func);
|
|
AddFormalParamsToScope(¶ms, current_block_->scope);
|
|
|
|
// Receiver is local 0.
|
|
LocalVariable* receiver = current_block_->scope->VariableAt(0);
|
|
LoadLocalNode* load_receiver = new LoadLocalNode(ident_pos, receiver);
|
|
String& field_name = String::Handle(Z, func.name());
|
|
field_name = Field::NameFromGetter(field_name);
|
|
|
|
const Class& field_class = Class::Handle(Z, func.Owner());
|
|
const Field& field =
|
|
Field::ZoneHandle(Z, field_class.LookupInstanceField(field_name));
|
|
ASSERT(!field.IsNull());
|
|
|
|
LoadInstanceFieldNode* load_field =
|
|
new LoadInstanceFieldNode(ident_pos, load_receiver, field);
|
|
|
|
ReturnNode* return_node = new ReturnNode(Scanner::kNoSourcePos, load_field);
|
|
current_block_->statements->Add(return_node);
|
|
return CloseBlock();
|
|
}
|
|
|
|
|
|
// Create AstNodes for an implicit instance setter method:
|
|
// LoadLocalNode 0 ('this')
|
|
// LoadLocalNode 1 ('value')
|
|
// SetInstanceField (field_name);
|
|
// ReturnNode (void);
|
|
SequenceNode* Parser::ParseInstanceSetter(const Function& func) {
|
|
TRACE_PARSER("ParseInstanceSetter");
|
|
// func.token_pos() points to the name of the field.
|
|
const intptr_t ident_pos = func.token_pos();
|
|
const String& field_name = *CurrentLiteral();
|
|
const Class& field_class = Class::ZoneHandle(Z, func.Owner());
|
|
const Field& field =
|
|
Field::ZoneHandle(Z, field_class.LookupInstanceField(field_name));
|
|
const AbstractType& field_type = AbstractType::ZoneHandle(Z, field.type());
|
|
|
|
ParamList params;
|
|
ASSERT(current_class().raw() == func.Owner());
|
|
params.AddReceiver(ReceiverType(current_class()), ident_pos);
|
|
params.AddFinalParameter(ident_pos,
|
|
&Symbols::Value(),
|
|
&field_type);
|
|
ASSERT(func.num_fixed_parameters() == 2); // receiver, value.
|
|
ASSERT(!func.HasOptionalParameters());
|
|
ASSERT(AbstractType::Handle(Z, func.result_type()).IsVoidType());
|
|
|
|
// Build local scope for function and populate with the formal parameters.
|
|
OpenFunctionBlock(func);
|
|
AddFormalParamsToScope(¶ms, current_block_->scope);
|
|
|
|
LoadLocalNode* receiver =
|
|
new LoadLocalNode(ident_pos, current_block_->scope->VariableAt(0));
|
|
LoadLocalNode* value =
|
|
new LoadLocalNode(ident_pos, current_block_->scope->VariableAt(1));
|
|
|
|
EnsureExpressionTemp();
|
|
StoreInstanceFieldNode* store_field =
|
|
new StoreInstanceFieldNode(ident_pos, receiver, field, value);
|
|
current_block_->statements->Add(store_field);
|
|
current_block_->statements->Add(new ReturnNode(Scanner::kNoSourcePos));
|
|
return CloseBlock();
|
|
}
|
|
|
|
|
|
SequenceNode* Parser::ParseMethodExtractor(const Function& func) {
|
|
TRACE_PARSER("ParseMethodExtractor");
|
|
ParamList params;
|
|
|
|
const intptr_t ident_pos = func.token_pos();
|
|
ASSERT(func.token_pos() == 0);
|
|
ASSERT(current_class().raw() == func.Owner());
|
|
params.AddReceiver(ReceiverType(current_class()), ident_pos);
|
|
ASSERT(func.num_fixed_parameters() == 1); // Receiver.
|
|
ASSERT(!func.HasOptionalParameters());
|
|
|
|
// Build local scope for function and populate with the formal parameters.
|
|
OpenFunctionBlock(func);
|
|
AddFormalParamsToScope(¶ms, current_block_->scope);
|
|
|
|
// Receiver is local 0.
|
|
LocalVariable* receiver = current_block_->scope->VariableAt(0);
|
|
LoadLocalNode* load_receiver = new LoadLocalNode(ident_pos, receiver);
|
|
|
|
ClosureNode* closure = new ClosureNode(
|
|
ident_pos,
|
|
Function::ZoneHandle(Z, func.extracted_method_closure()),
|
|
load_receiver,
|
|
NULL);
|
|
|
|
ReturnNode* return_node = new ReturnNode(Scanner::kNoSourcePos, closure);
|
|
current_block_->statements->Add(return_node);
|
|
return CloseBlock();
|
|
}
|
|
|
|
|
|
void Parser::BuildDispatcherScope(const Function& func,
|
|
const ArgumentsDescriptor& desc,
|
|
Array* default_values) {
|
|
ParamList params;
|
|
// Receiver first.
|
|
intptr_t token_pos = func.token_pos();
|
|
params.AddReceiver(ReceiverType(current_class()), token_pos);
|
|
// Remaining positional parameters.
|
|
intptr_t i = 1;
|
|
for (; i < desc.PositionalCount(); ++i) {
|
|
ParamDesc p;
|
|
char name[64];
|
|
OS::SNPrint(name, 64, ":p%" Pd, i);
|
|
p.name = &String::ZoneHandle(Z, Symbols::New(name));
|
|
p.type = &Type::ZoneHandle(Z, Type::DynamicType());
|
|
params.parameters->Add(p);
|
|
params.num_fixed_parameters++;
|
|
}
|
|
ASSERT(desc.PositionalCount() == params.num_fixed_parameters);
|
|
|
|
// Named parameters.
|
|
for (; i < desc.Count(); ++i) {
|
|
ParamDesc p;
|
|
intptr_t index = i - desc.PositionalCount();
|
|
p.name = &String::ZoneHandle(Z, desc.NameAt(index));
|
|
p.type = &Type::ZoneHandle(Z, Type::DynamicType());
|
|
p.default_value = &Object::null_object();
|
|
params.parameters->Add(p);
|
|
params.num_optional_parameters++;
|
|
params.has_optional_named_parameters = true;
|
|
}
|
|
ASSERT(desc.NamedCount() == params.num_optional_parameters);
|
|
|
|
SetupDefaultsForOptionalParams(¶ms, default_values);
|
|
|
|
// Build local scope for function and populate with the formal parameters.
|
|
OpenFunctionBlock(func);
|
|
AddFormalParamsToScope(¶ms, current_block_->scope);
|
|
}
|
|
|
|
SequenceNode* Parser::ParseNoSuchMethodDispatcher(const Function& func,
|
|
Array* default_values) {
|
|
TRACE_PARSER("ParseNoSuchMethodDispatcher");
|
|
|
|
ASSERT(func.IsNoSuchMethodDispatcher());
|
|
intptr_t token_pos = func.token_pos();
|
|
ASSERT(func.token_pos() == 0);
|
|
ASSERT(current_class().raw() == func.Owner());
|
|
|
|
ArgumentsDescriptor desc(Array::Handle(Z, func.saved_args_desc()));
|
|
ASSERT(desc.Count() > 0);
|
|
|
|
// Set up scope for this function.
|
|
BuildDispatcherScope(func, desc, default_values);
|
|
|
|
// Receiver is local 0.
|
|
LocalScope* scope = current_block_->scope;
|
|
ArgumentListNode* func_args = new ArgumentListNode(token_pos);
|
|
for (intptr_t i = 0; i < desc.Count(); ++i) {
|
|
func_args->Add(new LoadLocalNode(token_pos, scope->VariableAt(i)));
|
|
}
|
|
|
|
if (desc.NamedCount() > 0) {
|
|
const Array& arg_names =
|
|
Array::ZoneHandle(Z, Array::New(desc.NamedCount()));
|
|
for (intptr_t i = 0; i < arg_names.Length(); ++i) {
|
|
arg_names.SetAt(i, String::Handle(Z, desc.NameAt(i)));
|
|
}
|
|
func_args->set_names(arg_names);
|
|
}
|
|
|
|
const String& func_name = String::ZoneHandle(Z, func.name());
|
|
ArgumentListNode* arguments = BuildNoSuchMethodArguments(
|
|
token_pos, func_name, *func_args, NULL, false);
|
|
const intptr_t kNumArguments = 2; // Receiver, InvocationMirror.
|
|
ArgumentsDescriptor args_desc(
|
|
Array::Handle(Z, ArgumentsDescriptor::New(kNumArguments)));
|
|
Function& no_such_method = Function::ZoneHandle(Z,
|
|
Resolver::ResolveDynamicForReceiverClass(Class::Handle(Z, func.Owner()),
|
|
Symbols::NoSuchMethod(),
|
|
args_desc));
|
|
if (no_such_method.IsNull()) {
|
|
// If noSuchMethod(i) is not found, call Object:noSuchMethod.
|
|
no_such_method ^= Resolver::ResolveDynamicForReceiverClass(
|
|
Class::Handle(Z, I->object_store()->object_class()),
|
|
Symbols::NoSuchMethod(),
|
|
args_desc);
|
|
}
|
|
StaticCallNode* call =
|
|
new StaticCallNode(token_pos, no_such_method, arguments);
|
|
|
|
ReturnNode* return_node = new ReturnNode(token_pos, call);
|
|
current_block_->statements->Add(return_node);
|
|
return CloseBlock();
|
|
}
|
|
|
|
|
|
SequenceNode* Parser::ParseInvokeFieldDispatcher(const Function& func,
|
|
Array* default_values) {
|
|
TRACE_PARSER("ParseInvokeFieldDispatcher");
|
|
|
|
ASSERT(func.IsInvokeFieldDispatcher());
|
|
intptr_t token_pos = func.token_pos();
|
|
ASSERT(func.token_pos() == 0);
|
|
ASSERT(current_class().raw() == func.Owner());
|
|
|
|
const Array& args_desc = Array::Handle(Z, func.saved_args_desc());
|
|
ArgumentsDescriptor desc(args_desc);
|
|
ASSERT(desc.Count() > 0);
|
|
|
|
// Set up scope for this function.
|
|
BuildDispatcherScope(func, desc, default_values);
|
|
|
|
// Receiver is local 0.
|
|
LocalScope* scope = current_block_->scope;
|
|
ArgumentListNode* no_args = new ArgumentListNode(token_pos);
|
|
LoadLocalNode* receiver = new LoadLocalNode(token_pos, scope->VariableAt(0));
|
|
|
|
const Class& function_impl = Class::Handle(Type::Handle(
|
|
Isolate::Current()->object_store()->function_impl_type()).type_class());
|
|
|
|
const Class& owner = Class::Handle(Z, func.Owner());
|
|
ASSERT(!owner.IsNull());
|
|
const String& name = String::Handle(Z, func.name());
|
|
AstNode* function_object = NULL;
|
|
if (owner.raw() == function_impl.raw() && name.Equals(Symbols::Call())) {
|
|
function_object = receiver;
|
|
} else {
|
|
const String& getter_name = String::ZoneHandle(Z,
|
|
Symbols::New(String::Handle(Z, Field::GetterName(name))));
|
|
function_object = new(Z) InstanceCallNode(
|
|
token_pos, receiver, getter_name, no_args);
|
|
}
|
|
|
|
// Pass arguments 1..n to the closure call.
|
|
ArgumentListNode* args = new(Z) ArgumentListNode(token_pos);
|
|
const Array& names = Array::Handle(
|
|
Z, Array::New(desc.NamedCount(), Heap::kOld));
|
|
// Positional parameters.
|
|
intptr_t i = 1;
|
|
for (; i < desc.PositionalCount(); ++i) {
|
|
args->Add(new LoadLocalNode(token_pos, scope->VariableAt(i)));
|
|
}
|
|
// Named parameters.
|
|
for (; i < desc.Count(); i++) {
|
|
args->Add(new(Z) LoadLocalNode(token_pos, scope->VariableAt(i)));
|
|
intptr_t index = i - desc.PositionalCount();
|
|
names.SetAt(index, String::Handle(Z, desc.NameAt(index)));
|
|
}
|
|
args->set_names(names);
|
|
|
|
AstNode* result = NULL;
|
|
if (owner.raw() == function_impl.raw() && name.Equals(Symbols::Call())) {
|
|
result = new ClosureCallNode(token_pos, function_object, args);
|
|
} else {
|
|
result = BuildClosureCall(token_pos, function_object, args);
|
|
}
|
|
|
|
ReturnNode* return_node = new ReturnNode(token_pos, result);
|
|
current_block_->statements->Add(return_node);
|
|
return CloseBlock();
|
|
}
|
|
|
|
|
|
AstNode* Parser::BuildClosureCall(intptr_t token_pos,
|
|
AstNode* closure,
|
|
ArgumentListNode* arguments) {
|
|
return new InstanceCallNode(token_pos,
|
|
closure,
|
|
Symbols::Call(),
|
|
arguments);
|
|
}
|
|
|
|
|
|
void Parser::SkipBlock() {
|
|
ASSERT(CurrentToken() == Token::kLBRACE);
|
|
GrowableArray<Token::Kind> token_stack(8);
|
|
// Adding the first kLBRACE here, because it will be consumed in the loop
|
|
// right away.
|
|
token_stack.Add(CurrentToken());
|
|
const intptr_t block_start_pos = TokenPos();
|
|
bool is_match = true;
|
|
bool unexpected_token_found = false;
|
|
Token::Kind token;
|
|
intptr_t token_pos;
|
|
do {
|
|
ConsumeToken();
|
|
token = CurrentToken();
|
|
token_pos = TokenPos();
|
|
switch (token) {
|
|
case Token::kLBRACE:
|
|
case Token::kLPAREN:
|
|
case Token::kLBRACK:
|
|
token_stack.Add(token);
|
|
break;
|
|
case Token::kRBRACE:
|
|
is_match = token_stack.RemoveLast() == Token::kLBRACE;
|
|
break;
|
|
case Token::kRPAREN:
|
|
is_match = token_stack.RemoveLast() == Token::kLPAREN;
|
|
break;
|
|
case Token::kRBRACK:
|
|
is_match = token_stack.RemoveLast() == Token::kLBRACK;
|
|
break;
|
|
case Token::kEOS:
|
|
unexpected_token_found = true;
|
|
break;
|
|
default:
|
|
// nothing.
|
|
break;
|
|
}
|
|
} while (!token_stack.is_empty() && is_match && !unexpected_token_found);
|
|
if (!is_match) {
|
|
ReportError(token_pos, "unbalanced '%s'", Token::Str(token));
|
|
} else if (unexpected_token_found) {
|
|
ReportError(block_start_pos, "unterminated block");
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::ParseFormalParameter(bool allow_explicit_default_value,
|
|
bool evaluate_metadata,
|
|
ParamList* params) {
|
|
TRACE_PARSER("ParseFormalParameter");
|
|
ParamDesc parameter;
|
|
bool var_seen = false;
|
|
bool this_seen = false;
|
|
|
|
if (evaluate_metadata && (CurrentToken() == Token::kAT)) {
|
|
parameter.metadata = &Array::ZoneHandle(Z, EvaluateMetadata());
|
|
} else {
|
|
SkipMetadata();
|
|
}
|
|
|
|
if (CurrentToken() == Token::kFINAL) {
|
|
ConsumeToken();
|
|
parameter.is_final = true;
|
|
} else if (CurrentToken() == Token::kVAR) {
|
|
ConsumeToken();
|
|
var_seen = true;
|
|
// The parameter type is the 'dynamic' type.
|
|
// If this is an initializing formal, its type will be set to the type of
|
|
// the respective field when the constructor is fully parsed.
|
|
parameter.type = &Type::ZoneHandle(Z, Type::DynamicType());
|
|
}
|
|
if (CurrentToken() == Token::kTHIS) {
|
|
ConsumeToken();
|
|
ExpectToken(Token::kPERIOD);
|
|
this_seen = true;
|
|
parameter.is_field_initializer = true;
|
|
}
|
|
if ((parameter.type == NULL) && (CurrentToken() == Token::kVOID)) {
|
|
ConsumeToken();
|
|
// This must later be changed to a closure type if we recognize
|
|
// a closure/function type parameter. We check this at the end
|
|
// of ParseFormalParameter.
|
|
parameter.type = &Type::ZoneHandle(Z, Type::VoidType());
|
|
}
|
|
if (parameter.type == NULL) {
|
|
// At this point, we must see an identifier for the type or the
|
|
// function parameter.
|
|
if (!IsIdentifier()) {
|
|
ReportError("parameter name or type expected");
|
|
}
|
|
// We have not seen a parameter type yet, so we check if the next
|
|
// identifier could represent a type before parsing it.
|
|
Token::Kind follower = LookaheadToken(1);
|
|
// We have an identifier followed by a 'follower' token.
|
|
// We either parse a type or assume that no type is specified.
|
|
if ((follower == Token::kLT) || // Parameterized type.
|
|
(follower == Token::kPERIOD) || // Qualified class name of type.
|
|
Token::IsIdentifier(follower) || // Parameter name following a type.
|
|
(follower == Token::kTHIS)) { // Field parameter following a type.
|
|
// The types of formal parameters are never ignored, even in unchecked
|
|
// mode, because they are part of the function type of closurized
|
|
// functions appearing in type tests with typedefs.
|
|
parameter.has_explicit_type = true;
|
|
parameter.type = &AbstractType::ZoneHandle(Z,
|
|
ParseType(is_top_level_ ? ClassFinalizer::kResolveTypeParameters :
|
|
ClassFinalizer::kCanonicalize));
|
|
} else {
|
|
// If this is an initializing formal, its type will be set to the type of
|
|
// the respective field when the constructor is fully parsed.
|
|
parameter.type = &Type::ZoneHandle(Z, Type::DynamicType());
|
|
}
|
|
}
|
|
if (!this_seen && (CurrentToken() == Token::kTHIS)) {
|
|
ConsumeToken();
|
|
ExpectToken(Token::kPERIOD);
|
|
this_seen = true;
|
|
parameter.is_field_initializer = true;
|
|
}
|
|
|
|
// At this point, we must see an identifier for the parameter name.
|
|
parameter.name_pos = TokenPos();
|
|
parameter.name = ExpectIdentifier("parameter name expected");
|
|
if (parameter.is_field_initializer) {
|
|
params->has_field_initializer = true;
|
|
}
|
|
|
|
if (params->has_optional_named_parameters &&
|
|
(parameter.name->CharAt(0) == Library::kPrivateIdentifierStart)) {
|
|
ReportError(parameter.name_pos, "named parameter must not be private");
|
|
}
|
|
|
|
// Check for duplicate formal parameters.
|
|
const intptr_t num_existing_parameters =
|
|
params->num_fixed_parameters + params->num_optional_parameters;
|
|
for (intptr_t i = 0; i < num_existing_parameters; i++) {
|
|
ParamDesc& existing_parameter = (*params->parameters)[i];
|
|
if (existing_parameter.name->Equals(*parameter.name)) {
|
|
ReportError(parameter.name_pos, "duplicate formal parameter '%s'",
|
|
parameter.name->ToCString());
|
|
}
|
|
}
|
|
|
|
if (CurrentToken() == Token::kLPAREN) {
|
|
// This parameter is probably a closure. If we saw the keyword 'var'
|
|
// or 'final', a closure is not legal here and we ignore the
|
|
// opening parens.
|
|
if (!var_seen && !parameter.is_final) {
|
|
// The parsed parameter type is actually the function result type.
|
|
const AbstractType& result_type =
|
|
AbstractType::Handle(Z, parameter.type->raw());
|
|
|
|
// Finish parsing the function type parameter.
|
|
ParamList func_params;
|
|
|
|
// Add implicit closure object parameter.
|
|
func_params.AddFinalParameter(
|
|
TokenPos(),
|
|
&Symbols::ClosureParameter(),
|
|
&Type::ZoneHandle(Z, Type::DynamicType()));
|
|
|
|
const bool no_explicit_default_values = false;
|
|
ParseFormalParameterList(no_explicit_default_values, false, &func_params);
|
|
|
|
// The field 'is_static' has no meaning for signature functions.
|
|
const Function& signature_function = Function::Handle(Z,
|
|
Function::New(*parameter.name,
|
|
RawFunction::kSignatureFunction,
|
|
/* is_static = */ false,
|
|
/* is_const = */ false,
|
|
/* is_abstract = */ false,
|
|
/* is_external = */ false,
|
|
/* is_native = */ false,
|
|
current_class(),
|
|
parameter.name_pos));
|
|
signature_function.set_result_type(result_type);
|
|
signature_function.set_is_debuggable(false);
|
|
AddFormalParamsToFunction(&func_params, signature_function);
|
|
const String& signature = String::Handle(Z,
|
|
signature_function.Signature());
|
|
// Lookup the signature class, i.e. the class whose name is the signature.
|
|
// We only lookup in the current library, but not in its imports, and only
|
|
// create a new canonical signature class if it does not exist yet.
|
|
Class& signature_class = Class::ZoneHandle(Z,
|
|
library_.LookupLocalClass(signature));
|
|
if (signature_class.IsNull()) {
|
|
signature_class = Class::NewSignatureClass(signature,
|
|
signature_function,
|
|
script_,
|
|
parameter.name_pos);
|
|
// Record the function signature class in the current library, unless
|
|
// we are currently skipping a formal parameter list, in which case
|
|
// the signature class could remain unfinalized.
|
|
if (!params->skipped) {
|
|
library_.AddClass(signature_class);
|
|
}
|
|
} else {
|
|
signature_function.set_signature_class(signature_class);
|
|
}
|
|
ASSERT(signature_function.signature_class() == signature_class.raw());
|
|
Type& signature_type =
|
|
Type::ZoneHandle(Z, signature_class.SignatureType());
|
|
if (!is_top_level_ && !signature_type.IsFinalized()) {
|
|
signature_type ^= ClassFinalizer::FinalizeType(
|
|
signature_class, signature_type, ClassFinalizer::kCanonicalize);
|
|
}
|
|
// A signature type itself cannot be malformed or malbounded, only its
|
|
// signature function's result type or parameter types may be.
|
|
ASSERT(!signature_type.IsMalformed());
|
|
ASSERT(!signature_type.IsMalbounded());
|
|
// The type of the parameter is now the signature type.
|
|
parameter.type = &signature_type;
|
|
}
|
|
}
|
|
|
|
if ((CurrentToken() == Token::kASSIGN) || (CurrentToken() == Token::kCOLON)) {
|
|
if ((!params->has_optional_positional_parameters &&
|
|
!params->has_optional_named_parameters) ||
|
|
!allow_explicit_default_value) {
|
|
ReportError("parameter must not specify a default value");
|
|
}
|
|
if (params->has_optional_positional_parameters) {
|
|
ExpectToken(Token::kASSIGN);
|
|
} else {
|
|
ExpectToken(Token::kCOLON);
|
|
}
|
|
params->num_optional_parameters++;
|
|
params->has_explicit_default_values = true; // Also if explicitly NULL.
|
|
if (is_top_level_) {
|
|
// Skip default value parsing.
|
|
SkipExpr();
|
|
} else {
|
|
const Object& const_value = ParseConstExpr()->literal();
|
|
parameter.default_value = &const_value;
|
|
}
|
|
} else {
|
|
if (params->has_optional_positional_parameters ||
|
|
params->has_optional_named_parameters) {
|
|
// Implicit default value is null.
|
|
params->num_optional_parameters++;
|
|
parameter.default_value = &Object::null_object();
|
|
} else {
|
|
params->num_fixed_parameters++;
|
|
ASSERT(params->num_optional_parameters == 0);
|
|
}
|
|
}
|
|
if (parameter.type->IsVoidType()) {
|
|
ReportError("parameter '%s' may not be 'void'",
|
|
parameter.name->ToCString());
|
|
}
|
|
if (params->implicitly_final) {
|
|
parameter.is_final = true;
|
|
}
|
|
params->parameters->Add(parameter);
|
|
}
|
|
|
|
|
|
// Parses a sequence of normal or optional formal parameters.
|
|
void Parser::ParseFormalParameters(bool allow_explicit_default_values,
|
|
bool evaluate_metadata,
|
|
ParamList* params) {
|
|
TRACE_PARSER("ParseFormalParameters");
|
|
do {
|
|
ConsumeToken();
|
|
if (!params->has_optional_positional_parameters &&
|
|
!params->has_optional_named_parameters &&
|
|
(CurrentToken() == Token::kLBRACK)) {
|
|
// End of normal parameters, start of optional positional parameters.
|
|
params->has_optional_positional_parameters = true;
|
|
return;
|
|
}
|
|
if (!params->has_optional_positional_parameters &&
|
|
!params->has_optional_named_parameters &&
|
|
(CurrentToken() == Token::kLBRACE)) {
|
|
// End of normal parameters, start of optional named parameters.
|
|
params->has_optional_named_parameters = true;
|
|
return;
|
|
}
|
|
ParseFormalParameter(allow_explicit_default_values,
|
|
evaluate_metadata,
|
|
params);
|
|
} while (CurrentToken() == Token::kCOMMA);
|
|
}
|
|
|
|
|
|
void Parser::ParseFormalParameterList(bool allow_explicit_default_values,
|
|
bool evaluate_metadata,
|
|
ParamList* params) {
|
|
TRACE_PARSER("ParseFormalParameterList");
|
|
ASSERT(CurrentToken() == Token::kLPAREN);
|
|
|
|
if (LookaheadToken(1) != Token::kRPAREN) {
|
|
// Parse fixed parameters.
|
|
ParseFormalParameters(allow_explicit_default_values,
|
|
evaluate_metadata,
|
|
params);
|
|
if (params->has_optional_positional_parameters ||
|
|
params->has_optional_named_parameters) {
|
|
// Parse optional parameters.
|
|
ParseFormalParameters(allow_explicit_default_values,
|
|
evaluate_metadata,
|
|
params);
|
|
if (params->has_optional_positional_parameters) {
|
|
CheckToken(Token::kRBRACK, "',' or ']' expected");
|
|
} else {
|
|
CheckToken(Token::kRBRACE, "',' or '}' expected");
|
|
}
|
|
ConsumeToken(); // ']' or '}'.
|
|
}
|
|
if ((CurrentToken() != Token::kRPAREN) &&
|
|
!params->has_optional_positional_parameters &&
|
|
!params->has_optional_named_parameters) {
|
|
ReportError("',' or ')' expected");
|
|
}
|
|
} else {
|
|
ConsumeToken();
|
|
}
|
|
ExpectToken(Token::kRPAREN);
|
|
}
|
|
|
|
|
|
String& Parser::ParseNativeDeclaration() {
|
|
TRACE_PARSER("ParseNativeDeclaration");
|
|
ASSERT(IsLiteral("native"));
|
|
ConsumeToken();
|
|
CheckToken(Token::kSTRING, "string literal expected");
|
|
String& native_name = *CurrentLiteral();
|
|
ConsumeToken();
|
|
return native_name;
|
|
}
|
|
|
|
|
|
// Resolve and return the dynamic function of the given name in the superclass.
|
|
// If it is not found, and resolve_getter is true, try to resolve a getter of
|
|
// the same name. If it is still not found, return noSuchMethod and
|
|
// set is_no_such_method to true..
|
|
RawFunction* Parser::GetSuperFunction(intptr_t token_pos,
|
|
const String& name,
|
|
ArgumentListNode* arguments,
|
|
bool resolve_getter,
|
|
bool* is_no_such_method) {
|
|
const Class& super_class = Class::Handle(Z, current_class().SuperClass());
|
|
if (super_class.IsNull()) {
|
|
ReportError(token_pos, "class '%s' does not have a superclass",
|
|
String::Handle(Z, current_class().Name()).ToCString());
|
|
}
|
|
Function& super_func = Function::Handle(Z,
|
|
Resolver::ResolveDynamicAnyArgs(super_class, name));
|
|
if (!super_func.IsNull() &&
|
|
!super_func.AreValidArguments(arguments->length(),
|
|
arguments->names(),
|
|
NULL)) {
|
|
super_func = Function::null();
|
|
} else if (super_func.IsNull() && resolve_getter) {
|
|
const String& getter_name = String::ZoneHandle(Z, Field::GetterName(name));
|
|
super_func = Resolver::ResolveDynamicAnyArgs(super_class, getter_name);
|
|
ASSERT(super_func.IsNull() ||
|
|
(super_func.kind() != RawFunction::kImplicitStaticFinalGetter));
|
|
}
|
|
if (super_func.IsNull()) {
|
|
super_func =
|
|
Resolver::ResolveDynamicAnyArgs(super_class, Symbols::NoSuchMethod());
|
|
ASSERT(!super_func.IsNull());
|
|
*is_no_such_method = true;
|
|
} else {
|
|
*is_no_such_method = false;
|
|
}
|
|
return super_func.raw();
|
|
}
|
|
|
|
|
|
StaticCallNode* Parser::BuildInvocationMirrorAllocation(
|
|
intptr_t call_pos,
|
|
const String& function_name,
|
|
const ArgumentListNode& function_args,
|
|
const LocalVariable* temp_for_last_arg,
|
|
bool is_super_invocation) {
|
|
const intptr_t args_pos = function_args.token_pos();
|
|
// Build arguments to the call to the static
|
|
// InvocationMirror._allocateInvocationMirror method.
|
|
ArgumentListNode* arguments = new ArgumentListNode(args_pos);
|
|
// The first argument is the original function name.
|
|
arguments->Add(new LiteralNode(args_pos, function_name));
|
|
// The second argument is the arguments descriptor of the original function.
|
|
const Array& args_descriptor =
|
|
Array::ZoneHandle(ArgumentsDescriptor::New(function_args.length(),
|
|
function_args.names()));
|
|
arguments->Add(new LiteralNode(args_pos, args_descriptor));
|
|
// The third argument is an array containing the original function arguments,
|
|
// including the receiver.
|
|
ArrayNode* args_array =
|
|
new ArrayNode(args_pos, Type::ZoneHandle(Type::ArrayType()));
|
|
for (intptr_t i = 0; i < function_args.length(); i++) {
|
|
AstNode* arg = function_args.NodeAt(i);
|
|
if ((temp_for_last_arg != NULL) && (i == function_args.length() - 1)) {
|
|
LetNode* store_arg = new LetNode(arg->token_pos());
|
|
store_arg->AddNode(new StoreLocalNode(arg->token_pos(),
|
|
temp_for_last_arg,
|
|
arg));
|
|
store_arg->AddNode(new LoadLocalNode(arg->token_pos(),
|
|
temp_for_last_arg));
|
|
args_array->AddElement(store_arg);
|
|
} else {
|
|
args_array->AddElement(arg);
|
|
}
|
|
}
|
|
arguments->Add(args_array);
|
|
arguments->Add(new LiteralNode(args_pos, Bool::Get(is_super_invocation)));
|
|
// Lookup the static InvocationMirror._allocateInvocationMirror method.
|
|
const Class& mirror_class =
|
|
Class::Handle(Library::LookupCoreClass(Symbols::InvocationMirror()));
|
|
ASSERT(!mirror_class.IsNull());
|
|
const Function& allocation_function = Function::ZoneHandle(
|
|
mirror_class.LookupStaticFunction(
|
|
Library::PrivateCoreLibName(Symbols::AllocateInvocationMirror())));
|
|
ASSERT(!allocation_function.IsNull());
|
|
return new StaticCallNode(call_pos, allocation_function, arguments);
|
|
}
|
|
|
|
|
|
ArgumentListNode* Parser::BuildNoSuchMethodArguments(
|
|
intptr_t call_pos,
|
|
const String& function_name,
|
|
const ArgumentListNode& function_args,
|
|
const LocalVariable* temp_for_last_arg,
|
|
bool is_super_invocation) {
|
|
ASSERT(function_args.length() >= 1); // The receiver is the first argument.
|
|
const intptr_t args_pos = function_args.token_pos();
|
|
ArgumentListNode* arguments = new ArgumentListNode(args_pos);
|
|
arguments->Add(function_args.NodeAt(0));
|
|
// The second argument is the invocation mirror.
|
|
arguments->Add(BuildInvocationMirrorAllocation(call_pos,
|
|
function_name,
|
|
function_args,
|
|
temp_for_last_arg,
|
|
is_super_invocation));
|
|
return arguments;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseSuperCall(const String& function_name) {
|
|
TRACE_PARSER("ParseSuperCall");
|
|
ASSERT(CurrentToken() == Token::kLPAREN);
|
|
const intptr_t supercall_pos = TokenPos();
|
|
|
|
// 'this' parameter is the first argument to super call.
|
|
ArgumentListNode* arguments = new ArgumentListNode(supercall_pos);
|
|
AstNode* receiver = LoadReceiver(supercall_pos);
|
|
arguments->Add(receiver);
|
|
ParseActualParameters(arguments, kAllowConst);
|
|
|
|
const bool kResolveGetter = true;
|
|
bool is_no_such_method = false;
|
|
const Function& super_function = Function::ZoneHandle(Z,
|
|
GetSuperFunction(supercall_pos,
|
|
function_name,
|
|
arguments,
|
|
kResolveGetter,
|
|
&is_no_such_method));
|
|
if (super_function.IsGetterFunction() ||
|
|
super_function.IsImplicitGetterFunction()) {
|
|
const Class& super_class =
|
|
Class::ZoneHandle(Z, current_class().SuperClass());
|
|
AstNode* closure = new StaticGetterNode(supercall_pos,
|
|
LoadReceiver(supercall_pos),
|
|
/* is_super_getter */ true,
|
|
super_class,
|
|
function_name);
|
|
// 'this' is not passed as parameter to the closure.
|
|
ArgumentListNode* closure_arguments = new ArgumentListNode(supercall_pos);
|
|
for (int i = 1; i < arguments->length(); i++) {
|
|
closure_arguments->Add(arguments->NodeAt(i));
|
|
}
|
|
return BuildClosureCall(supercall_pos, closure, closure_arguments);
|
|
}
|
|
if (is_no_such_method) {
|
|
arguments = BuildNoSuchMethodArguments(
|
|
supercall_pos, function_name, *arguments, NULL, true);
|
|
}
|
|
return new StaticCallNode(supercall_pos, super_function, arguments);
|
|
}
|
|
|
|
|
|
// Simple test if a node is side effect free.
|
|
static bool IsSimpleLocalOrLiteralNode(AstNode* node) {
|
|
return node->IsLiteralNode() || node->IsLoadLocalNode();
|
|
}
|
|
|
|
|
|
AstNode* Parser::BuildUnarySuperOperator(Token::Kind op, PrimaryNode* super) {
|
|
ASSERT(super->IsSuper());
|
|
AstNode* super_op = NULL;
|
|
const intptr_t super_pos = super->token_pos();
|
|
if ((op == Token::kNEGATE) ||
|
|
(op == Token::kBIT_NOT)) {
|
|
// Resolve the operator function in the superclass.
|
|
const String& operator_function_name =
|
|
String::ZoneHandle(Z, Symbols::New(Token::Str(op)));
|
|
ArgumentListNode* op_arguments = new ArgumentListNode(super_pos);
|
|
AstNode* receiver = LoadReceiver(super_pos);
|
|
op_arguments->Add(receiver);
|
|
const bool kResolveGetter = false;
|
|
bool is_no_such_method = false;
|
|
const Function& super_operator = Function::ZoneHandle(Z,
|
|
GetSuperFunction(super_pos,
|
|
operator_function_name,
|
|
op_arguments,
|
|
kResolveGetter,
|
|
&is_no_such_method));
|
|
if (is_no_such_method) {
|
|
op_arguments = BuildNoSuchMethodArguments(
|
|
super_pos, operator_function_name, *op_arguments, NULL, true);
|
|
}
|
|
super_op = new StaticCallNode(super_pos, super_operator, op_arguments);
|
|
} else {
|
|
ReportError(super_pos, "illegal super operator call");
|
|
}
|
|
return super_op;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseSuperOperator() {
|
|
TRACE_PARSER("ParseSuperOperator");
|
|
AstNode* super_op = NULL;
|
|
const intptr_t operator_pos = TokenPos();
|
|
|
|
if (CurrentToken() == Token::kLBRACK) {
|
|
ConsumeToken();
|
|
AstNode* index_expr = ParseExpr(kAllowConst, kConsumeCascades);
|
|
ExpectToken(Token::kRBRACK);
|
|
AstNode* receiver = LoadReceiver(operator_pos);
|
|
const Class& super_class =
|
|
Class::ZoneHandle(Z, current_class().SuperClass());
|
|
ASSERT(!super_class.IsNull());
|
|
super_op =
|
|
new LoadIndexedNode(operator_pos, receiver, index_expr, super_class);
|
|
} else {
|
|
ASSERT(Token::CanBeOverloaded(CurrentToken()) ||
|
|
(CurrentToken() == Token::kNE));
|
|
Token::Kind op = CurrentToken();
|
|
ConsumeToken();
|
|
|
|
bool negate_result = false;
|
|
if (op == Token::kNE) {
|
|
op = Token::kEQ;
|
|
negate_result = true;
|
|
}
|
|
|
|
ASSERT(Token::Precedence(op) >= Token::Precedence(Token::kEQ));
|
|
AstNode* other_operand = ParseBinaryExpr(Token::Precedence(op) + 1);
|
|
|
|
ArgumentListNode* op_arguments = new ArgumentListNode(operator_pos);
|
|
AstNode* receiver = LoadReceiver(operator_pos);
|
|
op_arguments->Add(receiver);
|
|
op_arguments->Add(other_operand);
|
|
|
|
// Resolve the operator function in the superclass.
|
|
const String& operator_function_name =
|
|
String::ZoneHandle(Z, Symbols::New(Token::Str(op)));
|
|
const bool kResolveGetter = false;
|
|
bool is_no_such_method = false;
|
|
const Function& super_operator = Function::ZoneHandle(Z,
|
|
GetSuperFunction(operator_pos,
|
|
operator_function_name,
|
|
op_arguments,
|
|
kResolveGetter,
|
|
&is_no_such_method));
|
|
if (is_no_such_method) {
|
|
op_arguments = BuildNoSuchMethodArguments(
|
|
operator_pos, operator_function_name, *op_arguments, NULL, true);
|
|
}
|
|
super_op = new StaticCallNode(operator_pos, super_operator, op_arguments);
|
|
if (negate_result) {
|
|
super_op = new UnaryOpNode(operator_pos, Token::kNOT, super_op);
|
|
}
|
|
}
|
|
return super_op;
|
|
}
|
|
|
|
|
|
ClosureNode* Parser::CreateImplicitClosureNode(const Function& func,
|
|
intptr_t token_pos,
|
|
AstNode* receiver) {
|
|
Function& implicit_closure_function =
|
|
Function::ZoneHandle(Z, func.ImplicitClosureFunction());
|
|
if (receiver != NULL) {
|
|
// If we create an implicit instance closure from inside a closure of a
|
|
// parameterized class, make sure that the receiver is captured as
|
|
// instantiator.
|
|
if (current_block_->scope->function_level() > 0) {
|
|
const Class& signature_class = Class::Handle(Z,
|
|
implicit_closure_function.signature_class());
|
|
if (signature_class.NumTypeParameters() > 0) {
|
|
CaptureInstantiator();
|
|
}
|
|
}
|
|
}
|
|
return new ClosureNode(token_pos, implicit_closure_function, receiver, NULL);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseSuperFieldAccess(const String& field_name,
|
|
intptr_t field_pos) {
|
|
TRACE_PARSER("ParseSuperFieldAccess");
|
|
const Class& super_class = Class::ZoneHandle(Z, current_class().SuperClass());
|
|
if (super_class.IsNull()) {
|
|
ReportError("class '%s' does not have a superclass",
|
|
String::Handle(Z, current_class().Name()).ToCString());
|
|
}
|
|
AstNode* implicit_argument = LoadReceiver(field_pos);
|
|
|
|
const String& getter_name =
|
|
String::ZoneHandle(Z, Field::GetterName(field_name));
|
|
const Function& super_getter = Function::ZoneHandle(Z,
|
|
Resolver::ResolveDynamicAnyArgs(super_class, getter_name));
|
|
if (super_getter.IsNull()) {
|
|
const String& setter_name =
|
|
String::ZoneHandle(Z, Field::SetterName(field_name));
|
|
const Function& super_setter = Function::ZoneHandle(Z,
|
|
Resolver::ResolveDynamicAnyArgs(super_class, setter_name));
|
|
if (super_setter.IsNull()) {
|
|
// Check if this is an access to an implicit closure using 'super'.
|
|
// If a function exists of the specified field_name then try
|
|
// accessing it as a getter, at runtime we will handle this by
|
|
// creating an implicit closure of the function and returning it.
|
|
const Function& super_function = Function::ZoneHandle(Z,
|
|
Resolver::ResolveDynamicAnyArgs(super_class, field_name));
|
|
if (!super_function.IsNull()) {
|
|
// In case CreateAssignmentNode is called later on this
|
|
// CreateImplicitClosureNode, it will be replaced by a StaticSetterNode.
|
|
return CreateImplicitClosureNode(super_function,
|
|
field_pos,
|
|
implicit_argument);
|
|
}
|
|
// No function or field exists of the specified field_name.
|
|
// Emit a StaticGetterNode anyway, so that noSuchMethod gets called.
|
|
}
|
|
}
|
|
return new StaticGetterNode(
|
|
field_pos, implicit_argument, true, super_class, field_name);
|
|
}
|
|
|
|
|
|
void Parser::GenerateSuperConstructorCall(const Class& cls,
|
|
intptr_t supercall_pos,
|
|
LocalVariable* receiver,
|
|
ArgumentListNode* forwarding_args) {
|
|
const Class& super_class = Class::Handle(Z, cls.SuperClass());
|
|
// Omit the implicit super() if there is no super class (i.e.
|
|
// we're not compiling class Object), or if the super class is an
|
|
// artificially generated "wrapper class" that has no constructor.
|
|
if (super_class.IsNull() ||
|
|
(super_class.num_native_fields() > 0 &&
|
|
Class::Handle(Z, super_class.SuperClass()).IsObjectClass())) {
|
|
return;
|
|
}
|
|
String& super_ctor_name = String::Handle(Z, super_class.Name());
|
|
super_ctor_name = String::Concat(super_ctor_name, Symbols::Dot());
|
|
|
|
ArgumentListNode* arguments = new ArgumentListNode(supercall_pos);
|
|
// Implicit 'this' parameter is the first argument.
|
|
AstNode* implicit_argument = new LoadLocalNode(supercall_pos, receiver);
|
|
arguments->Add(implicit_argument);
|
|
// Implicit construction phase parameter is second argument.
|
|
AstNode* phase_parameter =
|
|
new LiteralNode(supercall_pos,
|
|
Smi::ZoneHandle(Z, Smi::New(Function::kCtorPhaseAll)));
|
|
arguments->Add(phase_parameter);
|
|
|
|
// If this is a super call in a forwarding constructor, add the user-
|
|
// defined arguments to the super call and adjust the the super
|
|
// constructor name to the respective named constructor if necessary.
|
|
if (forwarding_args != NULL) {
|
|
for (int i = 0; i < forwarding_args->length(); i++) {
|
|
arguments->Add(forwarding_args->NodeAt(i));
|
|
}
|
|
String& ctor_name = String::Handle(Z, current_function().name());
|
|
String& class_name = String::Handle(Z, cls.Name());
|
|
if (ctor_name.Length() > class_name.Length() + 1) {
|
|
// Generating a forwarding call to a named constructor 'C.n'.
|
|
// Add the constructor name 'n' to the super constructor.
|
|
ctor_name = String::SubString(ctor_name, class_name.Length() + 1);
|
|
super_ctor_name = String::Concat(super_ctor_name, ctor_name);
|
|
}
|
|
}
|
|
|
|
// Resolve super constructor function and check arguments.
|
|
const Function& super_ctor = Function::ZoneHandle(Z,
|
|
super_class.LookupConstructor(super_ctor_name));
|
|
if (super_ctor.IsNull()) {
|
|
ReportError(supercall_pos,
|
|
"unresolved implicit call to super constructor '%s()'",
|
|
String::Handle(Z, super_class.Name()).ToCString());
|
|
}
|
|
if (current_function().is_const() && !super_ctor.is_const()) {
|
|
ReportError(supercall_pos, "implicit call to non-const super constructor");
|
|
}
|
|
|
|
String& error_message = String::Handle(Z);
|
|
if (!super_ctor.AreValidArguments(arguments->length(),
|
|
arguments->names(),
|
|
&error_message)) {
|
|
ReportError(supercall_pos,
|
|
"invalid arguments passed to super constructor '%s()': %s",
|
|
String::Handle(Z, super_class.Name()).ToCString(),
|
|
error_message.ToCString());
|
|
}
|
|
current_block_->statements->Add(
|
|
new StaticCallNode(supercall_pos, super_ctor, arguments));
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseSuperInitializer(const Class& cls,
|
|
LocalVariable* receiver) {
|
|
TRACE_PARSER("ParseSuperInitializer");
|
|
ASSERT(CurrentToken() == Token::kSUPER);
|
|
const intptr_t supercall_pos = TokenPos();
|
|
ConsumeToken();
|
|
const Class& super_class = Class::Handle(Z, cls.SuperClass());
|
|
ASSERT(!super_class.IsNull());
|
|
String& ctor_name = String::Handle(Z, super_class.Name());
|
|
ctor_name = String::Concat(ctor_name, Symbols::Dot());
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
ctor_name = String::Concat(ctor_name,
|
|
*ExpectIdentifier("constructor name expected"));
|
|
}
|
|
CheckToken(Token::kLPAREN, "parameter list expected");
|
|
|
|
ArgumentListNode* arguments = new ArgumentListNode(supercall_pos);
|
|
// 'this' parameter is the first argument to super class constructor.
|
|
AstNode* implicit_argument = new LoadLocalNode(supercall_pos, receiver);
|
|
arguments->Add(implicit_argument);
|
|
// Second implicit parameter is the construction phase. We optimistically
|
|
// assume that we can execute both the super initializer and the super
|
|
// constructor body. We may later change this to only execute the
|
|
// super initializer.
|
|
AstNode* phase_parameter =
|
|
new LiteralNode(supercall_pos,
|
|
Smi::ZoneHandle(Z, Smi::New(Function::kCtorPhaseAll)));
|
|
arguments->Add(phase_parameter);
|
|
// 'this' parameter must not be accessible to the other super call arguments.
|
|
receiver->set_invisible(true);
|
|
ParseActualParameters(arguments, kAllowConst);
|
|
receiver->set_invisible(false);
|
|
|
|
// Resolve the constructor.
|
|
const Function& super_ctor = Function::ZoneHandle(Z,
|
|
super_class.LookupConstructor(ctor_name));
|
|
if (super_ctor.IsNull()) {
|
|
ReportError(supercall_pos,
|
|
"super class constructor '%s' not found",
|
|
ctor_name.ToCString());
|
|
}
|
|
if (current_function().is_const() && !super_ctor.is_const()) {
|
|
ReportError(supercall_pos, "super constructor must be const");
|
|
}
|
|
String& error_message = String::Handle(Z);
|
|
if (!super_ctor.AreValidArguments(arguments->length(),
|
|
arguments->names(),
|
|
&error_message)) {
|
|
ReportError(supercall_pos,
|
|
"invalid arguments passed to super class constructor '%s': %s",
|
|
ctor_name.ToCString(),
|
|
error_message.ToCString());
|
|
}
|
|
return new StaticCallNode(supercall_pos, super_ctor, arguments);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseInitializer(const Class& cls,
|
|
LocalVariable* receiver,
|
|
GrowableArray<Field*>* initialized_fields) {
|
|
TRACE_PARSER("ParseInitializer");
|
|
const intptr_t field_pos = TokenPos();
|
|
if (CurrentToken() == Token::kTHIS) {
|
|
ConsumeToken();
|
|
ExpectToken(Token::kPERIOD);
|
|
}
|
|
const String& field_name = *ExpectIdentifier("field name expected");
|
|
ExpectToken(Token::kASSIGN);
|
|
|
|
const bool saved_mode = SetAllowFunctionLiterals(false);
|
|
// "this" must not be accessible in initializer expressions.
|
|
receiver->set_invisible(true);
|
|
AstNode* init_expr = ParseConditionalExpr();
|
|
if (CurrentToken() == Token::kCASCADE) {
|
|
init_expr = ParseCascades(init_expr);
|
|
}
|
|
receiver->set_invisible(false);
|
|
SetAllowFunctionLiterals(saved_mode);
|
|
if (current_function().is_const() && !init_expr->IsPotentiallyConst()) {
|
|
ReportError(field_pos,
|
|
"initializer expression must be compile time constant.");
|
|
}
|
|
Field& field = Field::ZoneHandle(Z, cls.LookupInstanceField(field_name));
|
|
if (field.IsNull()) {
|
|
ReportError(field_pos, "unresolved reference to instance field '%s'",
|
|
field_name.ToCString());
|
|
}
|
|
EnsureExpressionTemp();
|
|
AstNode* instance = new(Z) LoadLocalNode(field_pos, receiver);
|
|
AstNode* initializer = CheckDuplicateFieldInit(field_pos,
|
|
initialized_fields, instance, &field, init_expr);
|
|
if (initializer == NULL) {
|
|
initializer =
|
|
new(Z) StoreInstanceFieldNode(field_pos, instance, field, init_expr);
|
|
}
|
|
return initializer;
|
|
}
|
|
|
|
|
|
void Parser::CheckFieldsInitialized(const Class& cls) {
|
|
const Array& fields = Array::Handle(Z, cls.fields());
|
|
Field& field = Field::Handle(Z);
|
|
SequenceNode* initializers = current_block_->statements;
|
|
for (int field_num = 0; field_num < fields.Length(); field_num++) {
|
|
field ^= fields.At(field_num);
|
|
if (field.is_static()) {
|
|
continue;
|
|
}
|
|
|
|
bool found = false;
|
|
for (int i = 0; i < initializers->length(); i++) {
|
|
found = false;
|
|
if (initializers->NodeAt(i)->IsStoreInstanceFieldNode()) {
|
|
StoreInstanceFieldNode* initializer =
|
|
initializers->NodeAt(i)->AsStoreInstanceFieldNode();
|
|
if (initializer->field().raw() == field.raw()) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (found) continue;
|
|
|
|
field.RecordStore(Object::Handle(Z));
|
|
}
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseExternalInitializedField(const Field& field) {
|
|
// Only use this function if the initialized field originates
|
|
// from a different class. We need to save and restore current
|
|
// class, library, and token stream (script).
|
|
ASSERT(current_class().raw() != field.origin());
|
|
const Class& saved_class = Class::Handle(Z, current_class().raw());
|
|
const Library& saved_library = Library::Handle(Z, library().raw());
|
|
const Script& saved_script = Script::Handle(Z, script().raw());
|
|
const intptr_t saved_token_pos = TokenPos();
|
|
|
|
set_current_class(Class::Handle(Z, field.origin()));
|
|
set_library(Library::Handle(Z, current_class().library()));
|
|
SetScript(Script::Handle(Z, current_class().script()),
|
|
field.token_pos());
|
|
|
|
ASSERT(IsIdentifier());
|
|
ConsumeToken();
|
|
ExpectToken(Token::kASSIGN);
|
|
AstNode* init_expr = NULL;
|
|
intptr_t expr_pos = TokenPos();
|
|
if (field.is_const()) {
|
|
init_expr = ParseConstExpr();
|
|
} else {
|
|
init_expr = ParseExpr(kAllowConst, kConsumeCascades);
|
|
if (init_expr->EvalConstExpr() != NULL) {
|
|
init_expr =
|
|
new LiteralNode(field.token_pos(),
|
|
EvaluateConstExpr(expr_pos, init_expr));
|
|
}
|
|
}
|
|
set_current_class(saved_class);
|
|
set_library(saved_library);
|
|
SetScript(saved_script, saved_token_pos);
|
|
return init_expr;
|
|
}
|
|
|
|
|
|
void Parser::ParseInitializedInstanceFields(const Class& cls,
|
|
LocalVariable* receiver,
|
|
GrowableArray<Field*>* initialized_fields) {
|
|
TRACE_PARSER("ParseInitializedInstanceFields");
|
|
const Array& fields = Array::Handle(Z, cls.fields());
|
|
Field& f = Field::Handle(Z);
|
|
const intptr_t saved_pos = TokenPos();
|
|
for (int i = 0; i < fields.Length(); i++) {
|
|
f ^= fields.At(i);
|
|
if (!f.is_static() && f.has_initializer()) {
|
|
Field& field = Field::ZoneHandle(Z);
|
|
field ^= fields.At(i);
|
|
if (field.is_final()) {
|
|
// Final fields with initializer expression may not be initialized
|
|
// again by constructors. Remember that this field is already
|
|
// initialized.
|
|
initialized_fields->Add(&field);
|
|
}
|
|
AstNode* init_expr = NULL;
|
|
if (current_class().raw() != field.origin()) {
|
|
init_expr = ParseExternalInitializedField(field);
|
|
} else {
|
|
SetPosition(field.token_pos());
|
|
ASSERT(IsIdentifier());
|
|
ConsumeToken();
|
|
ExpectToken(Token::kASSIGN);
|
|
if (current_class().is_const()) {
|
|
// If the class has a const contructor, the initializer
|
|
// expression must be a compile-time constant.
|
|
init_expr = ParseConstExpr();
|
|
} else {
|
|
intptr_t expr_pos = TokenPos();
|
|
init_expr = ParseExpr(kAllowConst, kConsumeCascades);
|
|
if (init_expr->EvalConstExpr() != NULL) {
|
|
init_expr = new LiteralNode(field.token_pos(),
|
|
EvaluateConstExpr(expr_pos, init_expr));
|
|
}
|
|
}
|
|
}
|
|
ASSERT(init_expr != NULL);
|
|
AstNode* instance = new LoadLocalNode(field.token_pos(), receiver);
|
|
EnsureExpressionTemp();
|
|
AstNode* field_init =
|
|
new StoreInstanceFieldNode(field.token_pos(),
|
|
instance,
|
|
field,
|
|
init_expr);
|
|
current_block_->statements->Add(field_init);
|
|
}
|
|
}
|
|
initialized_fields->Add(NULL); // End of inline initializers.
|
|
SetPosition(saved_pos);
|
|
}
|
|
|
|
|
|
AstNode* Parser::CheckDuplicateFieldInit(
|
|
intptr_t init_pos,
|
|
GrowableArray<Field*>* initialized_fields,
|
|
AstNode* instance,
|
|
Field* field,
|
|
AstNode* init_value) {
|
|
ASSERT(!field->is_static());
|
|
AstNode* result = NULL;
|
|
|
|
// The initializer_list is divided into two sections. The sections
|
|
// are separated by a NULL entry: [f0, ... fn, NULL, fn+1, ...]
|
|
// The first fields f0 .. fn are final fields of the class that
|
|
// have an initializer expression inlined in the class declaration.
|
|
// The remaining fields are those initialized by the constructor's
|
|
// initializing formals and initializer list
|
|
int initializer_idx = 0;
|
|
while (initializer_idx < initialized_fields->length()) {
|
|
Field* initialized_field = (*initialized_fields)[initializer_idx];
|
|
initializer_idx++;
|
|
if (initialized_field == NULL) {
|
|
break;
|
|
}
|
|
if (initialized_field->raw() == field->raw()) {
|
|
// This final field has been initialized by an inlined
|
|
// initializer expression. This is a runtime error.
|
|
// Throw a NoSuchMethodError for the missing setter.
|
|
ASSERT(field->is_final());
|
|
|
|
// Build a call to NoSuchMethodError::_throwNew(
|
|
// Object receiver,
|
|
// String memberName,
|
|
// int invocation_type,
|
|
// List arguments,
|
|
// List argumentNames,
|
|
// List existingArgumentNames);
|
|
|
|
ArgumentListNode* nsm_args = new(Z) ArgumentListNode(init_pos);
|
|
// Object receiver.
|
|
nsm_args->Add(instance);
|
|
|
|
// String memberName.
|
|
String& setter_name = String::ZoneHandle(field->name());
|
|
setter_name = Field::SetterSymbol(setter_name);
|
|
nsm_args->Add(new(Z) LiteralNode(init_pos, setter_name));
|
|
|
|
// Smi invocation_type.
|
|
const int invocation_type =
|
|
InvocationMirror::EncodeType(InvocationMirror::kDynamic,
|
|
InvocationMirror::kSetter);
|
|
nsm_args->Add(new(Z) LiteralNode(
|
|
init_pos, Smi::ZoneHandle(Z, Smi::New(invocation_type))));
|
|
|
|
// List arguments.
|
|
GrowableArray<AstNode*> setter_args;
|
|
setter_args.Add(init_value);
|
|
ArrayNode* setter_args_array = new(Z) ArrayNode(
|
|
init_pos,
|
|
Type::ZoneHandle(Z, Type::ArrayType()),
|
|
setter_args);
|
|
nsm_args->Add(setter_args_array);
|
|
|
|
// List argumentNames.
|
|
// The missing implicit setter of the field has no argument names.
|
|
nsm_args->Add(new(Z) LiteralNode(init_pos, Array::ZoneHandle(Z)));
|
|
|
|
// List existingArgumentNames.
|
|
// There is no setter for the final field, thus there are
|
|
// no existing names.
|
|
nsm_args->Add(new(Z) LiteralNode(init_pos, Array::ZoneHandle(Z)));
|
|
|
|
AstNode* nsm_call =
|
|
MakeStaticCall(Symbols::NoSuchMethodError(),
|
|
Library::PrivateCoreLibName(Symbols::ThrowNew()),
|
|
nsm_args);
|
|
|
|
LetNode* let = new(Z) LetNode(init_pos);
|
|
let->AddNode(init_value);
|
|
let->AddNode(nsm_call);
|
|
result = let;
|
|
}
|
|
}
|
|
// The remaining elements in initialized_fields are fields that
|
|
// are initialized through initializing formal parameters, or
|
|
// in the constructor's initializer list. If there is a duplicate,
|
|
// it is a compile time error.
|
|
while (initializer_idx < initialized_fields->length()) {
|
|
Field* initialized_field = (*initialized_fields)[initializer_idx];
|
|
initializer_idx++;
|
|
if (initialized_field->raw() == field->raw()) {
|
|
ReportError(init_pos,
|
|
"duplicate initializer for field %s",
|
|
String::Handle(Z, field->name()).ToCString());
|
|
}
|
|
}
|
|
initialized_fields->Add(field);
|
|
return result;
|
|
}
|
|
|
|
|
|
void Parser::ParseInitializers(const Class& cls,
|
|
LocalVariable* receiver,
|
|
GrowableArray<Field*>* initialized_fields) {
|
|
TRACE_PARSER("ParseInitializers");
|
|
bool super_init_seen = false;
|
|
if (CurrentToken() == Token::kCOLON) {
|
|
do {
|
|
ConsumeToken(); // Colon or comma.
|
|
AstNode* init_statement;
|
|
if (CurrentToken() == Token::kSUPER) {
|
|
if (super_init_seen) {
|
|
ReportError("duplicate call to super constructor");
|
|
}
|
|
init_statement = ParseSuperInitializer(cls, receiver);
|
|
super_init_seen = true;
|
|
} else {
|
|
init_statement = ParseInitializer(cls, receiver, initialized_fields);
|
|
}
|
|
current_block_->statements->Add(init_statement);
|
|
} while (CurrentToken() == Token::kCOMMA);
|
|
}
|
|
if (!super_init_seen) {
|
|
// Generate implicit super() if we haven't seen an explicit super call
|
|
// or constructor redirection.
|
|
GenerateSuperConstructorCall(cls, TokenPos(), receiver, NULL);
|
|
}
|
|
CheckFieldsInitialized(cls);
|
|
}
|
|
|
|
|
|
void Parser::ParseConstructorRedirection(const Class& cls,
|
|
LocalVariable* receiver) {
|
|
TRACE_PARSER("ParseConstructorRedirection");
|
|
ExpectToken(Token::kCOLON);
|
|
ASSERT(CurrentToken() == Token::kTHIS);
|
|
const intptr_t call_pos = TokenPos();
|
|
ConsumeToken();
|
|
String& ctor_name = String::Handle(Z, cls.Name());
|
|
|
|
ctor_name = String::Concat(ctor_name, Symbols::Dot());
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
ctor_name = String::Concat(ctor_name,
|
|
*ExpectIdentifier("constructor name expected"));
|
|
}
|
|
CheckToken(Token::kLPAREN, "parameter list expected");
|
|
|
|
ArgumentListNode* arguments = new ArgumentListNode(call_pos);
|
|
// 'this' parameter is the first argument to constructor.
|
|
AstNode* implicit_argument = new LoadLocalNode(call_pos, receiver);
|
|
arguments->Add(implicit_argument);
|
|
// Construction phase parameter is second argument.
|
|
LocalVariable* phase_param = LookupPhaseParameter();
|
|
ASSERT(phase_param != NULL);
|
|
AstNode* phase_argument = new LoadLocalNode(call_pos, phase_param);
|
|
arguments->Add(phase_argument);
|
|
receiver->set_invisible(true);
|
|
ParseActualParameters(arguments, kAllowConst);
|
|
receiver->set_invisible(false);
|
|
// Resolve the constructor.
|
|
const Function& redirect_ctor = Function::ZoneHandle(Z,
|
|
cls.LookupConstructor(ctor_name));
|
|
if (redirect_ctor.IsNull()) {
|
|
ReportError(call_pos, "constructor '%s' not found", ctor_name.ToCString());
|
|
}
|
|
String& error_message = String::Handle(Z);
|
|
if (!redirect_ctor.AreValidArguments(arguments->length(),
|
|
arguments->names(),
|
|
&error_message)) {
|
|
ReportError(call_pos,
|
|
"invalid arguments passed to constructor '%s': %s",
|
|
ctor_name.ToCString(),
|
|
error_message.ToCString());
|
|
}
|
|
current_block_->statements->Add(
|
|
new StaticCallNode(call_pos, redirect_ctor, arguments));
|
|
}
|
|
|
|
|
|
SequenceNode* Parser::MakeImplicitConstructor(const Function& func) {
|
|
ASSERT(func.IsConstructor());
|
|
ASSERT(func.Owner() == current_class().raw());
|
|
const intptr_t ctor_pos = TokenPos();
|
|
OpenFunctionBlock(func);
|
|
|
|
LocalVariable* receiver = new LocalVariable(
|
|
Scanner::kNoSourcePos, Symbols::This(), *ReceiverType(current_class()));
|
|
current_block_->scope->InsertParameterAt(0, receiver);
|
|
|
|
LocalVariable* phase_parameter =
|
|
new LocalVariable(Scanner::kNoSourcePos,
|
|
Symbols::PhaseParameter(),
|
|
Type::ZoneHandle(Z, Type::SmiType()));
|
|
current_block_->scope->InsertParameterAt(1, phase_parameter);
|
|
|
|
// Parse expressions of instance fields that have an explicit
|
|
// initializer expression.
|
|
// The receiver must not be visible to field initializer expressions.
|
|
receiver->set_invisible(true);
|
|
GrowableArray<Field*> initialized_fields;
|
|
ParseInitializedInstanceFields(
|
|
current_class(), receiver, &initialized_fields);
|
|
receiver->set_invisible(false);
|
|
|
|
// If the class of this implicit constructor is a mixin application alias,
|
|
// it is a forwarding constructor of the aliased mixin application class.
|
|
// If the class of this implicit constructor is a mixin application class,
|
|
// it is a forwarding constructor of the mixin. The forwarding
|
|
// constructor initializes the instance fields that have initializer
|
|
// expressions and then calls the respective super constructor with
|
|
// the same name and number of parameters.
|
|
ArgumentListNode* forwarding_args = NULL;
|
|
if (current_class().is_mixin_app_alias() ||
|
|
current_class().IsMixinApplication()) {
|
|
// At this point we don't support forwarding constructors
|
|
// that have optional parameters because we don't know the default
|
|
// values of the optional parameters. We would have to compile the super
|
|
// constructor to get the default values. Also, the spec is not clear
|
|
// whether optional parameters are even allowed in this situation.
|
|
// TODO(hausner): Remove this limitation if the language spec indeed
|
|
// allows optional parameters.
|
|
if (func.HasOptionalParameters()) {
|
|
const Class& super_class = Class::Handle(Z, current_class().SuperClass());
|
|
ReportError(ctor_pos,
|
|
"cannot generate an implicit mixin application constructor "
|
|
"forwarding to a super class constructor with optional "
|
|
"parameters; add a constructor without optional parameters "
|
|
"to class '%s' that redirects to the constructor with "
|
|
"optional parameters and invoke it via super from a "
|
|
"constructor of the class extending the mixin application",
|
|
String::Handle(Z, super_class.Name()).ToCString());
|
|
}
|
|
|
|
// Prepare user-defined arguments to be forwarded to super call.
|
|
// The first user-defined argument is at position 2.
|
|
forwarding_args = new ArgumentListNode(Scanner::kNoSourcePos);
|
|
for (int i = 2; i < func.NumParameters(); i++) {
|
|
LocalVariable* param = new LocalVariable(
|
|
Scanner::kNoSourcePos,
|
|
String::ZoneHandle(Z, func.ParameterNameAt(i)),
|
|
Type::ZoneHandle(Z, Type::DynamicType()));
|
|
current_block_->scope->InsertParameterAt(i, param);
|
|
forwarding_args->Add(new LoadLocalNode(Scanner::kNoSourcePos, param));
|
|
}
|
|
}
|
|
|
|
GenerateSuperConstructorCall(current_class(),
|
|
Scanner::kNoSourcePos,
|
|
receiver,
|
|
forwarding_args);
|
|
CheckFieldsInitialized(current_class());
|
|
|
|
// Empty constructor body.
|
|
current_block_->statements->Add(new ReturnNode(Scanner::kNoSourcePos));
|
|
SequenceNode* statements = CloseBlock();
|
|
return statements;
|
|
}
|
|
|
|
|
|
void Parser::CheckRecursiveInvocation() {
|
|
const GrowableObjectArray& pending_functions =
|
|
GrowableObjectArray::Handle(Z,
|
|
I->object_store()->pending_functions());
|
|
for (int i = 0; i < pending_functions.Length(); i++) {
|
|
if (pending_functions.At(i) == current_function().raw()) {
|
|
const String& fname =
|
|
String::Handle(Z, current_function().UserVisibleName());
|
|
ReportError("circular dependency for function %s", fname.ToCString());
|
|
}
|
|
}
|
|
ASSERT(!unregister_pending_function_);
|
|
pending_functions.Add(current_function());
|
|
unregister_pending_function_ = true;
|
|
}
|
|
|
|
|
|
// Parser is at the opening parenthesis of the formal parameter declaration
|
|
// of function. Parse the formal parameters, initializers and code.
|
|
SequenceNode* Parser::ParseConstructor(const Function& func,
|
|
Array* default_parameter_values) {
|
|
TRACE_PARSER("ParseConstructor");
|
|
ASSERT(func.IsConstructor());
|
|
ASSERT(!func.IsFactory());
|
|
ASSERT(!func.is_static());
|
|
ASSERT(!func.IsLocalFunction());
|
|
const Class& cls = Class::Handle(Z, func.Owner());
|
|
ASSERT(!cls.IsNull());
|
|
|
|
CheckRecursiveInvocation();
|
|
|
|
if (func.IsImplicitConstructor()) {
|
|
// Special case: implicit constructor.
|
|
// The parser adds an implicit default constructor when a class
|
|
// does not have any explicit constructor or factory (see
|
|
// Parser::AddImplicitConstructor).
|
|
// There is no source text to parse. We just build the
|
|
// sequence node by hand.
|
|
return MakeImplicitConstructor(func);
|
|
}
|
|
|
|
OpenFunctionBlock(func);
|
|
ParamList params;
|
|
const bool allow_explicit_default_values = true;
|
|
ASSERT(CurrentToken() == Token::kLPAREN);
|
|
|
|
// Add implicit receiver parameter which is passed the allocated
|
|
// but uninitialized instance to construct.
|
|
ASSERT(current_class().raw() == func.Owner());
|
|
params.AddReceiver(ReceiverType(current_class()), func.token_pos());
|
|
|
|
// Add implicit parameter for construction phase.
|
|
params.AddFinalParameter(
|
|
TokenPos(),
|
|
&Symbols::PhaseParameter(),
|
|
&Type::ZoneHandle(Z, Type::SmiType()));
|
|
|
|
if (func.is_const()) {
|
|
params.SetImplicitlyFinal();
|
|
}
|
|
ParseFormalParameterList(allow_explicit_default_values, false, ¶ms);
|
|
|
|
SetupDefaultsForOptionalParams(¶ms, default_parameter_values);
|
|
ASSERT(AbstractType::Handle(Z, func.result_type()).IsResolved());
|
|
ASSERT(func.NumParameters() == params.parameters->length());
|
|
|
|
// Now populate function scope with the formal parameters.
|
|
AddFormalParamsToScope(¶ms, current_block_->scope);
|
|
|
|
const bool is_redirecting_constructor =
|
|
(CurrentToken() == Token::kCOLON) &&
|
|
((LookaheadToken(1) == Token::kTHIS) &&
|
|
((LookaheadToken(2) == Token::kLPAREN) ||
|
|
((LookaheadToken(2) == Token::kPERIOD) &&
|
|
(LookaheadToken(4) == Token::kLPAREN))));
|
|
|
|
GrowableArray<Field*> initialized_fields;
|
|
LocalVariable* receiver = (*params.parameters)[0].var;
|
|
OpenBlock();
|
|
|
|
// If this is not a redirecting constructor, initialize
|
|
// instance fields that have an explicit initializer expression.
|
|
if (!is_redirecting_constructor) {
|
|
// The formal parameter names must not be visible to the instance
|
|
// field initializer expressions, yet the parameters must be added to
|
|
// the scope so the expressions use the correct offsets for 'this' when
|
|
// storing values. We make the formal parameters temporarily invisible
|
|
// while parsing the instance field initializer expressions.
|
|
params.SetInvisible(true);
|
|
ParseInitializedInstanceFields(cls, receiver, &initialized_fields);
|
|
// Make the parameters (which are in the outer scope) visible again.
|
|
params.SetInvisible(false);
|
|
}
|
|
|
|
// Turn formal field parameters into field initializers.
|
|
if (params.has_field_initializer) {
|
|
// First two parameters are implicit receiver and phase.
|
|
ASSERT(params.parameters->length() >= 2);
|
|
for (int i = 2; i < params.parameters->length(); i++) {
|
|
ParamDesc& param = (*params.parameters)[i];
|
|
if (param.is_field_initializer) {
|
|
const String& field_name = *param.name;
|
|
Field& field =
|
|
Field::ZoneHandle(Z, cls.LookupInstanceField(field_name));
|
|
if (field.IsNull()) {
|
|
ReportError(param.name_pos,
|
|
"unresolved reference to instance field '%s'",
|
|
field_name.ToCString());
|
|
}
|
|
if (is_redirecting_constructor) {
|
|
ReportError(param.name_pos,
|
|
"redirecting constructors may not have "
|
|
"initializing formal parameters");
|
|
}
|
|
|
|
if (!param.has_explicit_type) {
|
|
const AbstractType& field_type =
|
|
AbstractType::ZoneHandle(Z, field.type());
|
|
param.type = &field_type;
|
|
// Parameter type was already set to dynamic when parsing the class
|
|
// declaration: fix it.
|
|
func.SetParameterTypeAt(i, field_type);
|
|
}
|
|
|
|
AstNode* instance = new LoadLocalNode(param.name_pos, receiver);
|
|
// Initializing formals cannot be used in the explicit initializer
|
|
// list, nor can they be used in the constructor body.
|
|
// Thus, they are set to be invisible when added to the scope.
|
|
LocalVariable* p = param.var;
|
|
ASSERT(p != NULL);
|
|
ASSERT(p->is_invisible());
|
|
AstNode* value = new LoadLocalNode(param.name_pos, p);
|
|
EnsureExpressionTemp();
|
|
AstNode* initializer =
|
|
CheckDuplicateFieldInit(param.name_pos,
|
|
&initialized_fields,
|
|
instance,
|
|
&field,
|
|
value);
|
|
if (initializer == NULL) {
|
|
initializer = new(Z) StoreInstanceFieldNode(
|
|
param.name_pos, instance, field, value);
|
|
}
|
|
current_block_->statements->Add(initializer);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_redirecting_constructor) {
|
|
ParseConstructorRedirection(cls, receiver);
|
|
} else {
|
|
ParseInitializers(cls, receiver, &initialized_fields);
|
|
}
|
|
|
|
SequenceNode* init_statements = CloseBlock();
|
|
if (is_redirecting_constructor) {
|
|
// A redirecting super constructor simply passes the phase parameter on to
|
|
// the target which executes the corresponding phase.
|
|
current_block_->statements->Add(init_statements);
|
|
} else if (init_statements->length() > 0) {
|
|
// Generate guard around the initializer code.
|
|
LocalVariable* phase_param = LookupPhaseParameter();
|
|
AstNode* phase_value = new
|
|
LoadLocalNode(Scanner::kNoSourcePos, phase_param);
|
|
AstNode* phase_check = new BinaryOpNode(
|
|
Scanner::kNoSourcePos, Token::kBIT_AND, phase_value,
|
|
new LiteralNode(Scanner::kNoSourcePos,
|
|
Smi::ZoneHandle(Z, Smi::New(Function::kCtorPhaseInit))));
|
|
AstNode* comparison =
|
|
new ComparisonNode(Scanner::kNoSourcePos,
|
|
Token::kNE_STRICT,
|
|
phase_check,
|
|
new LiteralNode(TokenPos(),
|
|
Smi::ZoneHandle(Z, Smi::New(0))));
|
|
AstNode* guarded_init_statements =
|
|
new IfNode(Scanner::kNoSourcePos,
|
|
comparison,
|
|
init_statements,
|
|
NULL);
|
|
current_block_->statements->Add(guarded_init_statements);
|
|
}
|
|
|
|
// Parsing of initializers done. Now we parse the constructor body
|
|
// and add the implicit super call to the super constructor's body
|
|
// if necessary.
|
|
StaticCallNode* super_call = NULL;
|
|
// Look for the super initializer call in the sequence of initializer
|
|
// statements. If it exists and is not the last initializer statement,
|
|
// we need to create an implicit super call to the super constructor's
|
|
// body.
|
|
// Thus, iterate over all but the last initializer to see whether
|
|
// it's a super constructor call.
|
|
for (int i = 0; i < init_statements->length() - 1; i++) {
|
|
if (init_statements->NodeAt(i)->IsStaticCallNode()) {
|
|
StaticCallNode* static_call =
|
|
init_statements->NodeAt(i)->AsStaticCallNode();
|
|
if (static_call->function().IsConstructor()) {
|
|
super_call = static_call;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (super_call != NULL) {
|
|
// Generate an implicit call to the super constructor's body.
|
|
// We need to patch the super _initializer_ call so that it
|
|
// saves the evaluated actual arguments in temporary variables.
|
|
// The temporary variables are necessary so that the argument
|
|
// expressions are not evaluated twice.
|
|
// Note: we should never get here in the case of a redirecting
|
|
// constructor. In that case, the call to the target constructor
|
|
// is the "super call" and is implicitly at the end of the
|
|
// initializer list.
|
|
ASSERT(!is_redirecting_constructor);
|
|
ArgumentListNode* ctor_args = super_call->arguments();
|
|
// The super initializer call has at least 2 arguments: the
|
|
// implicit receiver, and the hidden construction phase.
|
|
ASSERT(ctor_args->length() >= 2);
|
|
for (int i = 2; i < ctor_args->length(); i++) {
|
|
AstNode* arg = ctor_args->NodeAt(i);
|
|
if (!IsSimpleLocalOrLiteralNode(arg)) {
|
|
LocalVariable* temp =
|
|
CreateTempConstVariable(arg->token_pos(), "sca");
|
|
AstNode* save_temp = new StoreLocalNode(arg->token_pos(), temp, arg);
|
|
ctor_args->SetNodeAt(i, save_temp);
|
|
}
|
|
}
|
|
}
|
|
OpenBlock(); // Block to collect constructor body nodes.
|
|
intptr_t body_pos = TokenPos();
|
|
|
|
// Insert the implicit super call to the super constructor body.
|
|
if (super_call != NULL) {
|
|
ArgumentListNode* initializer_args = super_call->arguments();
|
|
const Function& super_ctor = super_call->function();
|
|
// Patch the initializer call so it only executes the super initializer.
|
|
initializer_args->SetNodeAt(1, new LiteralNode(
|
|
body_pos, Smi::ZoneHandle(Z, Smi::New(Function::kCtorPhaseInit))));
|
|
|
|
ArgumentListNode* super_call_args = new ArgumentListNode(body_pos);
|
|
// First argument is the receiver.
|
|
super_call_args->Add(new LoadLocalNode(body_pos, receiver));
|
|
// Second argument is the construction phase argument.
|
|
AstNode* phase_parameter = new(Z) LiteralNode(
|
|
body_pos, Smi::ZoneHandle(Z, Smi::New(Function::kCtorPhaseBody)));
|
|
super_call_args->Add(phase_parameter);
|
|
super_call_args->set_names(initializer_args->names());
|
|
for (int i = 2; i < initializer_args->length(); i++) {
|
|
AstNode* arg = initializer_args->NodeAt(i);
|
|
if (arg->IsLiteralNode()) {
|
|
LiteralNode* lit = arg->AsLiteralNode();
|
|
super_call_args->Add(new LiteralNode(body_pos, lit->literal()));
|
|
} else {
|
|
ASSERT(arg->IsLoadLocalNode() || arg->IsStoreLocalNode());
|
|
if (arg->IsLoadLocalNode()) {
|
|
const LocalVariable& temp = arg->AsLoadLocalNode()->local();
|
|
super_call_args->Add(new LoadLocalNode(body_pos, &temp));
|
|
} else if (arg->IsStoreLocalNode()) {
|
|
const LocalVariable& temp = arg->AsStoreLocalNode()->local();
|
|
super_call_args->Add(new LoadLocalNode(body_pos, &temp));
|
|
}
|
|
}
|
|
}
|
|
ASSERT(super_ctor.AreValidArguments(super_call_args->length(),
|
|
super_call_args->names(),
|
|
NULL));
|
|
current_block_->statements->Add(
|
|
new StaticCallNode(body_pos, super_ctor, super_call_args));
|
|
}
|
|
|
|
if (CurrentToken() == Token::kLBRACE) {
|
|
// We checked in the top-level parse phase that a redirecting
|
|
// constructor does not have a body.
|
|
ASSERT(!is_redirecting_constructor);
|
|
ConsumeToken();
|
|
ParseStatementSequence();
|
|
ExpectToken(Token::kRBRACE);
|
|
} else if (CurrentToken() == Token::kARROW) {
|
|
ReportError("constructors may not return a value");
|
|
} else if (IsLiteral("native")) {
|
|
ReportError("native constructors not supported");
|
|
} else if (CurrentToken() == Token::kSEMICOLON) {
|
|
// Some constructors have no function body.
|
|
ConsumeToken();
|
|
if (func.is_external()) {
|
|
// Body of an external method contains a single throw.
|
|
const String& function_name = String::ZoneHandle(func.name());
|
|
current_block_->statements->Add(
|
|
ThrowNoSuchMethodError(TokenPos(),
|
|
cls,
|
|
function_name,
|
|
NULL, // No arguments.
|
|
InvocationMirror::kStatic,
|
|
InvocationMirror::kMethod,
|
|
NULL)); // No existing function.
|
|
}
|
|
} else {
|
|
UnexpectedToken();
|
|
}
|
|
|
|
SequenceNode* ctor_block = CloseBlock();
|
|
if (ctor_block->length() > 0) {
|
|
// Generate guard around the constructor body code.
|
|
LocalVariable* phase_param = LookupPhaseParameter();
|
|
AstNode* phase_value =
|
|
new LoadLocalNode(Scanner::kNoSourcePos, phase_param);
|
|
AstNode* phase_check =
|
|
new BinaryOpNode(Scanner::kNoSourcePos, Token::kBIT_AND,
|
|
phase_value,
|
|
new LiteralNode(Scanner::kNoSourcePos,
|
|
Smi::ZoneHandle(Smi::New(Function::kCtorPhaseBody))));
|
|
AstNode* comparison =
|
|
new ComparisonNode(Scanner::kNoSourcePos,
|
|
Token::kNE_STRICT,
|
|
phase_check,
|
|
new LiteralNode(body_pos,
|
|
Smi::ZoneHandle(Smi::New(0))));
|
|
AstNode* guarded_block_statements =
|
|
new IfNode(Scanner::kNoSourcePos, comparison, ctor_block, NULL);
|
|
current_block_->statements->Add(guarded_block_statements);
|
|
}
|
|
current_block_->statements->Add(new ReturnNode(func.end_token_pos()));
|
|
SequenceNode* statements = CloseBlock();
|
|
return statements;
|
|
}
|
|
|
|
|
|
// Parser is at the opening parenthesis of the formal parameter
|
|
// declaration of the function or constructor.
|
|
// Parse the formal parameters and code.
|
|
SequenceNode* Parser::ParseFunc(const Function& func,
|
|
Array* default_parameter_values) {
|
|
TRACE_PARSER("ParseFunc");
|
|
Function& saved_innermost_function =
|
|
Function::Handle(Z, innermost_function().raw());
|
|
innermost_function_ = func.raw();
|
|
|
|
// Save current try index. Try index starts at zero for each function.
|
|
intptr_t saved_try_index = last_used_try_index_;
|
|
last_used_try_index_ = 0;
|
|
|
|
// In case of nested async functions we also need to save the currently saved
|
|
// try context, the corresponding stack variable, and the scope where
|
|
// temporaries are added.
|
|
LocalVariable* saved_saved_try_ctx = parsed_function()->saved_try_ctx();
|
|
const String& saved_async_saved_try_ctx_name =
|
|
String::Handle(Z, parsed_function()->async_saved_try_ctx_name());
|
|
parsed_function()->reset_saved_try_ctx_vars();
|
|
LocalScope* saved_async_temp_scope = async_temp_scope_;
|
|
|
|
if (func.IsConstructor()) {
|
|
SequenceNode* statements = ParseConstructor(func, default_parameter_values);
|
|
innermost_function_ = saved_innermost_function.raw();
|
|
last_used_try_index_ = saved_try_index;
|
|
return statements;
|
|
}
|
|
|
|
ASSERT(!func.IsConstructor());
|
|
OpenFunctionBlock(func); // Build local scope for function.
|
|
|
|
ParamList params;
|
|
// An instance closure function may capture and access the receiver, but via
|
|
// the context and not via the first formal parameter.
|
|
if (func.IsClosureFunction()) {
|
|
// The first parameter of a closure function is the closure object.
|
|
ASSERT(!func.is_const()); // Closure functions cannot be const.
|
|
params.AddFinalParameter(
|
|
TokenPos(),
|
|
&Symbols::ClosureParameter(),
|
|
&Type::ZoneHandle(Z, Type::DynamicType()));
|
|
} else if (!func.is_static()) {
|
|
// Static functions do not have a receiver.
|
|
ASSERT(current_class().raw() == func.Owner());
|
|
params.AddReceiver(ReceiverType(current_class()), func.token_pos());
|
|
} else if (func.IsFactory()) {
|
|
// The first parameter of a factory is the TypeArguments vector of
|
|
// the type of the instance to be allocated.
|
|
params.AddFinalParameter(
|
|
TokenPos(),
|
|
&Symbols::TypeArgumentsParameter(),
|
|
&Type::ZoneHandle(Z, Type::DynamicType()));
|
|
}
|
|
ASSERT((CurrentToken() == Token::kLPAREN) ||
|
|
func.IsGetterFunction() ||
|
|
func.is_async_closure());
|
|
const bool allow_explicit_default_values = true;
|
|
if (func.IsGetterFunction()) {
|
|
// Populate function scope with the formal parameters. Since in this case
|
|
// we are compiling a getter this will at most populate the receiver.
|
|
AddFormalParamsToScope(¶ms, current_block_->scope);
|
|
} else if (func.is_async_closure()) {
|
|
// Async closures have two optional parameters:
|
|
// * A continuation result.
|
|
// * A continuation error.
|
|
//
|
|
// If the error!=null we rethrow the error at the next await.
|
|
const Type& dynamic_type = Type::ZoneHandle(Z, Type::DynamicType());
|
|
ParamDesc result_param;
|
|
result_param.name = &Symbols::AsyncOperationParam();
|
|
result_param.default_value = &Object::null_instance();
|
|
result_param.type = &dynamic_type;
|
|
ParamDesc error_param;
|
|
error_param.name = &Symbols::AsyncOperationErrorParam();
|
|
error_param.default_value = &Object::null_instance();
|
|
error_param.type = &dynamic_type;
|
|
ParamDesc stack_trace_param;
|
|
stack_trace_param.name = &Symbols::AsyncOperationStackTraceParam();
|
|
stack_trace_param.default_value = &Object::null_instance();
|
|
stack_trace_param.type = &dynamic_type;
|
|
params.parameters->Add(result_param);
|
|
params.parameters->Add(error_param);
|
|
params.parameters->Add(stack_trace_param);
|
|
params.num_optional_parameters += 3;
|
|
params.has_optional_positional_parameters = true;
|
|
SetupDefaultsForOptionalParams(¶ms, default_parameter_values);
|
|
AddFormalParamsToScope(¶ms, current_block_->scope);
|
|
ASSERT(AbstractType::Handle(Z, func.result_type()).IsResolved());
|
|
ASSERT(func.NumParameters() == params.parameters->length());
|
|
if (!Function::Handle(func.parent_function()).IsGetterFunction()) {
|
|
// Parse and discard any formal parameters. They are accessed as
|
|
// context variables.
|
|
ParamList parse_away;
|
|
ParseFormalParameterList(allow_explicit_default_values,
|
|
false,
|
|
&parse_away);
|
|
}
|
|
} else {
|
|
ParseFormalParameterList(allow_explicit_default_values, false, ¶ms);
|
|
|
|
// The number of parameters and their type are not yet set in local
|
|
// functions, since they are not 'top-level' parsed.
|
|
if (func.IsLocalFunction()) {
|
|
AddFormalParamsToFunction(¶ms, func);
|
|
}
|
|
SetupDefaultsForOptionalParams(¶ms, default_parameter_values);
|
|
ASSERT(AbstractType::Handle(Z, func.result_type()).IsResolved());
|
|
ASSERT(func.NumParameters() == params.parameters->length());
|
|
|
|
// Check whether the function has any field initializer formal parameters,
|
|
// which are not allowed in non-constructor functions.
|
|
if (params.has_field_initializer) {
|
|
for (int i = 0; i < params.parameters->length(); i++) {
|
|
ParamDesc& param = (*params.parameters)[i];
|
|
if (param.is_field_initializer) {
|
|
ReportError(param.name_pos,
|
|
"field initializer only allowed in constructors");
|
|
}
|
|
}
|
|
}
|
|
// Populate function scope with the formal parameters.
|
|
AddFormalParamsToScope(¶ms, current_block_->scope);
|
|
|
|
if (FLAG_enable_type_checks &&
|
|
(current_block_->scope->function_level() > 0)) {
|
|
// We are parsing, but not compiling, a local function.
|
|
// The instantiator may be required at run time for generic type checks.
|
|
if (IsInstantiatorRequired()) {
|
|
// Make sure that the receiver of the enclosing instance function
|
|
// (or implicit first parameter of an enclosing factory) is marked as
|
|
// captured if type checks are enabled, because they may access it to
|
|
// instantiate types.
|
|
CaptureInstantiator();
|
|
}
|
|
}
|
|
}
|
|
|
|
RawFunction::AsyncModifier func_modifier = ParseFunctionModifier();
|
|
func.set_modifier(func_modifier);
|
|
|
|
OpenBlock(); // Open a nested scope for the outermost function block.
|
|
|
|
Function& async_closure = Function::ZoneHandle(Z);
|
|
if (func.IsAsyncFunction() && !func.is_async_closure()) {
|
|
// The code of an async function is synthesized. Disable debugging.
|
|
func.set_is_debuggable(false);
|
|
async_closure = OpenAsyncFunction(func.token_pos());
|
|
} else if (func.is_async_closure()) {
|
|
// The closure containing the body of an async function is debuggable.
|
|
ASSERT(func.is_debuggable());
|
|
OpenAsyncClosure();
|
|
}
|
|
|
|
BoolScope allow_await(&this->await_is_keyword_,
|
|
func.IsAsyncFunction() || func.is_async_closure());
|
|
intptr_t end_token_pos = 0;
|
|
if (CurrentToken() == Token::kLBRACE) {
|
|
ConsumeToken();
|
|
if (String::Handle(Z, func.name()).Equals(Symbols::EqualOperator())) {
|
|
const Class& owner = Class::Handle(Z, func.Owner());
|
|
if (!owner.IsObjectClass()) {
|
|
AddEqualityNullCheck();
|
|
}
|
|
}
|
|
ParseStatementSequence();
|
|
end_token_pos = TokenPos();
|
|
ExpectToken(Token::kRBRACE);
|
|
} else if (CurrentToken() == Token::kARROW) {
|
|
ConsumeToken();
|
|
if (String::Handle(Z, func.name()).Equals(
|
|
Symbols::EqualOperator())) {
|
|
const Class& owner = Class::Handle(Z, func.Owner());
|
|
if (!owner.IsObjectClass()) {
|
|
AddEqualityNullCheck();
|
|
}
|
|
}
|
|
const intptr_t expr_pos = TokenPos();
|
|
AstNode* expr = ParseAwaitableExpr(kAllowConst, kConsumeCascades, NULL);
|
|
ASSERT(expr != NULL);
|
|
current_block_->statements->Add(new ReturnNode(expr_pos, expr));
|
|
end_token_pos = TokenPos();
|
|
} else if (IsLiteral("native")) {
|
|
if (String::Handle(Z, func.name()).Equals(
|
|
Symbols::EqualOperator())) {
|
|
const Class& owner = Class::Handle(Z, func.Owner());
|
|
if (!owner.IsObjectClass()) {
|
|
AddEqualityNullCheck();
|
|
}
|
|
}
|
|
ParseNativeFunctionBlock(¶ms, func);
|
|
end_token_pos = TokenPos();
|
|
ExpectSemicolon();
|
|
} else if (func.is_external()) {
|
|
// Body of an external method contains a single throw.
|
|
const String& function_name = String::ZoneHandle(Z, func.name());
|
|
current_block_->statements->Add(
|
|
ThrowNoSuchMethodError(TokenPos(),
|
|
Class::Handle(func.Owner()),
|
|
function_name,
|
|
NULL, // Ignore arguments.
|
|
func.is_static() ?
|
|
InvocationMirror::kStatic :
|
|
InvocationMirror::kDynamic,
|
|
InvocationMirror::kMethod,
|
|
&func)); // Unpatched external function.
|
|
end_token_pos = TokenPos();
|
|
} else {
|
|
UnexpectedToken();
|
|
}
|
|
|
|
ASSERT(func.end_token_pos() == func.token_pos() ||
|
|
func.end_token_pos() == end_token_pos);
|
|
func.set_end_token_pos(end_token_pos);
|
|
SequenceNode* body = CloseBlock();
|
|
if (func.IsAsyncFunction() && !func.is_async_closure()) {
|
|
body = CloseAsyncFunction(async_closure, body);
|
|
async_closure.set_end_token_pos(end_token_pos);
|
|
} else if (func.is_async_closure()) {
|
|
body = CloseAsyncClosure(body);
|
|
}
|
|
current_block_->statements->Add(body);
|
|
innermost_function_ = saved_innermost_function.raw();
|
|
last_used_try_index_ = saved_try_index;
|
|
async_temp_scope_ = saved_async_temp_scope;
|
|
parsed_function()->set_saved_try_ctx(saved_saved_try_ctx);
|
|
parsed_function()->set_async_saved_try_ctx_name(
|
|
saved_async_saved_try_ctx_name);
|
|
return CloseBlock();
|
|
}
|
|
|
|
|
|
void Parser::AddEqualityNullCheck() {
|
|
AstNode* argument =
|
|
new LoadLocalNode(Scanner::kNoSourcePos,
|
|
current_block_->scope->parent()->VariableAt(1));
|
|
LiteralNode* null_operand =
|
|
new LiteralNode(Scanner::kNoSourcePos, Instance::ZoneHandle(Z));
|
|
ComparisonNode* check_arg =
|
|
new ComparisonNode(Scanner::kNoSourcePos,
|
|
Token::kEQ_STRICT,
|
|
argument,
|
|
null_operand);
|
|
ComparisonNode* result =
|
|
new ComparisonNode(Scanner::kNoSourcePos,
|
|
Token::kEQ_STRICT,
|
|
LoadReceiver(Scanner::kNoSourcePos),
|
|
null_operand);
|
|
SequenceNode* arg_is_null = new SequenceNode(Scanner::kNoSourcePos,
|
|
current_block_->scope);
|
|
arg_is_null->Add(new ReturnNode(Scanner::kNoSourcePos, result));
|
|
IfNode* if_arg_null = new IfNode(Scanner::kNoSourcePos,
|
|
check_arg,
|
|
arg_is_null,
|
|
NULL);
|
|
current_block_->statements->Add(if_arg_null);
|
|
}
|
|
|
|
|
|
void Parser::SkipIf(Token::Kind token) {
|
|
if (CurrentToken() == token) {
|
|
ConsumeToken();
|
|
}
|
|
}
|
|
|
|
|
|
// Skips tokens up to matching closing parenthesis.
|
|
void Parser::SkipToMatchingParenthesis() {
|
|
Token::Kind current_token = CurrentToken();
|
|
ASSERT(current_token == Token::kLPAREN);
|
|
int level = 0;
|
|
do {
|
|
if (current_token == Token::kLPAREN) {
|
|
level++;
|
|
} else if (current_token == Token::kRPAREN) {
|
|
level--;
|
|
}
|
|
ConsumeToken();
|
|
current_token = CurrentToken();
|
|
} while ((level > 0) && (current_token != Token::kEOS));
|
|
}
|
|
|
|
|
|
void Parser::SkipInitializers() {
|
|
ASSERT(CurrentToken() == Token::kCOLON);
|
|
do {
|
|
ConsumeToken(); // Colon or comma.
|
|
if (CurrentToken() == Token::kSUPER) {
|
|
ConsumeToken();
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
ExpectIdentifier("identifier expected");
|
|
}
|
|
CheckToken(Token::kLPAREN);
|
|
SkipToMatchingParenthesis();
|
|
} else {
|
|
SkipIf(Token::kTHIS);
|
|
SkipIf(Token::kPERIOD);
|
|
ExpectIdentifier("identifier expected");
|
|
ExpectToken(Token::kASSIGN);
|
|
SetAllowFunctionLiterals(false);
|
|
SkipExpr();
|
|
SetAllowFunctionLiterals(true);
|
|
}
|
|
} while (CurrentToken() == Token::kCOMMA);
|
|
}
|
|
|
|
|
|
// If the current identifier is a library prefix followed by a period,
|
|
// consume the identifier and period, and return the resolved library
|
|
// prefix.
|
|
RawLibraryPrefix* Parser::ParsePrefix() {
|
|
ASSERT(IsIdentifier());
|
|
// A library prefix can never stand by itself. It must be followed by
|
|
// a period.
|
|
if (LookaheadToken(1) != Token::kPERIOD) {
|
|
return LibraryPrefix::null();
|
|
}
|
|
const String& ident = *CurrentLiteral();
|
|
|
|
// It is relatively fast to look up a name in the library dictionary,
|
|
// compared to searching the nested local scopes. Look up the name
|
|
// in the library scope and return in the common case where ident is
|
|
// not a library prefix.
|
|
LibraryPrefix& prefix =
|
|
LibraryPrefix::Handle(Z, library_.LookupLocalLibraryPrefix(ident));
|
|
if (prefix.IsNull()) {
|
|
return LibraryPrefix::null();
|
|
}
|
|
|
|
// A library prefix with the name exists. Now check whether it is
|
|
// shadowed by a local definition.
|
|
if (!is_top_level_ &&
|
|
ResolveIdentInLocalScope(TokenPos(), ident, NULL)) {
|
|
return LibraryPrefix::null();
|
|
}
|
|
// Check whether the identifier is shadowed by a type parameter.
|
|
ASSERT(!current_class().IsNull());
|
|
if (current_class().LookupTypeParameter(ident) != TypeParameter::null()) {
|
|
return LibraryPrefix::null();
|
|
}
|
|
|
|
// We have a name that is not shadowed, followed by a period.
|
|
// Consume the identifier and the period.
|
|
ConsumeToken();
|
|
ASSERT(CurrentToken() == Token::kPERIOD); // We checked above.
|
|
ConsumeToken();
|
|
return prefix.raw();
|
|
}
|
|
|
|
|
|
void Parser::ParseMethodOrConstructor(ClassDesc* members, MemberDesc* method) {
|
|
TRACE_PARSER("ParseMethodOrConstructor");
|
|
ASSERT(CurrentToken() == Token::kLPAREN || method->IsGetter());
|
|
ASSERT(method->type != NULL);
|
|
ASSERT(current_member_ == method);
|
|
|
|
if (method->has_var) {
|
|
ReportError(method->name_pos, "keyword var not allowed for methods");
|
|
}
|
|
if (method->has_final) {
|
|
ReportError(method->name_pos, "'final' not allowed for methods");
|
|
}
|
|
if (method->has_abstract && method->has_static) {
|
|
ReportError(method->name_pos,
|
|
"static method '%s' cannot be abstract",
|
|
method->name->ToCString());
|
|
}
|
|
if (method->has_const && !method->IsFactoryOrConstructor()) {
|
|
ReportError(method->name_pos, "'const' not allowed for methods");
|
|
}
|
|
if (method->has_abstract && method->IsFactoryOrConstructor()) {
|
|
ReportError(method->name_pos, "constructor cannot be abstract");
|
|
}
|
|
if (method->has_const && method->IsConstructor()) {
|
|
current_class().set_is_const();
|
|
}
|
|
|
|
// Parse the formal parameters.
|
|
const bool are_implicitly_final = method->has_const;
|
|
const bool allow_explicit_default_values = true;
|
|
const intptr_t formal_param_pos = TokenPos();
|
|
method->params.Clear();
|
|
// Static functions do not have a receiver.
|
|
// The first parameter of a factory is the TypeArguments vector of
|
|
// the type of the instance to be allocated.
|
|
if (!method->has_static || method->IsConstructor()) {
|
|
method->params.AddReceiver(ReceiverType(current_class()), formal_param_pos);
|
|
} else if (method->IsFactory()) {
|
|
method->params.AddFinalParameter(
|
|
formal_param_pos,
|
|
&Symbols::TypeArgumentsParameter(),
|
|
&Type::ZoneHandle(Z, Type::DynamicType()));
|
|
}
|
|
// Constructors have an implicit parameter for the construction phase.
|
|
if (method->IsConstructor()) {
|
|
method->params.AddFinalParameter(
|
|
TokenPos(),
|
|
&Symbols::PhaseParameter(),
|
|
&Type::ZoneHandle(Z, Type::SmiType()));
|
|
}
|
|
if (are_implicitly_final) {
|
|
method->params.SetImplicitlyFinal();
|
|
}
|
|
if (!method->IsGetter()) {
|
|
ParseFormalParameterList(allow_explicit_default_values,
|
|
false,
|
|
&method->params);
|
|
}
|
|
|
|
// Now that we know the parameter list, we can distinguish between the
|
|
// unary and binary operator -.
|
|
if (method->has_operator) {
|
|
if ((method->operator_token == Token::kSUB) &&
|
|
(method->params.num_fixed_parameters == 1)) {
|
|
// Patch up name for unary operator - so it does not clash with the
|
|
// name for binary operator -.
|
|
method->operator_token = Token::kNEGATE;
|
|
*method->name = Symbols::New(Token::Str(Token::kNEGATE));
|
|
}
|
|
CheckOperatorArity(*method);
|
|
}
|
|
|
|
// Mangle the name for getter and setter functions and check function
|
|
// arity.
|
|
if (method->IsGetter() || method->IsSetter()) {
|
|
int expected_num_parameters = 0;
|
|
if (method->IsGetter()) {
|
|
expected_num_parameters = (method->has_static) ? 0 : 1;
|
|
method->dict_name = method->name;
|
|
method->name = &String::ZoneHandle(Z, Field::GetterSymbol(*method->name));
|
|
} else {
|
|
ASSERT(method->IsSetter());
|
|
expected_num_parameters = (method->has_static) ? 1 : 2;
|
|
method->dict_name = &String::ZoneHandle(Z,
|
|
String::Concat(*method->name, Symbols::Equals()));
|
|
method->name = &String::ZoneHandle(Z, Field::SetterSymbol(*method->name));
|
|
}
|
|
if ((method->params.num_fixed_parameters != expected_num_parameters) ||
|
|
(method->params.num_optional_parameters != 0)) {
|
|
ReportError(method->name_pos, "illegal %s parameters",
|
|
method->IsGetter() ? "getter" : "setter");
|
|
}
|
|
}
|
|
|
|
// Parse redirecting factory constructor.
|
|
Type& redirection_type = Type::Handle(Z);
|
|
String& redirection_identifier = String::Handle(Z);
|
|
bool is_redirecting = false;
|
|
if (method->IsFactory() && (CurrentToken() == Token::kASSIGN)) {
|
|
// Default parameter values are disallowed in redirecting factories.
|
|
if (method->params.has_explicit_default_values) {
|
|
ReportError("redirecting factory '%s' may not specify default values "
|
|
"for optional parameters",
|
|
method->name->ToCString());
|
|
}
|
|
if (method->has_external) {
|
|
ReportError(TokenPos(),
|
|
"external factory constructor '%s' may not have redirection",
|
|
method->name->ToCString());
|
|
}
|
|
ConsumeToken();
|
|
const intptr_t type_pos = TokenPos();
|
|
is_redirecting = true;
|
|
const bool consume_unresolved_prefix =
|
|
(LookaheadToken(3) == Token::kLT) ||
|
|
(LookaheadToken(3) == Token::kPERIOD);
|
|
const AbstractType& type = AbstractType::Handle(Z,
|
|
ParseType(ClassFinalizer::kResolveTypeParameters,
|
|
false, // Deferred types not allowed.
|
|
consume_unresolved_prefix));
|
|
if (!type.IsMalformed() && type.IsTypeParameter()) {
|
|
// Replace the type with a malformed type and compile a throw when called.
|
|
redirection_type = ClassFinalizer::NewFinalizedMalformedType(
|
|
Error::Handle(Z), // No previous error.
|
|
script_,
|
|
type_pos,
|
|
"factory '%s' may not redirect to type parameter '%s'",
|
|
method->name->ToCString(),
|
|
String::Handle(Z, type.UserVisibleName()).ToCString());
|
|
} else {
|
|
// We handle malformed and malbounded redirection type at run time.
|
|
redirection_type ^= type.raw();
|
|
}
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
// Named constructor or factory.
|
|
ConsumeToken();
|
|
redirection_identifier = ExpectIdentifier("identifier expected")->raw();
|
|
}
|
|
} else if (CurrentToken() == Token::kCOLON) {
|
|
// Parse initializers.
|
|
if (!method->IsConstructor()) {
|
|
ReportError("initializers only allowed on constructors");
|
|
}
|
|
if (method->has_external) {
|
|
ReportError(TokenPos(),
|
|
"external constructor '%s' may not have initializers",
|
|
method->name->ToCString());
|
|
}
|
|
if ((LookaheadToken(1) == Token::kTHIS) &&
|
|
((LookaheadToken(2) == Token::kLPAREN) ||
|
|
LookaheadToken(4) == Token::kLPAREN)) {
|
|
// Redirected constructor: either this(...) or this.xxx(...).
|
|
is_redirecting = true;
|
|
if (method->params.has_field_initializer) {
|
|
// Constructors that redirect to another constructor must not
|
|
// initialize any fields using field initializer parameters.
|
|
ReportError(formal_param_pos, "Redirecting constructor "
|
|
"may not use field initializer parameters");
|
|
}
|
|
ConsumeToken(); // Colon.
|
|
ExpectToken(Token::kTHIS);
|
|
String& redir_name = String::ZoneHandle(Z,
|
|
String::Concat(members->class_name(), Symbols::Dot()));
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
redir_name = String::Concat(redir_name,
|
|
*ExpectIdentifier("constructor name expected"));
|
|
}
|
|
method->redirect_name = &redir_name;
|
|
CheckToken(Token::kLPAREN);
|
|
SkipToMatchingParenthesis();
|
|
} else {
|
|
SkipInitializers();
|
|
}
|
|
}
|
|
|
|
// Only constructors can redirect to another method.
|
|
ASSERT((method->redirect_name == NULL) || method->IsConstructor());
|
|
|
|
if (method->IsConstructor() &&
|
|
method->has_external &&
|
|
method->params.has_field_initializer) {
|
|
ReportError(method->name_pos,
|
|
"external constructor '%s' may not have field initializers",
|
|
method->name->ToCString());
|
|
}
|
|
|
|
RawFunction::AsyncModifier async_modifier = ParseFunctionModifier();
|
|
if ((method->IsFactoryOrConstructor() || method->IsSetter()) &&
|
|
async_modifier != RawFunction::kNoModifier) {
|
|
ReportError(method->name_pos,
|
|
"%s '%s' may not be async",
|
|
(method->IsSetter()) ? "setter" : "constructor",
|
|
method->name->ToCString());
|
|
}
|
|
|
|
intptr_t method_end_pos = TokenPos();
|
|
if ((CurrentToken() == Token::kLBRACE) ||
|
|
(CurrentToken() == Token::kARROW)) {
|
|
if (method->has_abstract) {
|
|
ReportError(TokenPos(),
|
|
"abstract method '%s' may not have a function body",
|
|
method->name->ToCString());
|
|
} else if (method->has_external) {
|
|
ReportError(TokenPos(),
|
|
"external %s '%s' may not have a function body",
|
|
method->IsFactoryOrConstructor() ? "constructor" : "method",
|
|
method->name->ToCString());
|
|
} else if (method->IsConstructor() && method->has_const) {
|
|
ReportError(TokenPos(),
|
|
"const constructor '%s' may not have a function body",
|
|
method->name->ToCString());
|
|
} else if (method->IsFactory() && method->has_const) {
|
|
ReportError(TokenPos(),
|
|
"const factory '%s' may not have a function body",
|
|
method->name->ToCString());
|
|
}
|
|
if (method->redirect_name != NULL) {
|
|
ReportError(method->name_pos,
|
|
"Constructor with redirection may not have a function body");
|
|
}
|
|
if (CurrentToken() == Token::kLBRACE) {
|
|
SkipBlock();
|
|
method_end_pos = TokenPos();
|
|
ExpectToken(Token::kRBRACE);
|
|
} else {
|
|
ConsumeToken();
|
|
BoolScope allow_await(&this->await_is_keyword_,
|
|
async_modifier != RawFunction::kNoModifier);
|
|
SkipExpr();
|
|
method_end_pos = TokenPos();
|
|
ExpectSemicolon();
|
|
}
|
|
} else if (IsLiteral("native")) {
|
|
if (method->has_abstract) {
|
|
ReportError(method->name_pos,
|
|
"abstract method '%s' may not have a function body",
|
|
method->name->ToCString());
|
|
} else if (method->IsConstructor() && method->has_const) {
|
|
ReportError(method->name_pos,
|
|
"const constructor '%s' may not be native",
|
|
method->name->ToCString());
|
|
}
|
|
if (method->redirect_name != NULL) {
|
|
ReportError(method->name_pos,
|
|
"Constructor with redirection may not have a function body");
|
|
}
|
|
ParseNativeDeclaration();
|
|
method_end_pos = TokenPos();
|
|
ExpectSemicolon();
|
|
method->has_native = true;
|
|
} else {
|
|
// We haven't found a method body. Issue error if one is required.
|
|
const bool must_have_body =
|
|
method->has_static &&
|
|
!method->has_external &&
|
|
redirection_type.IsNull();
|
|
if (must_have_body) {
|
|
ReportError(method->name_pos,
|
|
"function body expected for method '%s'",
|
|
method->name->ToCString());
|
|
}
|
|
|
|
if (CurrentToken() == Token::kSEMICOLON) {
|
|
ConsumeToken();
|
|
if (!method->has_static &&
|
|
!method->has_external &&
|
|
!method->IsConstructor()) {
|
|
// Methods, getters and setters without a body are
|
|
// implicitly abstract.
|
|
method->has_abstract = true;
|
|
}
|
|
} else {
|
|
// Signature is not followed by semicolon or body. Issue an
|
|
// appropriate error.
|
|
const bool must_have_semicolon =
|
|
(method->redirect_name != NULL) ||
|
|
(method->IsConstructor() && method->has_const) ||
|
|
method->has_external;
|
|
if (must_have_semicolon) {
|
|
ExpectSemicolon();
|
|
} else {
|
|
ReportError(method->name_pos,
|
|
"function body or semicolon expected for method '%s'",
|
|
method->name->ToCString());
|
|
}
|
|
}
|
|
}
|
|
|
|
RawFunction::Kind function_kind;
|
|
if (method->IsFactoryOrConstructor()) {
|
|
function_kind = RawFunction::kConstructor;
|
|
} else if (method->IsGetter()) {
|
|
function_kind = RawFunction::kGetterFunction;
|
|
} else if (method->IsSetter()) {
|
|
function_kind = RawFunction::kSetterFunction;
|
|
} else {
|
|
function_kind = RawFunction::kRegularFunction;
|
|
}
|
|
Function& func = Function::Handle(Z,
|
|
Function::New(*method->name,
|
|
function_kind,
|
|
method->has_static,
|
|
method->has_const,
|
|
method->has_abstract,
|
|
method->has_external,
|
|
method->has_native,
|
|
current_class(),
|
|
method->decl_begin_pos));
|
|
func.set_result_type(*method->type);
|
|
func.set_end_token_pos(method_end_pos);
|
|
func.set_is_redirecting(is_redirecting);
|
|
func.set_modifier(async_modifier);
|
|
if (library_.is_dart_scheme() && library_.IsPrivate(*method->name)) {
|
|
func.set_is_reflectable(false);
|
|
}
|
|
if (method->metadata_pos > 0) {
|
|
library_.AddFunctionMetadata(func, method->metadata_pos);
|
|
}
|
|
|
|
// If this method is a redirecting factory, set the redirection information.
|
|
if (!redirection_type.IsNull()) {
|
|
ASSERT(func.IsFactory());
|
|
func.SetRedirectionType(redirection_type);
|
|
if (!redirection_identifier.IsNull()) {
|
|
func.SetRedirectionIdentifier(redirection_identifier);
|
|
}
|
|
}
|
|
|
|
// No need to resolve parameter types yet, or add parameters to local scope.
|
|
ASSERT(is_top_level_);
|
|
AddFormalParamsToFunction(&method->params, func);
|
|
members->AddFunction(func);
|
|
}
|
|
|
|
|
|
void Parser::ParseFieldDefinition(ClassDesc* members, MemberDesc* field) {
|
|
TRACE_PARSER("ParseFieldDefinition");
|
|
// The parser has read the first field name and is now at the token
|
|
// after the field name.
|
|
ASSERT(CurrentToken() == Token::kSEMICOLON ||
|
|
CurrentToken() == Token::kCOMMA ||
|
|
CurrentToken() == Token::kASSIGN);
|
|
ASSERT(field->type != NULL);
|
|
ASSERT(field->name_pos > 0);
|
|
ASSERT(current_member_ == field);
|
|
// All const fields are also final.
|
|
ASSERT(!field->has_const || field->has_final);
|
|
|
|
if (field->has_abstract) {
|
|
ReportError("keyword 'abstract' not allowed in field declaration");
|
|
}
|
|
if (field->has_external) {
|
|
ReportError("keyword 'external' not allowed in field declaration");
|
|
}
|
|
if (field->has_factory) {
|
|
ReportError("keyword 'factory' not allowed in field declaration");
|
|
}
|
|
if (!field->has_static && field->has_const) {
|
|
ReportError(field->name_pos, "instance field may not be 'const'");
|
|
}
|
|
Function& getter = Function::Handle(Z);
|
|
Function& setter = Function::Handle(Z);
|
|
Field& class_field = Field::ZoneHandle(Z);
|
|
Instance& init_value = Instance::Handle(Z);
|
|
while (true) {
|
|
bool has_initializer = CurrentToken() == Token::kASSIGN;
|
|
bool has_simple_literal = false;
|
|
if (has_initializer) {
|
|
ConsumeToken();
|
|
init_value = Object::sentinel().raw();
|
|
// For static fields, the initialization expression will be parsed
|
|
// through the kImplicitStaticFinalGetter method invocation/compilation.
|
|
// For instance fields, the expression is parsed when a constructor
|
|
// is compiled.
|
|
// For static fields with very simple initializer expressions
|
|
// (e.g. a literal number or string), we optimize away the
|
|
// kImplicitStaticFinalGetter and initialize the field here.
|
|
// However, the class finalizer will check the value type for
|
|
// assignability once the declared field type can be resolved. If the
|
|
// value is not assignable (assuming checked mode and disregarding actual
|
|
// mode), the field value is reset and a kImplicitStaticFinalGetter is
|
|
// created at finalization time.
|
|
if (LookaheadToken(1) == Token::kSEMICOLON) {
|
|
has_simple_literal = IsSimpleLiteral(*field->type, &init_value);
|
|
}
|
|
SkipExpr();
|
|
} else {
|
|
// Static const and static final fields must have an initializer.
|
|
// Static const fields are implicitly final.
|
|
if (field->has_static && field->has_final) {
|
|
ReportError(field->name_pos,
|
|
"static %s field '%s' must have an initializer expression",
|
|
field->has_const ? "const" : "final",
|
|
field->name->ToCString());
|
|
}
|
|
}
|
|
|
|
// Create the field object.
|
|
class_field = Field::New(*field->name,
|
|
field->has_static,
|
|
field->has_final,
|
|
field->has_const,
|
|
false, // Not synthetic.
|
|
current_class(),
|
|
field->name_pos);
|
|
class_field.set_type(*field->type);
|
|
class_field.set_has_initializer(has_initializer);
|
|
members->AddField(class_field);
|
|
field->field_ = &class_field;
|
|
if (field->metadata_pos >= 0) {
|
|
library_.AddFieldMetadata(class_field, field->metadata_pos);
|
|
}
|
|
|
|
// Start tracking types for fields with simple initializers in their
|
|
// definition. This avoids some of the overhead to track this at runtime
|
|
// and rules out many fields from being unnecessary unboxing candidates.
|
|
if (!field->has_static && has_initializer && has_simple_literal) {
|
|
class_field.RecordStore(init_value);
|
|
}
|
|
|
|
// For static final fields (this includes static const fields), set value to
|
|
// "uninitialized" and create a kImplicitStaticFinalGetter getter method.
|
|
if (field->has_static && has_initializer) {
|
|
class_field.set_value(init_value);
|
|
if (!has_simple_literal) {
|
|
String& getter_name = String::Handle(Z,
|
|
Field::GetterSymbol(*field->name));
|
|
getter = Function::New(getter_name,
|
|
RawFunction::kImplicitStaticFinalGetter,
|
|
field->has_static,
|
|
field->has_const,
|
|
/* is_abstract = */ false,
|
|
/* is_external = */ false,
|
|
/* is_native = */ false,
|
|
current_class(),
|
|
field->name_pos);
|
|
getter.set_result_type(*field->type);
|
|
getter.set_is_debuggable(false);
|
|
if (library_.is_dart_scheme() && library_.IsPrivate(*field->name)) {
|
|
getter.set_is_reflectable(false);
|
|
}
|
|
members->AddFunction(getter);
|
|
}
|
|
}
|
|
|
|
// For instance fields, we create implicit getter and setter methods.
|
|
if (!field->has_static) {
|
|
String& getter_name = String::Handle(Z,
|
|
Field::GetterSymbol(*field->name));
|
|
getter = Function::New(getter_name, RawFunction::kImplicitGetter,
|
|
field->has_static,
|
|
field->has_final,
|
|
/* is_abstract = */ false,
|
|
/* is_external = */ false,
|
|
/* is_native = */ false,
|
|
current_class(),
|
|
field->name_pos);
|
|
ParamList params;
|
|
ASSERT(current_class().raw() == getter.Owner());
|
|
params.AddReceiver(ReceiverType(current_class()), field->name_pos);
|
|
getter.set_result_type(*field->type);
|
|
getter.set_is_debuggable(false);
|
|
AddFormalParamsToFunction(¶ms, getter);
|
|
members->AddFunction(getter);
|
|
if (!field->has_final) {
|
|
// Build a setter accessor for non-const fields.
|
|
String& setter_name = String::Handle(Z,
|
|
Field::SetterSymbol(*field->name));
|
|
setter = Function::New(setter_name, RawFunction::kImplicitSetter,
|
|
field->has_static,
|
|
field->has_final,
|
|
/* is_abstract = */ false,
|
|
/* is_external = */ false,
|
|
/* is_native = */ false,
|
|
current_class(),
|
|
field->name_pos);
|
|
ParamList params;
|
|
ASSERT(current_class().raw() == setter.Owner());
|
|
params.AddReceiver(ReceiverType(current_class()), field->name_pos);
|
|
params.AddFinalParameter(TokenPos(),
|
|
&Symbols::Value(),
|
|
field->type);
|
|
setter.set_result_type(Type::Handle(Z, Type::VoidType()));
|
|
setter.set_is_debuggable(false);
|
|
if (library_.is_dart_scheme() && library_.IsPrivate(*field->name)) {
|
|
setter.set_is_reflectable(false);
|
|
}
|
|
AddFormalParamsToFunction(¶ms, setter);
|
|
members->AddFunction(setter);
|
|
}
|
|
}
|
|
|
|
if (CurrentToken() != Token::kCOMMA) {
|
|
break;
|
|
}
|
|
ConsumeToken();
|
|
field->name_pos = this->TokenPos();
|
|
field->name = ExpectIdentifier("field name expected");
|
|
}
|
|
ExpectSemicolon();
|
|
}
|
|
|
|
|
|
void Parser::CheckOperatorArity(const MemberDesc& member) {
|
|
intptr_t expected_num_parameters; // Includes receiver.
|
|
Token::Kind op = member.operator_token;
|
|
if (op == Token::kASSIGN_INDEX) {
|
|
expected_num_parameters = 3;
|
|
} else if ((op == Token::kBIT_NOT) || (op == Token::kNEGATE)) {
|
|
expected_num_parameters = 1;
|
|
} else {
|
|
expected_num_parameters = 2;
|
|
}
|
|
if ((member.params.num_optional_parameters > 0) ||
|
|
member.params.has_optional_positional_parameters ||
|
|
member.params.has_optional_named_parameters ||
|
|
(member.params.num_fixed_parameters != expected_num_parameters)) {
|
|
// Subtract receiver when reporting number of expected arguments.
|
|
ReportError(member.name_pos, "operator %s expects %" Pd " argument(s)",
|
|
member.name->ToCString(), (expected_num_parameters - 1));
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::CheckMemberNameConflict(ClassDesc* members,
|
|
MemberDesc* member) {
|
|
const String& name = *member->DictName();
|
|
if (name.Equals(members->class_name())) {
|
|
ReportError(member->name_pos,
|
|
"%s '%s' conflicts with class name",
|
|
member->ToCString(),
|
|
name.ToCString());
|
|
}
|
|
if (members->clazz().LookupTypeParameter(name) != TypeParameter::null()) {
|
|
ReportError(member->name_pos,
|
|
"%s '%s' conflicts with type parameter",
|
|
member->ToCString(),
|
|
name.ToCString());
|
|
}
|
|
for (int i = 0; i < members->members().length(); i++) {
|
|
MemberDesc* existing_member = &members->members()[i];
|
|
if (name.Equals(*existing_member->DictName())) {
|
|
ReportError(member->name_pos,
|
|
"%s '%s' conflicts with previously declared %s",
|
|
member->ToCString(),
|
|
name.ToCString(),
|
|
existing_member->ToCString());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::ParseClassMemberDefinition(ClassDesc* members,
|
|
intptr_t metadata_pos) {
|
|
TRACE_PARSER("ParseClassMemberDefinition");
|
|
MemberDesc member;
|
|
current_member_ = &member;
|
|
member.metadata_pos = metadata_pos;
|
|
member.decl_begin_pos = TokenPos();
|
|
if ((CurrentToken() == Token::kEXTERNAL) &&
|
|
(LookaheadToken(1) != Token::kLPAREN)) {
|
|
ConsumeToken();
|
|
member.has_external = true;
|
|
}
|
|
if ((CurrentToken() == Token::kSTATIC) &&
|
|
(LookaheadToken(1) != Token::kLPAREN)) {
|
|
ConsumeToken();
|
|
member.has_static = true;
|
|
}
|
|
if (CurrentToken() == Token::kCONST) {
|
|
ConsumeToken();
|
|
member.has_const = true;
|
|
} else if (CurrentToken() == Token::kFINAL) {
|
|
ConsumeToken();
|
|
member.has_final = true;
|
|
}
|
|
if (CurrentToken() == Token::kVAR) {
|
|
if (member.has_const) {
|
|
ReportError("identifier expected after 'const'");
|
|
}
|
|
if (member.has_final) {
|
|
ReportError("identifier expected after 'final'");
|
|
}
|
|
ConsumeToken();
|
|
member.has_var = true;
|
|
// The member type is the 'dynamic' type.
|
|
member.type = &Type::ZoneHandle(Z, Type::DynamicType());
|
|
} else if (CurrentToken() == Token::kFACTORY) {
|
|
ConsumeToken();
|
|
if (member.has_static) {
|
|
ReportError("factory method cannot be explicitly marked static");
|
|
}
|
|
member.has_factory = true;
|
|
member.has_static = true;
|
|
// The result type depends on the name of the factory method.
|
|
}
|
|
// Optionally parse a type.
|
|
if (CurrentToken() == Token::kVOID) {
|
|
if (member.has_var || member.has_factory) {
|
|
ReportError("void not expected");
|
|
}
|
|
ConsumeToken();
|
|
ASSERT(member.type == NULL);
|
|
member.type = &Type::ZoneHandle(Z, Type::VoidType());
|
|
} else if (CurrentToken() == Token::kIDENT) {
|
|
// This is either a type name or the name of a method/constructor/field.
|
|
if ((member.type == NULL) && !member.has_factory) {
|
|
// We have not seen a member type yet, so we check if the next
|
|
// identifier could represent a type before parsing it.
|
|
Token::Kind follower = LookaheadToken(1);
|
|
// We have an identifier followed by a 'follower' token.
|
|
// We either parse a type or assume that no type is specified.
|
|
if ((follower == Token::kLT) || // Parameterized type.
|
|
(follower == Token::kGET) || // Getter following a type.
|
|
(follower == Token::kSET) || // Setter following a type.
|
|
(follower == Token::kOPERATOR) || // Operator following a type.
|
|
(Token::IsIdentifier(follower)) || // Member name following a type.
|
|
((follower == Token::kPERIOD) && // Qualified class name of type,
|
|
(LookaheadToken(3) != Token::kLPAREN))) { // but not a named constr.
|
|
ASSERT(is_top_level_);
|
|
// The declared type of fields is never ignored, even in unchecked mode,
|
|
// because getters and setters could be closurized at some time (not
|
|
// supported yet).
|
|
member.type = &AbstractType::ZoneHandle(Z,
|
|
ParseType(ClassFinalizer::kResolveTypeParameters));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Optionally parse a (possibly named) constructor name or factory.
|
|
if (IsIdentifier() &&
|
|
(CurrentLiteral()->Equals(members->class_name()) || member.has_factory)) {
|
|
member.name_pos = TokenPos();
|
|
member.name = CurrentLiteral(); // Unqualified identifier.
|
|
ConsumeToken();
|
|
if (member.has_factory) {
|
|
// The factory name may be qualified, but the first identifier must match
|
|
// the name of the immediately enclosing class.
|
|
if (!member.name->Equals(members->class_name())) {
|
|
ReportError(member.name_pos, "factory name must be '%s'",
|
|
members->class_name().ToCString());
|
|
}
|
|
} else if (member.has_static) {
|
|
ReportError(member.name_pos, "constructor cannot be static");
|
|
}
|
|
if (member.type != NULL) {
|
|
ReportError(member.name_pos, "constructor must not specify return type");
|
|
}
|
|
// Do not bypass class resolution by using current_class() directly, since
|
|
// it may be a patch class.
|
|
const Object& result_type_class = Object::Handle(Z,
|
|
UnresolvedClass::New(LibraryPrefix::Handle(Z),
|
|
*member.name,
|
|
member.name_pos));
|
|
// The type arguments of the result type are the type parameters of the
|
|
// current class. Note that in the case of a patch class, they are copied
|
|
// from the class being patched.
|
|
member.type = &Type::ZoneHandle(Z, Type::New(
|
|
result_type_class,
|
|
TypeArguments::Handle(Z, current_class().type_parameters()),
|
|
member.name_pos));
|
|
|
|
// We must be dealing with a constructor or named constructor.
|
|
member.kind = RawFunction::kConstructor;
|
|
*member.name = String::Concat(*member.name, Symbols::Dot());
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
// Named constructor.
|
|
ConsumeToken();
|
|
member.dict_name = ExpectIdentifier("identifier expected");
|
|
*member.name = String::Concat(*member.name, *member.dict_name);
|
|
}
|
|
// Ensure that names are symbols.
|
|
*member.name = Symbols::New(*member.name);
|
|
|
|
CheckToken(Token::kLPAREN);
|
|
} else if ((CurrentToken() == Token::kGET) && !member.has_var &&
|
|
(LookaheadToken(1) != Token::kLPAREN) &&
|
|
(LookaheadToken(1) != Token::kASSIGN) &&
|
|
(LookaheadToken(1) != Token::kCOMMA) &&
|
|
(LookaheadToken(1) != Token::kSEMICOLON)) {
|
|
ConsumeToken();
|
|
member.kind = RawFunction::kGetterFunction;
|
|
member.name_pos = this->TokenPos();
|
|
member.name = ExpectIdentifier("identifier expected");
|
|
// If the result type was not specified, it will be set to DynamicType.
|
|
} else if ((CurrentToken() == Token::kSET) && !member.has_var &&
|
|
(LookaheadToken(1) != Token::kLPAREN) &&
|
|
(LookaheadToken(1) != Token::kASSIGN) &&
|
|
(LookaheadToken(1) != Token::kCOMMA) &&
|
|
(LookaheadToken(1) != Token::kSEMICOLON)) {
|
|
ConsumeToken();
|
|
member.kind = RawFunction::kSetterFunction;
|
|
member.name_pos = this->TokenPos();
|
|
member.name = ExpectIdentifier("identifier expected");
|
|
CheckToken(Token::kLPAREN);
|
|
// The grammar allows a return type, so member.type is not always NULL here.
|
|
// If no return type is specified, the return type of the setter is dynamic.
|
|
if (member.type == NULL) {
|
|
member.type = &Type::ZoneHandle(Z, Type::DynamicType());
|
|
}
|
|
} else if ((CurrentToken() == Token::kOPERATOR) && !member.has_var &&
|
|
(LookaheadToken(1) != Token::kLPAREN) &&
|
|
(LookaheadToken(1) != Token::kASSIGN) &&
|
|
(LookaheadToken(1) != Token::kCOMMA) &&
|
|
(LookaheadToken(1) != Token::kSEMICOLON)) {
|
|
ConsumeToken();
|
|
if (!Token::CanBeOverloaded(CurrentToken())) {
|
|
ReportError("invalid operator overloading");
|
|
}
|
|
if (member.has_static) {
|
|
ReportError("operator overloading functions cannot be static");
|
|
}
|
|
member.operator_token = CurrentToken();
|
|
member.has_operator = true;
|
|
member.kind = RawFunction::kRegularFunction;
|
|
member.name_pos = this->TokenPos();
|
|
member.name =
|
|
&String::ZoneHandle(Z, Symbols::New(Token::Str(member.operator_token)));
|
|
ConsumeToken();
|
|
} else if (IsIdentifier()) {
|
|
member.name = CurrentLiteral();
|
|
member.name_pos = TokenPos();
|
|
ConsumeToken();
|
|
} else {
|
|
ReportError("identifier expected");
|
|
}
|
|
|
|
ASSERT(member.name != NULL);
|
|
if (CurrentToken() == Token::kLPAREN || member.IsGetter()) {
|
|
// Constructor or method.
|
|
if (member.type == NULL) {
|
|
member.type = &Type::ZoneHandle(Z, Type::DynamicType());
|
|
}
|
|
ASSERT(member.IsFactory() == member.has_factory);
|
|
ParseMethodOrConstructor(members, &member);
|
|
} else if (CurrentToken() == Token::kSEMICOLON ||
|
|
CurrentToken() == Token::kCOMMA ||
|
|
CurrentToken() == Token::kASSIGN) {
|
|
// Field definition.
|
|
if (member.has_const) {
|
|
// const fields are implicitly final.
|
|
member.has_final = true;
|
|
}
|
|
if (member.type == NULL) {
|
|
if (member.has_final) {
|
|
member.type = &Type::ZoneHandle(Z, Type::DynamicType());
|
|
} else {
|
|
ReportError("missing 'var', 'final', 'const' or type"
|
|
" in field declaration");
|
|
}
|
|
}
|
|
ParseFieldDefinition(members, &member);
|
|
} else {
|
|
UnexpectedToken();
|
|
}
|
|
current_member_ = NULL;
|
|
CheckMemberNameConflict(members, &member);
|
|
members->AddMember(member);
|
|
}
|
|
|
|
|
|
void Parser::ParseEnumDeclaration(const GrowableObjectArray& pending_classes,
|
|
const Class& toplevel_class,
|
|
intptr_t metadata_pos) {
|
|
TRACE_PARSER("ParseEnumDeclaration");
|
|
ConsumeToken();
|
|
const intptr_t enum_pos = TokenPos();
|
|
String* enum_name =
|
|
ExpectUserDefinedTypeIdentifier("enum type name expected");
|
|
if (FLAG_trace_parser) {
|
|
OS::Print("TopLevel parsing enum '%s'\n", enum_name->ToCString());
|
|
}
|
|
ExpectToken(Token::kLBRACE);
|
|
if (!IsIdentifier()) {
|
|
ReportError("Enumeration must have at least one name");
|
|
}
|
|
while (IsIdentifier()) {
|
|
ConsumeToken();
|
|
if (CurrentToken() == Token::kCOMMA) {
|
|
ConsumeToken();
|
|
if (CurrentToken() == Token::kRBRACE) {
|
|
break;
|
|
}
|
|
} else if (CurrentToken() == Token::kRBRACE) {
|
|
break;
|
|
} else {
|
|
ReportError(", or } expected");
|
|
}
|
|
}
|
|
ExpectToken(Token::kRBRACE);
|
|
|
|
Object& obj = Object::Handle(Z, library_.LookupLocalObject(*enum_name));
|
|
if (!obj.IsNull()) {
|
|
ReportError(enum_pos, "'%s' is already defined", enum_name->ToCString());
|
|
}
|
|
Class& cls = Class::Handle(Z);
|
|
cls = Class::New(*enum_name, script_, enum_pos);
|
|
cls.set_library(library_);
|
|
library_.AddClass(cls);
|
|
cls.set_is_synthesized_class();
|
|
cls.set_is_enum_class();
|
|
if (metadata_pos >= 0) {
|
|
library_.AddClassMetadata(cls, toplevel_class, metadata_pos);
|
|
}
|
|
cls.set_super_type(Type::Handle(Z, Type::ObjectType()));
|
|
pending_classes.Add(cls, Heap::kOld);
|
|
}
|
|
|
|
|
|
void Parser::ParseClassDeclaration(const GrowableObjectArray& pending_classes,
|
|
const Class& toplevel_class,
|
|
intptr_t metadata_pos) {
|
|
TRACE_PARSER("ParseClassDeclaration");
|
|
bool is_patch = false;
|
|
bool is_abstract = false;
|
|
if (is_patch_source() &&
|
|
(CurrentToken() == Token::kIDENT) &&
|
|
CurrentLiteral()->Equals("patch")) {
|
|
ConsumeToken();
|
|
is_patch = true;
|
|
} else if (CurrentToken() == Token::kABSTRACT) {
|
|
is_abstract = true;
|
|
ConsumeToken();
|
|
}
|
|
ExpectToken(Token::kCLASS);
|
|
const intptr_t classname_pos = TokenPos();
|
|
String& class_name = *ExpectUserDefinedTypeIdentifier("class name expected");
|
|
if (FLAG_trace_parser) {
|
|
OS::Print("TopLevel parsing class '%s'\n", class_name.ToCString());
|
|
}
|
|
Class& cls = Class::Handle(Z);
|
|
TypeArguments& orig_type_parameters = TypeArguments::Handle(Z);
|
|
Object& obj = Object::Handle(Z, library_.LookupLocalObject(class_name));
|
|
if (obj.IsNull()) {
|
|
if (is_patch) {
|
|
ReportError(classname_pos, "missing class '%s' cannot be patched",
|
|
class_name.ToCString());
|
|
}
|
|
cls = Class::New(class_name, script_, classname_pos);
|
|
library_.AddClass(cls);
|
|
} else {
|
|
if (!obj.IsClass()) {
|
|
ReportError(classname_pos, "'%s' is already defined",
|
|
class_name.ToCString());
|
|
}
|
|
cls ^= obj.raw();
|
|
if (is_patch) {
|
|
// Preserve and reuse the original type parameters and bounds since the
|
|
// ones defined in the patch class will not be finalized.
|
|
orig_type_parameters = cls.type_parameters();
|
|
// A patch class must be given the same name as the class it is patching,
|
|
// otherwise the generic signature classes it defines will not match the
|
|
// patched generic signature classes. Therefore, new signature classes
|
|
// will be introduced and the original ones will not get finalized.
|
|
cls = Class::New(class_name, script_, classname_pos);
|
|
cls.set_library(library_);
|
|
} else {
|
|
// Not patching a class, but it has been found. This must be one of the
|
|
// pre-registered classes from object.cc or a duplicate definition.
|
|
if (!(cls.is_prefinalized() ||
|
|
RawObject::IsImplicitFieldClassId(cls.id()))) {
|
|
ReportError(classname_pos, "class '%s' is already defined",
|
|
class_name.ToCString());
|
|
}
|
|
// Pre-registered classes need their scripts connected at this time.
|
|
cls.set_script(script_);
|
|
cls.set_token_pos(classname_pos);
|
|
}
|
|
}
|
|
ASSERT(!cls.IsNull());
|
|
ASSERT(cls.functions() == Object::empty_array().raw());
|
|
set_current_class(cls);
|
|
ParseTypeParameters(cls);
|
|
if (is_patch) {
|
|
// Check that the new type parameters are identical to the original ones.
|
|
const TypeArguments& new_type_parameters =
|
|
TypeArguments::Handle(Z, cls.type_parameters());
|
|
const int new_type_params_count =
|
|
new_type_parameters.IsNull() ? 0 : new_type_parameters.Length();
|
|
const int orig_type_params_count =
|
|
orig_type_parameters.IsNull() ? 0 : orig_type_parameters.Length();
|
|
if (new_type_params_count != orig_type_params_count) {
|
|
ReportError(classname_pos,
|
|
"class '%s' must be patched with identical type parameters",
|
|
class_name.ToCString());
|
|
}
|
|
TypeParameter& new_type_param = TypeParameter::Handle(Z);
|
|
TypeParameter& orig_type_param = TypeParameter::Handle(Z);
|
|
String& new_name = String::Handle(Z);
|
|
String& orig_name = String::Handle(Z);
|
|
AbstractType& new_bound = AbstractType::Handle(Z);
|
|
AbstractType& orig_bound = AbstractType::Handle(Z);
|
|
for (int i = 0; i < new_type_params_count; i++) {
|
|
new_type_param ^= new_type_parameters.TypeAt(i);
|
|
orig_type_param ^= orig_type_parameters.TypeAt(i);
|
|
new_name = new_type_param.name();
|
|
orig_name = orig_type_param.name();
|
|
if (!new_name.Equals(orig_name)) {
|
|
ReportError(new_type_param.token_pos(),
|
|
"type parameter '%s' of patch class '%s' does not match "
|
|
"original type parameter '%s'",
|
|
new_name.ToCString(),
|
|
class_name.ToCString(),
|
|
orig_name.ToCString());
|
|
}
|
|
new_bound = new_type_param.bound();
|
|
orig_bound = orig_type_param.bound();
|
|
if (!new_bound.Equals(orig_bound)) {
|
|
ReportError(new_type_param.token_pos(),
|
|
"bound '%s' of type parameter '%s' of patch class '%s' "
|
|
"does not match original type parameter bound '%s'",
|
|
String::Handle(new_bound.UserVisibleName()).ToCString(),
|
|
new_name.ToCString(),
|
|
class_name.ToCString(),
|
|
String::Handle(orig_bound.UserVisibleName()).ToCString());
|
|
}
|
|
}
|
|
cls.set_type_parameters(orig_type_parameters);
|
|
}
|
|
|
|
if (is_abstract) {
|
|
cls.set_is_abstract();
|
|
}
|
|
if (metadata_pos >= 0) {
|
|
library_.AddClassMetadata(cls, toplevel_class, metadata_pos);
|
|
}
|
|
|
|
const bool is_mixin_declaration = (CurrentToken() == Token::kASSIGN);
|
|
if (is_mixin_declaration && is_patch) {
|
|
ReportError(classname_pos,
|
|
"mixin application '%s' may not be a patch class",
|
|
class_name.ToCString());
|
|
}
|
|
|
|
AbstractType& super_type = Type::Handle(Z);
|
|
if ((CurrentToken() == Token::kEXTENDS) || is_mixin_declaration) {
|
|
ConsumeToken(); // extends or =
|
|
const intptr_t type_pos = TokenPos();
|
|
super_type = ParseType(ClassFinalizer::kResolveTypeParameters);
|
|
if (super_type.IsMalformedOrMalbounded()) {
|
|
ReportError(Error::Handle(Z, super_type.error()));
|
|
}
|
|
if (super_type.IsDynamicType()) {
|
|
// Unlikely here, since super type is not resolved yet.
|
|
ReportError(type_pos,
|
|
"class '%s' may not extend 'dynamic'",
|
|
class_name.ToCString());
|
|
}
|
|
if (super_type.IsTypeParameter()) {
|
|
ReportError(type_pos,
|
|
"class '%s' may not extend type parameter '%s'",
|
|
class_name.ToCString(),
|
|
String::Handle(Z,
|
|
super_type.UserVisibleName()).ToCString());
|
|
}
|
|
// The class finalizer will check whether the super type is malbounded.
|
|
if (is_mixin_declaration) {
|
|
if (CurrentToken() != Token::kWITH) {
|
|
ReportError("mixin application clause 'with type' expected");
|
|
}
|
|
cls.set_is_mixin_app_alias();
|
|
cls.set_is_synthesized_class();
|
|
}
|
|
if (CurrentToken() == Token::kWITH) {
|
|
super_type = ParseMixins(super_type);
|
|
}
|
|
} else {
|
|
// No extends clause: implicitly extend Object, unless Object itself.
|
|
if (!cls.IsObjectClass()) {
|
|
super_type = Type::ObjectType();
|
|
}
|
|
}
|
|
ASSERT(!super_type.IsNull() || cls.IsObjectClass());
|
|
cls.set_super_type(super_type);
|
|
|
|
if (CurrentToken() == Token::kIMPLEMENTS) {
|
|
ParseInterfaceList(cls);
|
|
}
|
|
|
|
if (is_patch) {
|
|
// Apply the changes to the patched class looked up above.
|
|
ASSERT(obj.raw() == library_.LookupLocalObject(class_name));
|
|
// The patched class must not be finalized yet.
|
|
const Class& orig_class = Class::Cast(obj);
|
|
ASSERT(!orig_class.is_finalized());
|
|
orig_class.set_patch_class(cls);
|
|
cls.set_is_patch();
|
|
}
|
|
pending_classes.Add(cls, Heap::kOld);
|
|
|
|
if (is_mixin_declaration) {
|
|
ExpectSemicolon();
|
|
} else {
|
|
CheckToken(Token::kLBRACE);
|
|
SkipBlock();
|
|
ExpectToken(Token::kRBRACE);
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::ParseClassDefinition(const Class& cls) {
|
|
TRACE_PARSER("ParseClassDefinition");
|
|
CompilerStats::num_classes_compiled++;
|
|
set_current_class(cls);
|
|
is_top_level_ = true;
|
|
String& class_name = String::Handle(Z, cls.Name());
|
|
const intptr_t class_pos = TokenPos();
|
|
ClassDesc members(cls, class_name, false, class_pos);
|
|
while (CurrentToken() != Token::kLBRACE) {
|
|
ConsumeToken();
|
|
}
|
|
ExpectToken(Token::kLBRACE);
|
|
while (CurrentToken() != Token::kRBRACE) {
|
|
intptr_t metadata_pos = SkipMetadata();
|
|
ParseClassMemberDefinition(&members, metadata_pos);
|
|
}
|
|
ExpectToken(Token::kRBRACE);
|
|
|
|
CheckConstructors(&members);
|
|
|
|
// Need to compute this here since MakeArray() will clear the
|
|
// functions array in members.
|
|
const bool need_implicit_constructor =
|
|
!members.has_constructor() && !cls.is_patch();
|
|
|
|
cls.AddFields(members.fields());
|
|
|
|
// Creating a new array for functions marks the class as parsed.
|
|
const Array& array = Array::Handle(Z, Array::MakeArray(members.functions()));
|
|
cls.SetFunctions(array);
|
|
|
|
// Add an implicit constructor if no explicit constructor is present.
|
|
// No implicit constructors are needed for patch classes.
|
|
if (need_implicit_constructor) {
|
|
AddImplicitConstructor(cls);
|
|
}
|
|
|
|
if (cls.is_patch()) {
|
|
// Apply the changes to the patched class looked up above.
|
|
Object& obj = Object::Handle(Z, library_.LookupLocalObject(class_name));
|
|
// The patched class must not be finalized yet.
|
|
const Class& orig_class = Class::Cast(obj);
|
|
ASSERT(!orig_class.is_finalized());
|
|
Error& error = Error::Handle(Z);
|
|
if (!orig_class.ApplyPatch(cls, &error)) {
|
|
Report::LongJumpF(error, script_, class_pos, "applying patch failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::ParseEnumDefinition(const Class& cls) {
|
|
TRACE_PARSER("ParseEnumDefinition");
|
|
CompilerStats::num_classes_compiled++;
|
|
|
|
const String& enum_name = String::Handle(Z, cls.Name());
|
|
ClassDesc enum_members(cls, enum_name, false, cls.token_pos());
|
|
|
|
// Add instance field 'final int index'.
|
|
Field& index_field = Field::ZoneHandle(Z);
|
|
const Type& int_type = Type::Handle(Z, Type::IntType());
|
|
const Type& dynamic_type = Type::Handle(Type::DynamicType());
|
|
index_field = Field::New(Symbols::Index(),
|
|
false, // Not static.
|
|
true, // Field is final.
|
|
false, // Not const.
|
|
false, // Not synthetic.
|
|
cls,
|
|
cls.token_pos());
|
|
index_field.set_type(int_type);
|
|
enum_members.AddField(index_field);
|
|
|
|
// Add implicit getter for index field.
|
|
const String& getter_name =
|
|
String::Handle(Z, Field::GetterSymbol(Symbols::Index()));
|
|
Function& getter = Function::Handle(Z);
|
|
getter = Function::New(getter_name,
|
|
RawFunction::kImplicitGetter,
|
|
/* is_static = */ false,
|
|
/* is_const = */ true,
|
|
/* is_abstract = */ false,
|
|
/* is_external = */ false,
|
|
/* is_native = */ false,
|
|
cls,
|
|
cls.token_pos());
|
|
getter.set_result_type(int_type);
|
|
getter.set_is_debuggable(false);
|
|
ParamList params;
|
|
params.AddReceiver(&dynamic_type, cls.token_pos());
|
|
AddFormalParamsToFunction(¶ms, getter);
|
|
enum_members.AddFunction(getter);
|
|
|
|
GrowableObjectArray& enum_names = GrowableObjectArray::Handle(Z,
|
|
GrowableObjectArray::New(8, Heap::kOld));
|
|
const String& name_prefix =
|
|
String::Handle(String::Concat(enum_name, Symbols::Dot()));
|
|
|
|
ASSERT(IsIdentifier());
|
|
ASSERT(CurrentLiteral()->raw() == cls.Name());
|
|
|
|
ConsumeToken(); // Enum type name.
|
|
ExpectToken(Token::kLBRACE);
|
|
Field& enum_value = Field::Handle(Z);
|
|
String& enum_value_name = String::Handle(Z);
|
|
intptr_t i = 0;
|
|
GrowableArray<String*> declared_names(8);
|
|
|
|
while (IsIdentifier()) {
|
|
String* enum_ident = CurrentLiteral();
|
|
|
|
// Check for name conflicts.
|
|
if (enum_ident->raw() == cls.Name()) {
|
|
ReportError("enum identifier '%s' cannot be equal to enum type name",
|
|
CurrentLiteral()->ToCString());
|
|
} else if (enum_ident->raw() == Symbols::Index().raw()) {
|
|
ReportError("enum identifier conflicts with "
|
|
"implicit instance field 'index'");
|
|
} else if (enum_ident->raw() == Symbols::Values().raw()) {
|
|
ReportError("enum identifier conflicts with "
|
|
"implicit static field 'values'");
|
|
} else if (enum_ident->raw() == Symbols::toString().raw()) {
|
|
ReportError("enum identifier conflicts with "
|
|
"implicit instance method 'toString()'");
|
|
}
|
|
for (intptr_t n = 0; n < declared_names.length(); n++) {
|
|
if (enum_ident->Equals(*declared_names[n])) {
|
|
ReportError("Duplicate name '%s' in enum definition '%s'",
|
|
enum_ident->ToCString(),
|
|
enum_name.ToCString());
|
|
}
|
|
}
|
|
declared_names.Add(enum_ident);
|
|
|
|
// Create the static const field for the enumeration value.
|
|
enum_value = Field::New(*enum_ident,
|
|
/* is_static = */ true,
|
|
/* is_final = */ true,
|
|
/* is_const = */ true,
|
|
/* is_synthetic = */ false,
|
|
cls,
|
|
cls.token_pos());
|
|
enum_value.set_type(dynamic_type);
|
|
enum_value.set_has_initializer(false);
|
|
enum_members.AddField(enum_value);
|
|
// Initialize the field with the ordinal value. It will be patched
|
|
// later with the enum constant instance.
|
|
const Smi& ordinal_value = Smi::Handle(Z, Smi::New(i));
|
|
enum_value.set_value(ordinal_value);
|
|
enum_value.RecordStore(ordinal_value);
|
|
i++;
|
|
|
|
// For the user-visible name of the enumeration value, we need to
|
|
// unmangle private names.
|
|
if (enum_ident->CharAt(0) == '_') {
|
|
*enum_ident = String::IdentifierPrettyName(*enum_ident);
|
|
}
|
|
enum_value_name = Symbols::FromConcat(name_prefix, *enum_ident);
|
|
enum_names.Add(enum_value_name, Heap::kOld);
|
|
|
|
ConsumeToken(); // Enum value name.
|
|
if (CurrentToken() == Token::kCOMMA) {
|
|
ConsumeToken();
|
|
}
|
|
}
|
|
ExpectToken(Token::kRBRACE);
|
|
|
|
const Class& helper_class =
|
|
Class::Handle(Z, Library::LookupCoreClass(Symbols::_EnumHelper()));
|
|
ASSERT(!helper_class.IsNull());
|
|
|
|
// Add static field 'const List values'.
|
|
Field& values_field = Field::ZoneHandle(Z);
|
|
values_field = Field::New(Symbols::Values(),
|
|
/* is_static = */ true,
|
|
/* is_final = */ true,
|
|
/* is_const = */ true,
|
|
/* is_synthetic = */ false,
|
|
cls,
|
|
cls.token_pos());
|
|
values_field.set_type(Type::Handle(Z, Type::ArrayType()));
|
|
enum_members.AddField(values_field);
|
|
|
|
// Allocate the immutable array containing the enumeration values.
|
|
// The actual enum instance values will be patched in later.
|
|
const Array& values_array = Array::Handle(Z, Array::New(i, Heap::kOld));
|
|
values_field.set_value(values_array);
|
|
values_field.RecordStore(values_array);
|
|
|
|
// Create a static field that contains the list of enumeration names.
|
|
// Clone the _enum_names field from the helper class.
|
|
Field& names_field = Field::Handle(Z,
|
|
helper_class.LookupStaticField(Symbols::_EnumNames()));
|
|
ASSERT(!names_field.IsNull());
|
|
names_field = names_field.Clone(cls);
|
|
enum_members.AddField(names_field);
|
|
const Array& names_array = Array::Handle(Array::MakeArray(enum_names));
|
|
names_field.set_value(names_array);
|
|
names_field.RecordStore(names_array);
|
|
|
|
// Clone the toString() function from the helper class.
|
|
Function& to_string_func = Function::Handle(Z,
|
|
helper_class.LookupDynamicFunctionAllowPrivate(Symbols::toString()));
|
|
ASSERT(!to_string_func.IsNull());
|
|
to_string_func = to_string_func.Clone(cls);
|
|
enum_members.AddFunction(to_string_func);
|
|
|
|
cls.AddFields(enum_members.fields());
|
|
const Array& functions =
|
|
Array::Handle(Z, Array::MakeArray(enum_members.functions()));
|
|
cls.SetFunctions(functions);
|
|
}
|
|
|
|
|
|
// Add an implicit constructor to the given class.
|
|
void Parser::AddImplicitConstructor(const Class& cls) {
|
|
// The implicit constructor is unnamed, has no explicit parameter.
|
|
String& ctor_name = String::ZoneHandle(Z, cls.Name());
|
|
ctor_name = String::Concat(ctor_name, Symbols::Dot());
|
|
ctor_name = Symbols::New(ctor_name);
|
|
// To indicate that this is an implicit constructor, we set the
|
|
// token position and end token position of the function
|
|
// to the token position of the class.
|
|
Function& ctor = Function::Handle(Z,
|
|
Function::New(ctor_name,
|
|
RawFunction::kConstructor,
|
|
/* is_static = */ false,
|
|
/* is_const = */ false,
|
|
/* is_abstract = */ false,
|
|
/* is_external = */ false,
|
|
/* is_native = */ false,
|
|
cls,
|
|
cls.token_pos()));
|
|
ctor.set_end_token_pos(ctor.token_pos());
|
|
ctor.set_is_debuggable(false);
|
|
if (library_.is_dart_scheme() && library_.IsPrivate(ctor_name)) {
|
|
ctor.set_is_reflectable(false);
|
|
}
|
|
|
|
ParamList params;
|
|
// Add implicit 'this' parameter.
|
|
const AbstractType* receiver_type = ReceiverType(cls);
|
|
params.AddReceiver(receiver_type, cls.token_pos());
|
|
// Add implicit parameter for construction phase.
|
|
params.AddFinalParameter(cls.token_pos(),
|
|
&Symbols::PhaseParameter(),
|
|
&Type::ZoneHandle(Z, Type::SmiType()));
|
|
|
|
AddFormalParamsToFunction(¶ms, ctor);
|
|
// The body of the constructor cannot modify the type of the constructed
|
|
// instance, which is passed in as the receiver.
|
|
ctor.set_result_type(*receiver_type);
|
|
cls.AddFunction(ctor);
|
|
}
|
|
|
|
|
|
// Check for cycles in constructor redirection.
|
|
void Parser::CheckConstructors(ClassDesc* class_desc) {
|
|
// Check for cycles in constructor redirection.
|
|
const GrowableArray<MemberDesc>& members = class_desc->members();
|
|
for (int i = 0; i < members.length(); i++) {
|
|
MemberDesc* member = &members[i];
|
|
if (member->redirect_name == NULL) {
|
|
continue;
|
|
}
|
|
GrowableArray<MemberDesc*> ctors;
|
|
while ((member != NULL) && (member->redirect_name != NULL)) {
|
|
ASSERT(member->IsConstructor());
|
|
// Check whether we have already seen this member.
|
|
for (int i = 0; i < ctors.length(); i++) {
|
|
if (ctors[i] == member) {
|
|
ReportError(member->name_pos,
|
|
"cyclic reference in constructor redirection");
|
|
}
|
|
}
|
|
// We haven't seen this member. Add it to the list and follow
|
|
// the next redirection. If we can't find the constructor to
|
|
// which the current one redirects, we ignore the unresolved
|
|
// reference. We'll catch it later when the constructor gets
|
|
// compiled.
|
|
ctors.Add(member);
|
|
member = class_desc->LookupMember(*member->redirect_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::ParseMixinAppAlias(
|
|
const GrowableObjectArray& pending_classes,
|
|
const Class& toplevel_class,
|
|
intptr_t metadata_pos) {
|
|
TRACE_PARSER("ParseMixinAppAlias");
|
|
const intptr_t classname_pos = TokenPos();
|
|
String& class_name = *ExpectUserDefinedTypeIdentifier("class name expected");
|
|
if (FLAG_trace_parser) {
|
|
OS::Print("toplevel parsing mixin application alias class '%s'\n",
|
|
class_name.ToCString());
|
|
}
|
|
const Object& obj = Object::Handle(Z, library_.LookupLocalObject(class_name));
|
|
if (!obj.IsNull()) {
|
|
ReportError(classname_pos, "'%s' is already defined",
|
|
class_name.ToCString());
|
|
}
|
|
const Class& mixin_application =
|
|
Class::Handle(Z, Class::New(class_name, script_, classname_pos));
|
|
mixin_application.set_is_mixin_app_alias();
|
|
library_.AddClass(mixin_application);
|
|
set_current_class(mixin_application);
|
|
ParseTypeParameters(mixin_application);
|
|
|
|
ExpectToken(Token::kASSIGN);
|
|
|
|
if (CurrentToken() == Token::kABSTRACT) {
|
|
mixin_application.set_is_abstract();
|
|
ConsumeToken();
|
|
}
|
|
|
|
const intptr_t type_pos = TokenPos();
|
|
AbstractType& type =
|
|
AbstractType::Handle(Z,
|
|
ParseType(ClassFinalizer::kResolveTypeParameters));
|
|
if (type.IsTypeParameter()) {
|
|
ReportError(type_pos,
|
|
"class '%s' may not extend type parameter '%s'",
|
|
class_name.ToCString(),
|
|
String::Handle(Z, type.UserVisibleName()).ToCString());
|
|
}
|
|
|
|
CheckToken(Token::kWITH, "mixin application 'with Type' expected");
|
|
type = ParseMixins(type);
|
|
|
|
mixin_application.set_super_type(type);
|
|
mixin_application.set_is_synthesized_class();
|
|
|
|
// This mixin application alias needs an implicit constructor, but it is
|
|
// too early to call 'AddImplicitConstructor(mixin_application)' here,
|
|
// because this class should be lazily compiled.
|
|
if (CurrentToken() == Token::kIMPLEMENTS) {
|
|
ParseInterfaceList(mixin_application);
|
|
}
|
|
ExpectSemicolon();
|
|
pending_classes.Add(mixin_application, Heap::kOld);
|
|
if (metadata_pos >= 0) {
|
|
library_.AddClassMetadata(mixin_application, toplevel_class, metadata_pos);
|
|
}
|
|
}
|
|
|
|
|
|
// Look ahead to detect if we are seeing ident [ TypeParameters ] "(".
|
|
// We need this lookahead to distinguish between the optional return type
|
|
// and the alias name of a function type alias.
|
|
// Token position remains unchanged.
|
|
bool Parser::IsFunctionTypeAliasName() {
|
|
if (IsIdentifier() && (LookaheadToken(1) == Token::kLPAREN)) {
|
|
return true;
|
|
}
|
|
const intptr_t saved_pos = TokenPos();
|
|
bool is_alias_name = false;
|
|
if (IsIdentifier() && (LookaheadToken(1) == Token::kLT)) {
|
|
ConsumeToken();
|
|
if (TryParseTypeParameters() && (CurrentToken() == Token::kLPAREN)) {
|
|
is_alias_name = true;
|
|
}
|
|
}
|
|
SetPosition(saved_pos);
|
|
return is_alias_name;
|
|
}
|
|
|
|
|
|
// Look ahead to detect if we are seeing ident [ TypeParameters ] "=".
|
|
// Token position remains unchanged.
|
|
bool Parser::IsMixinAppAlias() {
|
|
if (IsIdentifier() && (LookaheadToken(1) == Token::kASSIGN)) {
|
|
return true;
|
|
}
|
|
const intptr_t saved_pos = TokenPos();
|
|
bool is_mixin_def = false;
|
|
if (IsIdentifier() && (LookaheadToken(1) == Token::kLT)) {
|
|
ConsumeToken();
|
|
if (TryParseTypeParameters() && (CurrentToken() == Token::kASSIGN)) {
|
|
is_mixin_def = true;
|
|
}
|
|
}
|
|
SetPosition(saved_pos);
|
|
return is_mixin_def;
|
|
}
|
|
|
|
|
|
void Parser::ParseTypedef(const GrowableObjectArray& pending_classes,
|
|
const Class& toplevel_class,
|
|
intptr_t metadata_pos) {
|
|
TRACE_PARSER("ParseTypedef");
|
|
ExpectToken(Token::kTYPEDEF);
|
|
|
|
if (IsMixinAppAlias()) {
|
|
if (FLAG_warn_mixin_typedef) {
|
|
ReportWarning(TokenPos(), "deprecated mixin application typedef");
|
|
}
|
|
ParseMixinAppAlias(pending_classes, toplevel_class, metadata_pos);
|
|
return;
|
|
}
|
|
|
|
// Parse the result type of the function type.
|
|
AbstractType& result_type = Type::Handle(Z, Type::DynamicType());
|
|
if (CurrentToken() == Token::kVOID) {
|
|
ConsumeToken();
|
|
result_type = Type::VoidType();
|
|
} else if (!IsFunctionTypeAliasName()) {
|
|
// Type annotations in typedef are never ignored, even in production mode.
|
|
// Wait until we have an owner class before resolving the result type.
|
|
result_type = ParseType(ClassFinalizer::kDoNotResolve);
|
|
}
|
|
|
|
const intptr_t alias_name_pos = TokenPos();
|
|
const String* alias_name =
|
|
ExpectUserDefinedTypeIdentifier("function alias name expected");
|
|
|
|
// Lookup alias name and report an error if it is already defined in
|
|
// the library scope.
|
|
const Object& obj =
|
|
Object::Handle(Z, library_.LookupLocalObject(*alias_name));
|
|
if (!obj.IsNull()) {
|
|
ReportError(alias_name_pos,
|
|
"'%s' is already defined", alias_name->ToCString());
|
|
}
|
|
|
|
// Create the function type alias signature class. It will be linked to its
|
|
// signature function after it has been parsed. The type parameters, in order
|
|
// to be properly finalized, need to be associated to this signature class as
|
|
// they are parsed.
|
|
const Class& function_type_alias = Class::Handle(Z,
|
|
Class::NewSignatureClass(*alias_name,
|
|
Function::Handle(Z),
|
|
script_,
|
|
alias_name_pos));
|
|
library_.AddClass(function_type_alias);
|
|
set_current_class(function_type_alias);
|
|
// Parse the type parameters of the function type.
|
|
ParseTypeParameters(function_type_alias);
|
|
// At this point, the type parameters have been parsed, so we can resolve the
|
|
// result type.
|
|
if (!result_type.IsNull()) {
|
|
ResolveTypeFromClass(function_type_alias,
|
|
ClassFinalizer::kResolveTypeParameters,
|
|
&result_type);
|
|
}
|
|
// Parse the formal parameters of the function type.
|
|
CheckToken(Token::kLPAREN, "formal parameter list expected");
|
|
ParamList func_params;
|
|
|
|
// Add implicit closure object parameter.
|
|
func_params.AddFinalParameter(
|
|
TokenPos(),
|
|
&Symbols::ClosureParameter(),
|
|
&Type::ZoneHandle(Z, Type::DynamicType()));
|
|
|
|
const bool no_explicit_default_values = false;
|
|
ParseFormalParameterList(no_explicit_default_values, false, &func_params);
|
|
ExpectSemicolon();
|
|
// The field 'is_static' has no meaning for signature functions.
|
|
Function& signature_function = Function::Handle(Z,
|
|
Function::New(*alias_name,
|
|
RawFunction::kSignatureFunction,
|
|
/* is_static = */ false,
|
|
/* is_const = */ false,
|
|
/* is_abstract = */ false,
|
|
/* is_external = */ false,
|
|
/* is_native = */ false,
|
|
function_type_alias,
|
|
alias_name_pos));
|
|
signature_function.set_result_type(result_type);
|
|
signature_function.set_is_debuggable(false);
|
|
AddFormalParamsToFunction(&func_params, signature_function);
|
|
|
|
// Patch the signature function in the signature class.
|
|
function_type_alias.PatchSignatureFunction(signature_function);
|
|
|
|
const String& signature = String::Handle(Z,
|
|
signature_function.Signature());
|
|
if (FLAG_trace_parser) {
|
|
OS::Print("TopLevel parsing function type alias '%s'\n",
|
|
signature.ToCString());
|
|
}
|
|
// Lookup the signature class, i.e. the class whose name is the signature.
|
|
// We only lookup in the current library, but not in its imports, and only
|
|
// create a new canonical signature class if it does not exist yet.
|
|
Class& signature_class =
|
|
Class::ZoneHandle(Z, library_.LookupLocalClass(signature));
|
|
if (signature_class.IsNull()) {
|
|
signature_class = Class::NewSignatureClass(signature,
|
|
signature_function,
|
|
script_,
|
|
alias_name_pos);
|
|
// Record the function signature class in the current library.
|
|
library_.AddClass(signature_class);
|
|
} else {
|
|
// Forget the just created signature function and use the existing one.
|
|
signature_function = signature_class.signature_function();
|
|
function_type_alias.PatchSignatureFunction(signature_function);
|
|
}
|
|
ASSERT(signature_function.signature_class() == signature_class.raw());
|
|
|
|
// The alias should not be marked as finalized yet, since it needs to be
|
|
// checked in the class finalizer for illegal self references.
|
|
ASSERT(!function_type_alias.IsCanonicalSignatureClass());
|
|
ASSERT(!function_type_alias.is_finalized());
|
|
pending_classes.Add(function_type_alias, Heap::kOld);
|
|
if (metadata_pos >= 0) {
|
|
library_.AddClassMetadata(function_type_alias,
|
|
toplevel_class,
|
|
metadata_pos);
|
|
}
|
|
}
|
|
|
|
|
|
// Consumes exactly one right angle bracket. If the current token is a single
|
|
// bracket token, it is consumed normally. However, if it is a double or triple
|
|
// bracket, it is replaced by a single or double bracket token without
|
|
// incrementing the token index.
|
|
void Parser::ConsumeRightAngleBracket() {
|
|
if (token_kind_ == Token::kGT) {
|
|
ConsumeToken();
|
|
} else if (token_kind_ == Token::kSHR) {
|
|
token_kind_ = Token::kGT;
|
|
} else {
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
|
|
intptr_t Parser::SkipMetadata() {
|
|
if (CurrentToken() != Token::kAT) {
|
|
return -1;
|
|
}
|
|
intptr_t metadata_pos = TokenPos();
|
|
while (CurrentToken() == Token::kAT) {
|
|
ConsumeToken();
|
|
ExpectIdentifier("identifier expected");
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
ExpectIdentifier("identifier expected");
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
ExpectIdentifier("identifier expected");
|
|
}
|
|
}
|
|
if (CurrentToken() == Token::kLPAREN) {
|
|
SkipToMatchingParenthesis();
|
|
}
|
|
}
|
|
return metadata_pos;
|
|
}
|
|
|
|
|
|
void Parser::SkipTypeArguments() {
|
|
if (CurrentToken() == Token::kLT) {
|
|
do {
|
|
ConsumeToken();
|
|
SkipType(false);
|
|
} while (CurrentToken() == Token::kCOMMA);
|
|
Token::Kind token = CurrentToken();
|
|
if ((token == Token::kGT) || (token == Token::kSHR)) {
|
|
ConsumeRightAngleBracket();
|
|
} else {
|
|
ReportError("right angle bracket expected");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipType(bool allow_void) {
|
|
if (CurrentToken() == Token::kVOID) {
|
|
if (!allow_void) {
|
|
ReportError("'void' not allowed here");
|
|
}
|
|
ConsumeToken();
|
|
} else {
|
|
ExpectIdentifier("type name expected");
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
ExpectIdentifier("name expected");
|
|
}
|
|
SkipTypeArguments();
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::ParseTypeParameters(const Class& cls) {
|
|
TRACE_PARSER("ParseTypeParameters");
|
|
if (CurrentToken() == Token::kLT) {
|
|
const GrowableObjectArray& type_parameters_array =
|
|
GrowableObjectArray::Handle(Z, GrowableObjectArray::New());
|
|
intptr_t index = 0;
|
|
TypeParameter& type_parameter = TypeParameter::Handle(Z);
|
|
TypeParameter& existing_type_parameter = TypeParameter::Handle(Z);
|
|
String& existing_type_parameter_name = String::Handle(Z);
|
|
AbstractType& type_parameter_bound = Type::Handle(Z);
|
|
do {
|
|
ConsumeToken();
|
|
const intptr_t metadata_pos = SkipMetadata();
|
|
const intptr_t type_parameter_pos = TokenPos();
|
|
String& type_parameter_name =
|
|
*ExpectUserDefinedTypeIdentifier("type parameter expected");
|
|
// Check for duplicate type parameters.
|
|
for (intptr_t i = 0; i < index; i++) {
|
|
existing_type_parameter ^= type_parameters_array.At(i);
|
|
existing_type_parameter_name = existing_type_parameter.name();
|
|
if (existing_type_parameter_name.Equals(type_parameter_name)) {
|
|
ReportError(type_parameter_pos, "duplicate type parameter '%s'",
|
|
type_parameter_name.ToCString());
|
|
}
|
|
}
|
|
if (CurrentToken() == Token::kEXTENDS) {
|
|
ConsumeToken();
|
|
// A bound may refer to the owner of the type parameter it applies to,
|
|
// i.e. to the class or interface currently being parsed.
|
|
// Postpone resolution in order to avoid resolving the class and its
|
|
// type parameters, as they are not fully parsed yet.
|
|
type_parameter_bound = ParseType(ClassFinalizer::kDoNotResolve);
|
|
} else {
|
|
type_parameter_bound = I->object_store()->object_type();
|
|
}
|
|
type_parameter = TypeParameter::New(cls,
|
|
index,
|
|
type_parameter_name,
|
|
type_parameter_bound,
|
|
type_parameter_pos);
|
|
type_parameters_array.Add(type_parameter);
|
|
if (metadata_pos >= 0) {
|
|
library_.AddTypeParameterMetadata(type_parameter, metadata_pos);
|
|
}
|
|
index++;
|
|
} while (CurrentToken() == Token::kCOMMA);
|
|
Token::Kind token = CurrentToken();
|
|
if ((token == Token::kGT) || (token == Token::kSHR)) {
|
|
ConsumeRightAngleBracket();
|
|
} else {
|
|
ReportError("right angle bracket expected");
|
|
}
|
|
const TypeArguments& type_parameters =
|
|
TypeArguments::Handle(Z,
|
|
NewTypeArguments(type_parameters_array));
|
|
cls.set_type_parameters(type_parameters);
|
|
// Try to resolve the upper bounds, which will at least resolve the
|
|
// referenced type parameters.
|
|
const intptr_t num_types = type_parameters.Length();
|
|
for (intptr_t i = 0; i < num_types; i++) {
|
|
type_parameter ^= type_parameters.TypeAt(i);
|
|
type_parameter_bound = type_parameter.bound();
|
|
ResolveTypeFromClass(cls,
|
|
ClassFinalizer::kResolveTypeParameters,
|
|
&type_parameter_bound);
|
|
type_parameter.set_bound(type_parameter_bound);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
RawTypeArguments* Parser::ParseTypeArguments(
|
|
ClassFinalizer::FinalizationKind finalization) {
|
|
TRACE_PARSER("ParseTypeArguments");
|
|
if (CurrentToken() == Token::kLT) {
|
|
const GrowableObjectArray& types =
|
|
GrowableObjectArray::Handle(Z, GrowableObjectArray::New());
|
|
AbstractType& type = AbstractType::Handle(Z);
|
|
do {
|
|
ConsumeToken();
|
|
type = ParseType(finalization);
|
|
// Map a malformed type argument to dynamic.
|
|
if (type.IsMalformed()) {
|
|
type = Type::DynamicType();
|
|
}
|
|
types.Add(type);
|
|
} while (CurrentToken() == Token::kCOMMA);
|
|
Token::Kind token = CurrentToken();
|
|
if ((token == Token::kGT) || (token == Token::kSHR)) {
|
|
ConsumeRightAngleBracket();
|
|
} else {
|
|
ReportError("right angle bracket expected");
|
|
}
|
|
if (finalization != ClassFinalizer::kIgnore) {
|
|
return NewTypeArguments(types);
|
|
}
|
|
}
|
|
return TypeArguments::null();
|
|
}
|
|
|
|
|
|
// Parse interface list and add to class cls.
|
|
void Parser::ParseInterfaceList(const Class& cls) {
|
|
TRACE_PARSER("ParseInterfaceList");
|
|
ASSERT(CurrentToken() == Token::kIMPLEMENTS);
|
|
const GrowableObjectArray& all_interfaces =
|
|
GrowableObjectArray::Handle(Z, GrowableObjectArray::New());
|
|
AbstractType& interface = AbstractType::Handle(Z);
|
|
// First get all the interfaces already implemented by class.
|
|
Array& cls_interfaces = Array::Handle(Z, cls.interfaces());
|
|
for (intptr_t i = 0; i < cls_interfaces.Length(); i++) {
|
|
interface ^= cls_interfaces.At(i);
|
|
all_interfaces.Add(interface);
|
|
}
|
|
// Now parse and add the new interfaces.
|
|
do {
|
|
ConsumeToken();
|
|
intptr_t interface_pos = TokenPos();
|
|
interface = ParseType(ClassFinalizer::kResolveTypeParameters);
|
|
if (interface.IsTypeParameter()) {
|
|
ReportError(interface_pos,
|
|
"type parameter '%s' may not be used in interface list",
|
|
String::Handle(Z, interface.UserVisibleName()).ToCString());
|
|
}
|
|
all_interfaces.Add(interface);
|
|
} while (CurrentToken() == Token::kCOMMA);
|
|
cls_interfaces = Array::MakeArray(all_interfaces);
|
|
cls.set_interfaces(cls_interfaces);
|
|
}
|
|
|
|
|
|
RawAbstractType* Parser::ParseMixins(const AbstractType& super_type) {
|
|
TRACE_PARSER("ParseMixins");
|
|
ASSERT(CurrentToken() == Token::kWITH);
|
|
const GrowableObjectArray& mixin_types =
|
|
GrowableObjectArray::Handle(Z, GrowableObjectArray::New());
|
|
AbstractType& mixin_type = AbstractType::Handle(Z);
|
|
do {
|
|
ConsumeToken();
|
|
mixin_type = ParseType(ClassFinalizer::kResolveTypeParameters);
|
|
if (mixin_type.IsDynamicType()) {
|
|
// The string 'dynamic' is not resolved yet at this point, but a malformed
|
|
// type mapped to dynamic can be encountered here.
|
|
ReportError(mixin_type.token_pos(), "illegal mixin of a malformed type");
|
|
}
|
|
if (mixin_type.IsTypeParameter()) {
|
|
ReportError(mixin_type.token_pos(),
|
|
"mixin type '%s' may not be a type parameter",
|
|
String::Handle(Z, mixin_type.UserVisibleName()).ToCString());
|
|
}
|
|
mixin_types.Add(mixin_type);
|
|
} while (CurrentToken() == Token::kCOMMA);
|
|
return MixinAppType::New(super_type,
|
|
Array::Handle(Z, Array::MakeArray(mixin_types)));
|
|
}
|
|
|
|
|
|
void Parser::ParseTopLevelVariable(TopLevel* top_level,
|
|
intptr_t metadata_pos) {
|
|
TRACE_PARSER("ParseTopLevelVariable");
|
|
const bool is_const = (CurrentToken() == Token::kCONST);
|
|
// Const fields are implicitly final.
|
|
const bool is_final = is_const || (CurrentToken() == Token::kFINAL);
|
|
const bool is_static = true;
|
|
const bool is_synthetic = false;
|
|
const AbstractType& type = AbstractType::ZoneHandle(Z,
|
|
ParseConstFinalVarOrType(ClassFinalizer::kResolveTypeParameters));
|
|
Field& field = Field::Handle(Z);
|
|
Function& getter = Function::Handle(Z);
|
|
while (true) {
|
|
const intptr_t name_pos = TokenPos();
|
|
String& var_name = *ExpectIdentifier("variable name expected");
|
|
|
|
if (library_.LookupLocalObject(var_name) != Object::null()) {
|
|
ReportError(name_pos, "'%s' is already defined", var_name.ToCString());
|
|
}
|
|
|
|
// Check whether a getter or setter for this name exists. A const
|
|
// or final field implies a setter which throws a NoSuchMethodError,
|
|
// thus we need to check for conflicts with existing setters and
|
|
// getters.
|
|
String& accessor_name = String::Handle(Z,
|
|
Field::GetterName(var_name));
|
|
if (library_.LookupLocalObject(accessor_name) != Object::null()) {
|
|
ReportError(name_pos, "getter for '%s' is already defined",
|
|
var_name.ToCString());
|
|
}
|
|
accessor_name = Field::SetterName(var_name);
|
|
if (library_.LookupLocalObject(accessor_name) != Object::null()) {
|
|
ReportError(name_pos, "setter for '%s' is already defined",
|
|
var_name.ToCString());
|
|
}
|
|
|
|
field = Field::New(var_name, is_static, is_final, is_const, is_synthetic,
|
|
current_class(), name_pos);
|
|
field.set_type(type);
|
|
field.set_value(Instance::Handle(Z, Instance::null()));
|
|
top_level->fields.Add(field);
|
|
library_.AddObject(field, var_name);
|
|
if (metadata_pos >= 0) {
|
|
library_.AddFieldMetadata(field, metadata_pos);
|
|
}
|
|
if (CurrentToken() == Token::kASSIGN) {
|
|
ConsumeToken();
|
|
Instance& field_value = Instance::Handle(Z,
|
|
Object::sentinel().raw());
|
|
bool has_simple_literal = false;
|
|
if (LookaheadToken(1) == Token::kSEMICOLON) {
|
|
has_simple_literal = IsSimpleLiteral(type, &field_value);
|
|
}
|
|
SkipExpr();
|
|
field.set_value(field_value);
|
|
if (!has_simple_literal) {
|
|
// Create a static final getter.
|
|
String& getter_name = String::Handle(Z,
|
|
Field::GetterSymbol(var_name));
|
|
getter = Function::New(getter_name,
|
|
RawFunction::kImplicitStaticFinalGetter,
|
|
is_static,
|
|
is_const,
|
|
/* is_abstract = */ false,
|
|
/* is_external = */ false,
|
|
/* is_native = */ false,
|
|
current_class(),
|
|
name_pos);
|
|
getter.set_result_type(type);
|
|
getter.set_is_debuggable(false);
|
|
if (library_.is_dart_scheme() && library_.IsPrivate(var_name)) {
|
|
getter.set_is_reflectable(false);
|
|
}
|
|
top_level->functions.Add(getter);
|
|
}
|
|
} else if (is_final) {
|
|
ReportError(name_pos, "missing initializer for final or const variable");
|
|
}
|
|
|
|
if (CurrentToken() == Token::kCOMMA) {
|
|
ConsumeToken();
|
|
} else if (CurrentToken() == Token::kSEMICOLON) {
|
|
ConsumeToken();
|
|
break;
|
|
} else {
|
|
ExpectSemicolon(); // Reports error.
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
RawFunction::AsyncModifier Parser::ParseFunctionModifier() {
|
|
if (CurrentLiteral()->raw() == Symbols::Async().raw()) {
|
|
ConsumeToken();
|
|
return RawFunction::kAsync;
|
|
}
|
|
return RawFunction::kNoModifier;
|
|
}
|
|
|
|
|
|
void Parser::ParseTopLevelFunction(TopLevel* top_level,
|
|
intptr_t metadata_pos) {
|
|
TRACE_PARSER("ParseTopLevelFunction");
|
|
const intptr_t decl_begin_pos = TokenPos();
|
|
AbstractType& result_type = Type::Handle(Z, Type::DynamicType());
|
|
const bool is_static = true;
|
|
bool is_external = false;
|
|
bool is_patch = false;
|
|
if (is_patch_source() &&
|
|
(CurrentToken() == Token::kIDENT) &&
|
|
CurrentLiteral()->Equals("patch") &&
|
|
(LookaheadToken(1) != Token::kLPAREN)) {
|
|
ConsumeToken();
|
|
is_patch = true;
|
|
} else if (CurrentToken() == Token::kEXTERNAL) {
|
|
ConsumeToken();
|
|
is_external = true;
|
|
}
|
|
if (CurrentToken() == Token::kVOID) {
|
|
ConsumeToken();
|
|
result_type = Type::VoidType();
|
|
} else {
|
|
// Parse optional type.
|
|
if ((CurrentToken() == Token::kIDENT) &&
|
|
(LookaheadToken(1) != Token::kLPAREN)) {
|
|
result_type = ParseType(ClassFinalizer::kResolveTypeParameters);
|
|
}
|
|
}
|
|
const intptr_t name_pos = TokenPos();
|
|
const String& func_name = *ExpectIdentifier("function name expected");
|
|
|
|
bool found = library_.LookupLocalObject(func_name) != Object::null();
|
|
if (found && !is_patch) {
|
|
ReportError(name_pos, "'%s' is already defined", func_name.ToCString());
|
|
} else if (!found && is_patch) {
|
|
ReportError(name_pos, "missing '%s' cannot be patched",
|
|
func_name.ToCString());
|
|
}
|
|
String& accessor_name = String::Handle(Z,
|
|
Field::GetterName(func_name));
|
|
if (library_.LookupLocalObject(accessor_name) != Object::null()) {
|
|
ReportError(name_pos, "'%s' is already defined as getter",
|
|
func_name.ToCString());
|
|
}
|
|
// A setter named x= may co-exist with a function named x, thus we do
|
|
// not need to check setters.
|
|
|
|
CheckToken(Token::kLPAREN);
|
|
const intptr_t function_pos = TokenPos();
|
|
ParamList params;
|
|
const bool allow_explicit_default_values = true;
|
|
ParseFormalParameterList(allow_explicit_default_values, false, ¶ms);
|
|
|
|
RawFunction::AsyncModifier func_modifier = ParseFunctionModifier();
|
|
|
|
intptr_t function_end_pos = function_pos;
|
|
bool is_native = false;
|
|
if (is_external) {
|
|
function_end_pos = TokenPos();
|
|
ExpectSemicolon();
|
|
} else if (CurrentToken() == Token::kLBRACE) {
|
|
SkipBlock();
|
|
function_end_pos = TokenPos();
|
|
ExpectToken(Token::kRBRACE);
|
|
} else if (CurrentToken() == Token::kARROW) {
|
|
ConsumeToken();
|
|
BoolScope allow_await(&this->await_is_keyword_,
|
|
func_modifier != RawFunction::kNoModifier);
|
|
SkipExpr();
|
|
function_end_pos = TokenPos();
|
|
ExpectSemicolon();
|
|
} else if (IsLiteral("native")) {
|
|
ParseNativeDeclaration();
|
|
function_end_pos = TokenPos();
|
|
ExpectSemicolon();
|
|
is_native = true;
|
|
} else {
|
|
ReportError("function block expected");
|
|
}
|
|
Function& func = Function::Handle(Z,
|
|
Function::New(func_name,
|
|
RawFunction::kRegularFunction,
|
|
is_static,
|
|
/* is_const = */ false,
|
|
/* is_abstract = */ false,
|
|
is_external,
|
|
is_native,
|
|
current_class(),
|
|
decl_begin_pos));
|
|
func.set_result_type(result_type);
|
|
func.set_end_token_pos(function_end_pos);
|
|
func.set_modifier(func_modifier);
|
|
if (library_.is_dart_scheme() && library_.IsPrivate(func_name)) {
|
|
func.set_is_reflectable(false);
|
|
}
|
|
AddFormalParamsToFunction(¶ms, func);
|
|
top_level->functions.Add(func);
|
|
if (!is_patch) {
|
|
library_.AddObject(func, func_name);
|
|
} else {
|
|
library_.ReplaceObject(func, func_name);
|
|
}
|
|
if (metadata_pos >= 0) {
|
|
library_.AddFunctionMetadata(func, metadata_pos);
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::ParseTopLevelAccessor(TopLevel* top_level,
|
|
intptr_t metadata_pos) {
|
|
TRACE_PARSER("ParseTopLevelAccessor");
|
|
const intptr_t decl_begin_pos = TokenPos();
|
|
const bool is_static = true;
|
|
bool is_external = false;
|
|
bool is_patch = false;
|
|
AbstractType& result_type = AbstractType::Handle(Z);
|
|
if (is_patch_source() &&
|
|
(CurrentToken() == Token::kIDENT) &&
|
|
CurrentLiteral()->Equals("patch")) {
|
|
ConsumeToken();
|
|
is_patch = true;
|
|
} else if (CurrentToken() == Token::kEXTERNAL) {
|
|
ConsumeToken();
|
|
is_external = true;
|
|
}
|
|
bool is_getter = (CurrentToken() == Token::kGET);
|
|
if (CurrentToken() == Token::kGET ||
|
|
CurrentToken() == Token::kSET) {
|
|
ConsumeToken();
|
|
result_type = Type::DynamicType();
|
|
} else {
|
|
if (CurrentToken() == Token::kVOID) {
|
|
ConsumeToken();
|
|
result_type = Type::VoidType();
|
|
} else {
|
|
result_type = ParseType(ClassFinalizer::kResolveTypeParameters);
|
|
}
|
|
is_getter = (CurrentToken() == Token::kGET);
|
|
if (CurrentToken() == Token::kGET || CurrentToken() == Token::kSET) {
|
|
ConsumeToken();
|
|
} else {
|
|
UnexpectedToken();
|
|
}
|
|
}
|
|
const intptr_t name_pos = TokenPos();
|
|
const String* field_name = ExpectIdentifier("accessor name expected");
|
|
|
|
const intptr_t accessor_pos = TokenPos();
|
|
ParamList params;
|
|
|
|
if (!is_getter) {
|
|
const bool allow_explicit_default_values = true;
|
|
ParseFormalParameterList(allow_explicit_default_values, false, ¶ms);
|
|
}
|
|
String& accessor_name = String::ZoneHandle(Z);
|
|
int expected_num_parameters = -1;
|
|
if (is_getter) {
|
|
expected_num_parameters = 0;
|
|
accessor_name = Field::GetterSymbol(*field_name);
|
|
} else {
|
|
expected_num_parameters = 1;
|
|
accessor_name = Field::SetterSymbol(*field_name);
|
|
}
|
|
if ((params.num_fixed_parameters != expected_num_parameters) ||
|
|
(params.num_optional_parameters != 0)) {
|
|
ReportError(name_pos, "illegal %s parameters",
|
|
is_getter ? "getter" : "setter");
|
|
}
|
|
|
|
// Check whether this getter conflicts with a function or top-level variable
|
|
// with the same name.
|
|
if (is_getter &&
|
|
(library_.LookupLocalObject(*field_name) != Object::null())) {
|
|
ReportError(name_pos, "'%s' is already defined in this library",
|
|
field_name->ToCString());
|
|
}
|
|
// Check whether this setter conflicts with the implicit setter
|
|
// of a top-level variable with the same name.
|
|
if (!is_getter &&
|
|
(library_.LookupLocalField(*field_name) != Object::null())) {
|
|
ReportError(name_pos, "Variable '%s' is already defined in this library",
|
|
field_name->ToCString());
|
|
}
|
|
bool found = library_.LookupLocalObject(accessor_name) != Object::null();
|
|
if (found && !is_patch) {
|
|
ReportError(name_pos, "%s for '%s' is already defined",
|
|
is_getter ? "getter" : "setter",
|
|
field_name->ToCString());
|
|
} else if (!found && is_patch) {
|
|
ReportError(name_pos, "missing %s for '%s' cannot be patched",
|
|
is_getter ? "getter" : "setter",
|
|
field_name->ToCString());
|
|
}
|
|
|
|
RawFunction::AsyncModifier func_modifier = ParseFunctionModifier();
|
|
|
|
intptr_t accessor_end_pos = accessor_pos;
|
|
bool is_native = false;
|
|
if (is_external) {
|
|
accessor_end_pos = TokenPos();
|
|
ExpectSemicolon();
|
|
} else if (CurrentToken() == Token::kLBRACE) {
|
|
SkipBlock();
|
|
accessor_end_pos = TokenPos();
|
|
ExpectToken(Token::kRBRACE);
|
|
} else if (CurrentToken() == Token::kARROW) {
|
|
ConsumeToken();
|
|
SkipExpr();
|
|
accessor_end_pos = TokenPos();
|
|
ExpectSemicolon();
|
|
} else if (IsLiteral("native")) {
|
|
ParseNativeDeclaration();
|
|
accessor_end_pos = TokenPos();
|
|
ExpectSemicolon();
|
|
is_native = true;
|
|
} else {
|
|
ReportError("function block expected");
|
|
}
|
|
Function& func = Function::Handle(Z,
|
|
Function::New(accessor_name,
|
|
is_getter ? RawFunction::kGetterFunction :
|
|
RawFunction::kSetterFunction,
|
|
is_static,
|
|
/* is_const = */ false,
|
|
/* is_abstract = */ false,
|
|
is_external,
|
|
is_native,
|
|
current_class(),
|
|
decl_begin_pos));
|
|
func.set_result_type(result_type);
|
|
func.set_end_token_pos(accessor_end_pos);
|
|
func.set_modifier(func_modifier);
|
|
if (is_native) {
|
|
func.set_is_debuggable(false);
|
|
}
|
|
if (library_.is_dart_scheme() && library_.IsPrivate(accessor_name)) {
|
|
func.set_is_reflectable(false);
|
|
}
|
|
AddFormalParamsToFunction(¶ms, func);
|
|
top_level->functions.Add(func);
|
|
if (!is_patch) {
|
|
library_.AddObject(func, accessor_name);
|
|
} else {
|
|
library_.ReplaceObject(func, accessor_name);
|
|
}
|
|
if (metadata_pos >= 0) {
|
|
library_.AddFunctionMetadata(func, metadata_pos);
|
|
}
|
|
}
|
|
|
|
|
|
RawObject* Parser::CallLibraryTagHandler(Dart_LibraryTag tag,
|
|
intptr_t token_pos,
|
|
const String& url) {
|
|
Dart_LibraryTagHandler handler = I->library_tag_handler();
|
|
if (handler == NULL) {
|
|
if (url.StartsWith(Symbols::DartScheme())) {
|
|
if (tag == Dart_kCanonicalizeUrl) {
|
|
return url.raw();
|
|
}
|
|
return Object::null();
|
|
}
|
|
ReportError(token_pos, "no library handler registered");
|
|
}
|
|
// Block class finalization attempts when calling into the library
|
|
// tag handler.
|
|
I->BlockClassFinalization();
|
|
Api::Scope api_scope(I);
|
|
Dart_Handle result = handler(tag,
|
|
Api::NewHandle(I, library_.raw()),
|
|
Api::NewHandle(I, url.raw()));
|
|
I->UnblockClassFinalization();
|
|
if (Dart_IsError(result)) {
|
|
// In case of an error we append an explanatory error message to the
|
|
// error obtained from the library tag handler.
|
|
Error& prev_error = Error::Handle(Z);
|
|
prev_error ^= Api::UnwrapHandle(result);
|
|
Report::LongJumpF(prev_error, script_, token_pos, "library handler failed");
|
|
}
|
|
if (tag == Dart_kCanonicalizeUrl) {
|
|
if (!Dart_IsString(result)) {
|
|
ReportError(token_pos, "library handler failed URI canonicalization");
|
|
}
|
|
}
|
|
return Api::UnwrapHandle(result);
|
|
}
|
|
|
|
|
|
void Parser::ParseLibraryName() {
|
|
ASSERT(CurrentToken() == Token::kLIBRARY);
|
|
ConsumeToken();
|
|
String& lib_name = *ExpectIdentifier("library name expected");
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
while (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
lib_name = String::Concat(lib_name, Symbols::Dot());
|
|
lib_name = String::Concat(lib_name,
|
|
*ExpectIdentifier("malformed library name"));
|
|
}
|
|
lib_name = Symbols::New(lib_name);
|
|
}
|
|
library_.SetName(lib_name);
|
|
ExpectSemicolon();
|
|
}
|
|
|
|
|
|
void Parser::ParseIdentList(GrowableObjectArray* names) {
|
|
if (!IsIdentifier()) {
|
|
ReportError("identifier expected");
|
|
}
|
|
while (IsIdentifier()) {
|
|
names->Add(*CurrentLiteral());
|
|
ConsumeToken(); // Identifier.
|
|
if (CurrentToken() != Token::kCOMMA) {
|
|
return;
|
|
}
|
|
ConsumeToken(); // Comma.
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::ParseLibraryImportExport(intptr_t metadata_pos) {
|
|
bool is_import = (CurrentToken() == Token::kIMPORT);
|
|
bool is_export = (CurrentToken() == Token::kEXPORT);
|
|
ASSERT(is_import || is_export);
|
|
const intptr_t import_pos = TokenPos();
|
|
ConsumeToken();
|
|
CheckToken(Token::kSTRING, "library url expected");
|
|
AstNode* url_literal = ParseStringLiteral(false);
|
|
ASSERT(url_literal->IsLiteralNode());
|
|
ASSERT(url_literal->AsLiteralNode()->literal().IsString());
|
|
const String& url = String::Cast(url_literal->AsLiteralNode()->literal());
|
|
if (url.Length() == 0) {
|
|
ReportError("library url expected");
|
|
}
|
|
bool is_deferred_import = false;
|
|
if (is_import && (IsLiteral("deferred"))) {
|
|
is_deferred_import = true;
|
|
ConsumeToken();
|
|
CheckToken(Token::kAS, "'as' expected");
|
|
}
|
|
String& prefix = String::Handle(Z);
|
|
intptr_t prefix_pos = 0;
|
|
if (is_import && (CurrentToken() == Token::kAS)) {
|
|
ConsumeToken();
|
|
prefix_pos = TokenPos();
|
|
prefix = ExpectIdentifier("prefix identifier expected")->raw();
|
|
}
|
|
|
|
Array& show_names = Array::Handle(Z);
|
|
Array& hide_names = Array::Handle(Z);
|
|
if (is_deferred_import || IsLiteral("show") || IsLiteral("hide")) {
|
|
GrowableObjectArray& show_list =
|
|
GrowableObjectArray::Handle(Z, GrowableObjectArray::New());
|
|
GrowableObjectArray& hide_list =
|
|
GrowableObjectArray::Handle(Z, GrowableObjectArray::New());
|
|
// Libraries imported through deferred import automatically hide
|
|
// the name 'loadLibrary'.
|
|
if (is_deferred_import) {
|
|
hide_list.Add(Symbols::LoadLibrary());
|
|
}
|
|
for (;;) {
|
|
if (IsLiteral("show")) {
|
|
ConsumeToken();
|
|
ParseIdentList(&show_list);
|
|
} else if (IsLiteral("hide")) {
|
|
ConsumeToken();
|
|
ParseIdentList(&hide_list);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (show_list.Length() > 0) {
|
|
show_names = Array::MakeArray(show_list);
|
|
}
|
|
if (hide_list.Length() > 0) {
|
|
hide_names = Array::MakeArray(hide_list);
|
|
}
|
|
}
|
|
ExpectSemicolon();
|
|
|
|
// Canonicalize library URL.
|
|
const String& canon_url = String::CheckedHandle(
|
|
CallLibraryTagHandler(Dart_kCanonicalizeUrl, import_pos, url));
|
|
|
|
// Create a new library if it does not exist yet.
|
|
Library& library = Library::Handle(Z, Library::LookupLibrary(canon_url));
|
|
if (library.IsNull()) {
|
|
library = Library::New(canon_url);
|
|
library.Register();
|
|
}
|
|
|
|
// If loading hasn't been requested yet, and if this is not a deferred
|
|
// library import, call the library tag handler to request loading
|
|
// the library.
|
|
if (library.LoadNotStarted() && !is_deferred_import) {
|
|
library.SetLoadRequested();
|
|
CallLibraryTagHandler(Dart_kImportTag, import_pos, canon_url);
|
|
}
|
|
|
|
Namespace& ns = Namespace::Handle(Z,
|
|
Namespace::New(library, show_names, hide_names));
|
|
if (metadata_pos >= 0) {
|
|
ns.AddMetadata(metadata_pos, current_class());
|
|
}
|
|
|
|
if (is_import) {
|
|
// Ensure that private dart:_ libraries are only imported into dart:
|
|
// libraries.
|
|
const String& lib_url = String::Handle(Z, library_.url());
|
|
if (canon_url.StartsWith(Symbols::DartSchemePrivate()) &&
|
|
!lib_url.StartsWith(Symbols::DartScheme())) {
|
|
ReportError(import_pos, "private library is not accessible");
|
|
}
|
|
if (prefix.IsNull() || (prefix.Length() == 0)) {
|
|
ASSERT(!is_deferred_import);
|
|
library_.AddImport(ns);
|
|
} else {
|
|
LibraryPrefix& library_prefix = LibraryPrefix::Handle(Z);
|
|
library_prefix = library_.LookupLocalLibraryPrefix(prefix);
|
|
if (!library_prefix.IsNull()) {
|
|
// Check that prefix names of deferred import clauses are
|
|
// unique.
|
|
if (!is_deferred_import && library_prefix.is_deferred_load()) {
|
|
ReportError(prefix_pos,
|
|
"prefix '%s' already used in a deferred import clause",
|
|
prefix.ToCString());
|
|
}
|
|
if (is_deferred_import) {
|
|
ReportError(prefix_pos, "prefix of deferred import must be uniqe");
|
|
}
|
|
library_prefix.AddImport(ns);
|
|
} else {
|
|
library_prefix =
|
|
LibraryPrefix::New(prefix, ns, is_deferred_import, library_);
|
|
library_.AddObject(library_prefix, prefix);
|
|
}
|
|
}
|
|
} else {
|
|
ASSERT(is_export);
|
|
library_.AddExport(ns);
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::ParseLibraryPart() {
|
|
const intptr_t source_pos = TokenPos();
|
|
ConsumeToken(); // Consume "part".
|
|
CheckToken(Token::kSTRING, "url expected");
|
|
AstNode* url_literal = ParseStringLiteral(false);
|
|
ASSERT(url_literal->IsLiteralNode());
|
|
ASSERT(url_literal->AsLiteralNode()->literal().IsString());
|
|
const String& url = String::Cast(url_literal->AsLiteralNode()->literal());
|
|
ExpectSemicolon();
|
|
const String& canon_url = String::CheckedHandle(
|
|
CallLibraryTagHandler(Dart_kCanonicalizeUrl, source_pos, url));
|
|
CallLibraryTagHandler(Dart_kSourceTag, source_pos, canon_url);
|
|
}
|
|
|
|
|
|
void Parser::ParseLibraryDefinition() {
|
|
TRACE_PARSER("ParseLibraryDefinition");
|
|
|
|
// Handle the script tag.
|
|
if (CurrentToken() == Token::kSCRIPTTAG) {
|
|
// Nothing to do for script tags except to skip them.
|
|
ConsumeToken();
|
|
}
|
|
|
|
ASSERT(script_.kind() != RawScript::kSourceTag);
|
|
|
|
// We may read metadata tokens that are part of the toplevel
|
|
// declaration that follows the library definitions. Therefore, we
|
|
// need to remember the position of the last token that was
|
|
// successfully consumed.
|
|
intptr_t rewind_pos = TokenPos();
|
|
intptr_t metadata_pos = SkipMetadata();
|
|
if (CurrentToken() == Token::kLIBRARY) {
|
|
if (is_patch_source()) {
|
|
ReportError("patch cannot override library name");
|
|
}
|
|
ParseLibraryName();
|
|
if (metadata_pos >= 0) {
|
|
library_.AddLibraryMetadata(current_class(), metadata_pos);
|
|
}
|
|
rewind_pos = TokenPos();
|
|
metadata_pos = SkipMetadata();
|
|
}
|
|
while ((CurrentToken() == Token::kIMPORT) ||
|
|
(CurrentToken() == Token::kEXPORT)) {
|
|
ParseLibraryImportExport(metadata_pos);
|
|
rewind_pos = TokenPos();
|
|
metadata_pos = SkipMetadata();
|
|
}
|
|
// Core lib has not been explicitly imported, so we implicitly
|
|
// import it here.
|
|
if (!library_.ImportsCorelib()) {
|
|
Library& core_lib = Library::Handle(Z, Library::CoreLibrary());
|
|
ASSERT(!core_lib.IsNull());
|
|
const Namespace& core_ns = Namespace::Handle(Z,
|
|
Namespace::New(core_lib, Object::null_array(), Object::null_array()));
|
|
library_.AddImport(core_ns);
|
|
}
|
|
while (CurrentToken() == Token::kPART) {
|
|
ParseLibraryPart();
|
|
rewind_pos = TokenPos();
|
|
metadata_pos = SkipMetadata();
|
|
}
|
|
SetPosition(rewind_pos);
|
|
}
|
|
|
|
|
|
void Parser::ParsePartHeader() {
|
|
SkipMetadata();
|
|
CheckToken(Token::kPART, "'part of' expected");
|
|
ConsumeToken();
|
|
if (!IsLiteral("of")) {
|
|
ReportError("'part of' expected");
|
|
}
|
|
ConsumeToken();
|
|
// The VM is not required to check that the library name matches the
|
|
// name of the current library, so we ignore it.
|
|
ExpectIdentifier("library name expected");
|
|
while (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
ExpectIdentifier("malformed library name");
|
|
}
|
|
ExpectSemicolon();
|
|
}
|
|
|
|
|
|
void Parser::ParseTopLevel() {
|
|
TRACE_PARSER("ParseTopLevel");
|
|
// Collect the classes found at the top level in this growable array.
|
|
// They need to be registered with class finalization after parsing
|
|
// has been completed.
|
|
ObjectStore* object_store = I->object_store();
|
|
const GrowableObjectArray& pending_classes =
|
|
GrowableObjectArray::Handle(Z, object_store->pending_classes());
|
|
SetPosition(0);
|
|
is_top_level_ = true;
|
|
TopLevel top_level;
|
|
Class& toplevel_class = Class::Handle(Z,
|
|
Class::New(Symbols::TopLevel(), script_, TokenPos()));
|
|
toplevel_class.set_library(library_);
|
|
|
|
if (is_library_source() || is_patch_source()) {
|
|
set_current_class(toplevel_class);
|
|
ParseLibraryDefinition();
|
|
} else if (is_part_source()) {
|
|
ParsePartHeader();
|
|
}
|
|
|
|
const Class& cls = Class::Handle(Z);
|
|
while (true) {
|
|
set_current_class(cls); // No current class.
|
|
intptr_t metadata_pos = SkipMetadata();
|
|
if (CurrentToken() == Token::kCLASS) {
|
|
ParseClassDeclaration(pending_classes, toplevel_class, metadata_pos);
|
|
} else if (CurrentToken() == Token::kENUM) {
|
|
ParseEnumDeclaration(pending_classes, toplevel_class, metadata_pos);
|
|
} else if ((CurrentToken() == Token::kTYPEDEF) &&
|
|
(LookaheadToken(1) != Token::kLPAREN)) {
|
|
set_current_class(toplevel_class);
|
|
ParseTypedef(pending_classes, toplevel_class, metadata_pos);
|
|
} else if ((CurrentToken() == Token::kABSTRACT) &&
|
|
(LookaheadToken(1) == Token::kCLASS)) {
|
|
ParseClassDeclaration(pending_classes, toplevel_class, metadata_pos);
|
|
} else if (is_patch_source() && IsLiteral("patch") &&
|
|
(LookaheadToken(1) == Token::kCLASS)) {
|
|
ParseClassDeclaration(pending_classes, toplevel_class, metadata_pos);
|
|
} else {
|
|
set_current_class(toplevel_class);
|
|
if (IsVariableDeclaration()) {
|
|
ParseTopLevelVariable(&top_level, metadata_pos);
|
|
} else if (IsFunctionDeclaration()) {
|
|
ParseTopLevelFunction(&top_level, metadata_pos);
|
|
} else if (IsTopLevelAccessor()) {
|
|
ParseTopLevelAccessor(&top_level, metadata_pos);
|
|
} else if (CurrentToken() == Token::kEOS) {
|
|
break;
|
|
} else {
|
|
UnexpectedToken();
|
|
}
|
|
}
|
|
}
|
|
if ((top_level.fields.Length() > 0) || (top_level.functions.Length() > 0)) {
|
|
toplevel_class.AddFields(top_level.fields);
|
|
|
|
const Array& array = Array::Handle(Z,
|
|
Array::MakeArray(top_level.functions));
|
|
toplevel_class.SetFunctions(array);
|
|
|
|
library_.AddAnonymousClass(toplevel_class);
|
|
pending_classes.Add(toplevel_class, Heap::kOld);
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::ChainNewBlock(LocalScope* outer_scope) {
|
|
Block* block = new(Z) Block(
|
|
current_block_,
|
|
outer_scope,
|
|
new(Z) SequenceNode(TokenPos(), outer_scope));
|
|
current_block_ = block;
|
|
}
|
|
|
|
|
|
void Parser::OpenBlock() {
|
|
ASSERT(current_block_ != NULL);
|
|
LocalScope* outer_scope = current_block_->scope;
|
|
ChainNewBlock(new(Z) LocalScope(
|
|
outer_scope, outer_scope->function_level(), outer_scope->loop_level()));
|
|
}
|
|
|
|
|
|
void Parser::OpenLoopBlock() {
|
|
ASSERT(current_block_ != NULL);
|
|
LocalScope* outer_scope = current_block_->scope;
|
|
ChainNewBlock(new(Z) LocalScope(
|
|
outer_scope,
|
|
outer_scope->function_level(),
|
|
outer_scope->loop_level() + 1));
|
|
}
|
|
|
|
|
|
void Parser::OpenFunctionBlock(const Function& func) {
|
|
LocalScope* outer_scope;
|
|
if (current_block_ == NULL) {
|
|
if (!func.IsLocalFunction()) {
|
|
// We are compiling a non-nested function.
|
|
outer_scope = new(Z) LocalScope(NULL, 0, 0);
|
|
} else {
|
|
// We are compiling the function of an invoked closure.
|
|
// Restore the outer scope containing all captured variables.
|
|
const ContextScope& context_scope =
|
|
ContextScope::Handle(Z, func.context_scope());
|
|
ASSERT(!context_scope.IsNull());
|
|
outer_scope = new(Z) LocalScope(
|
|
LocalScope::RestoreOuterScope(context_scope), 0, 0);
|
|
}
|
|
} else {
|
|
// We are parsing a nested function while compiling the enclosing function.
|
|
outer_scope =
|
|
new(Z) LocalScope(current_block_->scope,
|
|
current_block_->scope->function_level() + 1,
|
|
0);
|
|
}
|
|
ChainNewBlock(outer_scope);
|
|
}
|
|
|
|
|
|
void Parser::OpenAsyncClosure() {
|
|
TRACE_PARSER("OpenAsyncClosure");
|
|
|
|
async_temp_scope_ = current_block_->scope;
|
|
|
|
OpenAsyncTryBlock();
|
|
}
|
|
|
|
|
|
SequenceNode* Parser::CloseAsyncTryBlock(SequenceNode* try_block) {
|
|
try_blocks_list_->enter_catch();
|
|
|
|
OpenBlock();
|
|
OpenBlock();
|
|
const AbstractType& dynamic_type =
|
|
AbstractType::ZoneHandle(Z, Type::DynamicType());
|
|
CatchParamDesc exception_param;
|
|
CatchParamDesc stack_trace_param;
|
|
exception_param.token_pos = Scanner::kNoSourcePos;
|
|
exception_param.type = &dynamic_type;
|
|
exception_param.name = &Symbols::ExceptionParameter();
|
|
stack_trace_param.token_pos = Scanner::kNoSourcePos;
|
|
stack_trace_param.type = &dynamic_type;
|
|
stack_trace_param.name = &Symbols::StackTraceParameter();
|
|
|
|
AddCatchParamsToScope(
|
|
&exception_param, &stack_trace_param, current_block_->scope);
|
|
|
|
LocalVariable* context_var = current_block_->scope->LookupVariable(
|
|
Symbols::SavedTryContextVar(), false);
|
|
ASSERT(context_var != NULL);
|
|
LocalVariable* exception_var = current_block_->scope->LookupVariable(
|
|
Symbols::ExceptionVar(), false);
|
|
if (exception_param.var != NULL) {
|
|
// Generate code to load the exception object (:exception_var) into
|
|
// the exception variable specified in this block.
|
|
ASSERT(exception_var != NULL);
|
|
current_block_->statements->Add(new(Z) StoreLocalNode(
|
|
Scanner::kNoSourcePos,
|
|
exception_param.var,
|
|
new(Z) LoadLocalNode(Scanner::kNoSourcePos, exception_var)));
|
|
}
|
|
LocalVariable* stack_trace_var =
|
|
current_block_->scope->LookupVariable(Symbols::StackTraceVar(), false);
|
|
if (stack_trace_param.var != NULL) {
|
|
// A stack trace variable is specified in this block, so generate code
|
|
// to load the stack trace object (:stack_trace_var) into the stack
|
|
// trace variable specified in this block.
|
|
ArgumentListNode* no_args = new(Z) ArgumentListNode(Scanner::kNoSourcePos);
|
|
ASSERT(stack_trace_var != NULL);
|
|
current_block_->statements->Add(new(Z) StoreLocalNode(
|
|
Scanner::kNoSourcePos,
|
|
stack_trace_param.var,
|
|
new(Z) LoadLocalNode(Scanner::kNoSourcePos, stack_trace_var)));
|
|
current_block_->statements->Add(new(Z) InstanceCallNode(
|
|
Scanner::kNoSourcePos,
|
|
new(Z) LoadLocalNode(Scanner::kNoSourcePos, stack_trace_param.var),
|
|
Library::PrivateCoreLibName(Symbols::_setupFullStackTrace()),
|
|
no_args));
|
|
}
|
|
|
|
ASSERT(try_blocks_list_ != NULL);
|
|
if (innermost_function().is_async_closure() ||
|
|
innermost_function().IsAsyncFunction()) {
|
|
if ((try_blocks_list_->outer_try_block() != NULL) &&
|
|
(try_blocks_list_->outer_try_block()->try_block()
|
|
->scope->function_level() ==
|
|
current_block_->scope->function_level())) {
|
|
// We need to unchain three scope levels: catch clause, catch
|
|
// parameters, and the general try block.
|
|
RestoreSavedTryContext(
|
|
current_block_->scope->parent()->parent()->parent(),
|
|
try_blocks_list_->outer_try_block()->try_index(),
|
|
current_block_->statements);
|
|
} else {
|
|
parsed_function()->reset_saved_try_ctx_vars();
|
|
}
|
|
}
|
|
|
|
// Complete the async future with an error.
|
|
// Since we control the catch block there is no need to generate a nested
|
|
// if/then/else.
|
|
LocalVariable* async_completer = current_block_->scope->LookupVariable(
|
|
Symbols::AsyncCompleter(), false);
|
|
ASSERT(async_completer != NULL);
|
|
ArgumentListNode* completer_args =
|
|
new (Z) ArgumentListNode(Scanner::kNoSourcePos);
|
|
completer_args->Add(
|
|
new (Z) LoadLocalNode(Scanner::kNoSourcePos, exception_param.var));
|
|
completer_args->Add(
|
|
new (Z) LoadLocalNode(Scanner::kNoSourcePos, stack_trace_param.var));
|
|
current_block_->statements->Add(new (Z) InstanceCallNode(
|
|
Scanner::kNoSourcePos,
|
|
new (Z) LoadLocalNode(Scanner::kNoSourcePos, async_completer),
|
|
Symbols::CompleterCompleteError(),
|
|
completer_args));
|
|
ReturnNode* return_node = new (Z) ReturnNode(Scanner::kNoSourcePos);
|
|
// Behavior like a continuation return, i.e,. don't call a completer.
|
|
return_node->set_return_type(ReturnNode::kContinuation);
|
|
current_block_->statements->Add(return_node);
|
|
AstNode* catch_block = CloseBlock();
|
|
current_block_->statements->Add(catch_block);
|
|
SequenceNode* catch_handler_list = CloseBlock();
|
|
|
|
const GrowableObjectArray& handler_types =
|
|
GrowableObjectArray::Handle(Z, GrowableObjectArray::New());
|
|
handler_types.SetLength(0);
|
|
handler_types.Add(*exception_param.type);
|
|
|
|
TryBlocks* inner_try_block = PopTryBlock();
|
|
const intptr_t try_index = inner_try_block->try_index();
|
|
|
|
CatchClauseNode* catch_clause = new (Z) CatchClauseNode(
|
|
Scanner::kNoSourcePos,
|
|
catch_handler_list,
|
|
Array::ZoneHandle(Z, Array::MakeArray(handler_types)),
|
|
context_var,
|
|
exception_var,
|
|
stack_trace_var,
|
|
CatchClauseNode::kInvalidTryIndex,
|
|
true);
|
|
AstNode* try_catch_node = new (Z) TryCatchNode(
|
|
Scanner::kNoSourcePos,
|
|
try_block,
|
|
context_var,
|
|
catch_clause,
|
|
NULL,
|
|
try_index);
|
|
current_block_->statements->Add(try_catch_node);
|
|
return CloseBlock();
|
|
}
|
|
|
|
|
|
void Parser::OpenAsyncTryBlock() {
|
|
// Manually wrapping the actual body into a try/catch block.
|
|
LocalVariable* context_var =
|
|
current_block_->scope->LocalLookupVariable(Symbols::SavedTryContextVar());
|
|
if (context_var == NULL) {
|
|
context_var = new(Z) LocalVariable(
|
|
TokenPos(),
|
|
Symbols::SavedTryContextVar(),
|
|
Type::ZoneHandle(Z, Type::DynamicType()));
|
|
current_block_->scope->AddVariable(context_var);
|
|
}
|
|
LocalVariable* exception_var =
|
|
current_block_->scope->LocalLookupVariable(Symbols::ExceptionVar());
|
|
if (exception_var == NULL) {
|
|
exception_var = new(Z) LocalVariable(
|
|
TokenPos(),
|
|
Symbols::ExceptionVar(),
|
|
Type::ZoneHandle(Z, Type::DynamicType()));
|
|
current_block_->scope->AddVariable(exception_var);
|
|
}
|
|
LocalVariable* stack_trace_var =
|
|
current_block_->scope->LocalLookupVariable(Symbols::StackTraceVar());
|
|
if (stack_trace_var == NULL) {
|
|
stack_trace_var = new(Z) LocalVariable(
|
|
TokenPos(),
|
|
Symbols::StackTraceVar(),
|
|
Type::ZoneHandle(Z, Type::DynamicType()));
|
|
current_block_->scope->AddVariable(stack_trace_var);
|
|
}
|
|
|
|
// Open the try block.
|
|
OpenBlock();
|
|
PushTryBlock(current_block_);
|
|
|
|
if (innermost_function().is_async_closure() ||
|
|
innermost_function().IsAsyncFunction()) {
|
|
SetupSavedTryContext(context_var);
|
|
}
|
|
}
|
|
|
|
|
|
RawFunction* Parser::OpenAsyncFunction(intptr_t async_func_pos) {
|
|
TRACE_PARSER("OpenAsyncFunction");
|
|
AddAsyncClosureVariables();
|
|
Function& closure = Function::Handle(Z);
|
|
bool is_new_closure = false;
|
|
|
|
// Check whether a function for the asynchronous function body of
|
|
// this async function has already been created by a previous
|
|
// compilation of this function.
|
|
const Function& found_func = Function::Handle(
|
|
Z, current_class().LookupClosureFunction(async_func_pos));
|
|
if (!found_func.IsNull() &&
|
|
(found_func.token_pos() == async_func_pos) &&
|
|
(found_func.script() == innermost_function().script()) &&
|
|
(found_func.parent_function() == innermost_function().raw())) {
|
|
ASSERT(found_func.is_async_closure());
|
|
closure = found_func.raw();
|
|
} else {
|
|
// Create the closure containing the body of this async function.
|
|
const String& async_func_name =
|
|
String::Handle(Z, innermost_function().name());
|
|
String& closure_name = String::Handle(Z,
|
|
String::NewFormatted("<%s_async_body>", async_func_name.ToCString()));
|
|
closure = Function::NewClosureFunction(
|
|
String::Handle(Z, Symbols::New(closure_name)),
|
|
innermost_function(),
|
|
async_func_pos);
|
|
closure.set_is_async_closure(true);
|
|
closure.set_result_type(AbstractType::Handle(Type::DynamicType()));
|
|
is_new_closure = true;
|
|
}
|
|
// Create the parameter list for the async body closure.
|
|
ParamList closure_params;
|
|
const Type& dynamic_type = Type::ZoneHandle(Z, Type::DynamicType());
|
|
closure_params.AddFinalParameter(
|
|
async_func_pos, &Symbols::ClosureParameter(), &dynamic_type);
|
|
ParamDesc result_param;
|
|
result_param.name = &Symbols::AsyncOperationParam();
|
|
result_param.default_value = &Object::null_instance();
|
|
result_param.type = &dynamic_type;
|
|
closure_params.parameters->Add(result_param);
|
|
ParamDesc error_param;
|
|
error_param.name = &Symbols::AsyncOperationErrorParam();
|
|
error_param.default_value = &Object::null_instance();
|
|
error_param.type = &dynamic_type;
|
|
closure_params.parameters->Add(error_param);
|
|
ParamDesc stack_trace_param;
|
|
stack_trace_param.name = &Symbols::AsyncOperationStackTraceParam();
|
|
stack_trace_param.default_value = &Object::null_instance();
|
|
stack_trace_param.type = &dynamic_type;
|
|
closure_params.parameters->Add(stack_trace_param);
|
|
closure_params.has_optional_positional_parameters = true;
|
|
closure_params.num_optional_parameters += 3;
|
|
|
|
if (is_new_closure) {
|
|
// Add the parameters to the newly created closure.
|
|
AddFormalParamsToFunction(&closure_params, closure);
|
|
|
|
// Create and set the signature class of the closure.
|
|
const String& sig = String::Handle(Z, closure.Signature());
|
|
Class& sig_cls = Class::Handle(Z, library_.LookupLocalClass(sig));
|
|
if (sig_cls.IsNull()) {
|
|
sig_cls =
|
|
Class::NewSignatureClass(sig, closure, script_, closure.token_pos());
|
|
library_.AddClass(sig_cls);
|
|
}
|
|
closure.set_signature_class(sig_cls);
|
|
const Type& sig_type = Type::Handle(Z, sig_cls.SignatureType());
|
|
if (!sig_type.IsFinalized()) {
|
|
ClassFinalizer::FinalizeType(
|
|
sig_cls, sig_type, ClassFinalizer::kCanonicalize);
|
|
}
|
|
ASSERT(AbstractType::Handle(Z, closure.result_type()).IsResolved());
|
|
ASSERT(closure.NumParameters() == closure_params.parameters->length());
|
|
}
|
|
OpenFunctionBlock(closure);
|
|
AddFormalParamsToScope(&closure_params, current_block_->scope);
|
|
OpenBlock();
|
|
async_temp_scope_ = current_block_->scope;
|
|
return closure.raw();
|
|
}
|
|
|
|
|
|
void Parser::AddAsyncClosureVariables() {
|
|
// Add to AST:
|
|
// var :await_jump_var;
|
|
// var :await_ctx_var;
|
|
// var :async_op;
|
|
// var :async_completer;
|
|
const Type& dynamic_type = Type::ZoneHandle(Z, Type::DynamicType());
|
|
LocalVariable* await_jump_var = new (Z) LocalVariable(
|
|
Scanner::kNoSourcePos, Symbols::AwaitJumpVar(), dynamic_type);
|
|
current_block_->scope->AddVariable(await_jump_var);
|
|
current_block_->scope->CaptureVariable(Symbols::AwaitJumpVar());
|
|
await_jump_var->set_is_captured();
|
|
LocalVariable* await_ctx_var = new (Z) LocalVariable(
|
|
Scanner::kNoSourcePos, Symbols::AwaitContextVar(), dynamic_type);
|
|
current_block_->scope->AddVariable(await_ctx_var);
|
|
current_block_->scope->CaptureVariable(Symbols::AwaitContextVar());
|
|
await_ctx_var->set_is_captured();
|
|
LocalVariable* async_op_var = new (Z) LocalVariable(
|
|
Scanner::kNoSourcePos, Symbols::AsyncOperation(), dynamic_type);
|
|
current_block_->scope->AddVariable(async_op_var);
|
|
current_block_->scope->CaptureVariable(Symbols::AsyncOperation());
|
|
async_op_var->set_is_captured();
|
|
LocalVariable* async_completer = new (Z) LocalVariable(
|
|
Scanner::kNoSourcePos, Symbols::AsyncCompleter(), dynamic_type);
|
|
current_block_->scope->AddVariable(async_completer);
|
|
current_block_->scope->CaptureVariable(Symbols::AsyncCompleter());
|
|
async_completer->set_is_captured();
|
|
}
|
|
|
|
|
|
SequenceNode* Parser::CloseBlock() {
|
|
SequenceNode* statements = current_block_->statements;
|
|
if (current_block_->scope != NULL) {
|
|
// Record the begin and end token index of the scope.
|
|
ASSERT(statements != NULL);
|
|
current_block_->scope->set_begin_token_pos(statements->token_pos());
|
|
current_block_->scope->set_end_token_pos(TokenPos());
|
|
}
|
|
current_block_ = current_block_->parent;
|
|
return statements;
|
|
}
|
|
|
|
|
|
static inline String& BuildAsyncSavedTryContextName(Zone* zone,
|
|
int16_t id) {
|
|
const char* async_saved_prefix = ":async_saved_try_ctx_var_";
|
|
// Can be a regular handle since we only use it to build an actual symbol.
|
|
const String& cnt_str = String::Handle(zone,
|
|
String::NewFormatted("%s%d", async_saved_prefix, id));
|
|
return String::ZoneHandle(zone, Symbols::New(cnt_str));
|
|
}
|
|
|
|
|
|
SequenceNode* Parser::CloseAsyncFunction(const Function& closure,
|
|
SequenceNode* closure_body) {
|
|
TRACE_PARSER("CloseAsyncFunction");
|
|
ASSERT(!closure.IsNull());
|
|
ASSERT(closure_body != NULL);
|
|
// The block for the async closure body has already been closed. Close the
|
|
// corresponding function block.
|
|
CloseBlock();
|
|
|
|
closure_body->scope()->LookupVariable(Symbols::AwaitJumpVar(), false);
|
|
closure_body->scope()->LookupVariable(Symbols::AwaitContextVar(), false);
|
|
closure_body->scope()->CaptureVariable(Symbols::AsyncCompleter());
|
|
|
|
// Create and return a new future that executes a closure with the current
|
|
// body.
|
|
|
|
// No need to capture parameters or other variables, since they have already
|
|
// been captured in the corresponding scope as the body has been parsed within
|
|
// a nested block (contained in the async funtion's block).
|
|
const Class& future =
|
|
Class::ZoneHandle(Z, I->object_store()->future_class());
|
|
ASSERT(!future.IsNull());
|
|
const Function& constructor = Function::ZoneHandle(Z,
|
|
future.LookupFunction(Symbols::FutureMicrotask()));
|
|
ASSERT(!constructor.IsNull());
|
|
const Class& completer =
|
|
Class::ZoneHandle(Z, I->object_store()->completer_class());
|
|
ASSERT(!completer.IsNull());
|
|
const Function& completer_constructor = Function::ZoneHandle(Z,
|
|
completer.LookupFunction(Symbols::CompleterConstructor()));
|
|
ASSERT(!completer_constructor.IsNull());
|
|
|
|
LocalVariable* async_completer = current_block_->scope->LookupVariable(
|
|
Symbols::AsyncCompleter(), false);
|
|
|
|
// Add to AST:
|
|
// :async_completer = new Completer();
|
|
ArgumentListNode* empty_args =
|
|
new (Z) ArgumentListNode(Scanner::kNoSourcePos);
|
|
ConstructorCallNode* completer_constructor_node = new (Z) ConstructorCallNode(
|
|
Scanner::kNoSourcePos,
|
|
TypeArguments::ZoneHandle(Z),
|
|
completer_constructor,
|
|
empty_args);
|
|
StoreLocalNode* store_completer = new (Z) StoreLocalNode(
|
|
Scanner::kNoSourcePos,
|
|
async_completer,
|
|
completer_constructor_node);
|
|
current_block_->statements->Add(store_completer);
|
|
|
|
// :await_jump_var = -1;
|
|
LocalVariable* jump_var = current_block_->scope->LookupVariable(
|
|
Symbols::AwaitJumpVar(), false);
|
|
LiteralNode* init_value =
|
|
new(Z) LiteralNode(Scanner::kNoSourcePos, Smi::ZoneHandle(Smi::New(-1)));
|
|
current_block_->statements->Add(
|
|
new (Z) StoreLocalNode(Scanner::kNoSourcePos, jump_var, init_value));
|
|
|
|
// Add to AST:
|
|
// :async_op = <closure>; (containing the original body)
|
|
LocalVariable* async_op_var = current_block_->scope->LookupVariable(
|
|
Symbols::AsyncOperation(), false);
|
|
ClosureNode* cn = new(Z) ClosureNode(
|
|
Scanner::kNoSourcePos, closure, NULL, closure_body->scope());
|
|
StoreLocalNode* store_async_op = new (Z) StoreLocalNode(
|
|
Scanner::kNoSourcePos,
|
|
async_op_var,
|
|
cn);
|
|
current_block_->statements->Add(store_async_op);
|
|
|
|
// Add to AST:
|
|
// new Future(:async_op);
|
|
ArgumentListNode* arguments = new (Z) ArgumentListNode(Scanner::kNoSourcePos);
|
|
arguments->Add(new (Z) LoadLocalNode(
|
|
Scanner::kNoSourcePos, async_op_var));
|
|
ConstructorCallNode* future_node = new (Z) ConstructorCallNode(
|
|
Scanner::kNoSourcePos, TypeArguments::ZoneHandle(Z), constructor,
|
|
arguments);
|
|
current_block_->statements->Add(future_node);
|
|
|
|
// Add to AST:
|
|
// return :async_completer.future;
|
|
ReturnNode* return_node = new (Z) ReturnNode(
|
|
Scanner::kNoSourcePos,
|
|
new (Z) InstanceGetterNode(
|
|
Scanner::kNoSourcePos,
|
|
new (Z) LoadLocalNode(
|
|
Scanner::kNoSourcePos,
|
|
async_completer),
|
|
Symbols::CompleterFuture()));
|
|
current_block_->statements->Add(return_node);
|
|
return CloseBlock();
|
|
}
|
|
|
|
|
|
SequenceNode* Parser::CloseAsyncClosure(SequenceNode* body) {
|
|
TRACE_PARSER("CloseAsyncClosure");
|
|
|
|
// We need a temporary expression to store intermediate return values.
|
|
parsed_function()->EnsureExpressionTemp();
|
|
// Implicitly mark those variables below as captured. We currently mark all
|
|
// variables of all scopes as captured (below), but as soon as we do something
|
|
// smarter we rely on these internal variables to be available.
|
|
SequenceNode* new_body = CloseAsyncTryBlock(body);
|
|
ASSERT(new_body != NULL);
|
|
ASSERT(new_body->scope() != NULL);
|
|
new_body->scope()->LookupVariable(Symbols::AwaitJumpVar(), false);
|
|
new_body->scope()->LookupVariable(Symbols::AwaitContextVar(), false);
|
|
new_body->scope()->LookupVariable(Symbols::AsyncCompleter(), false);
|
|
new_body->scope()->RecursivelyCaptureAllVariables();
|
|
return new_body;
|
|
}
|
|
|
|
|
|
// Set up default values for all optional parameters to the function.
|
|
void Parser::SetupDefaultsForOptionalParams(const ParamList* params,
|
|
Array* default_values) {
|
|
if (params->num_optional_parameters > 0) {
|
|
// Build array of default parameter values.
|
|
ParamDesc* param =
|
|
params->parameters->data() + params->num_fixed_parameters;
|
|
*default_values = Array::New(params->num_optional_parameters);
|
|
for (int i = 0; i < params->num_optional_parameters; i++) {
|
|
ASSERT(param->default_value != NULL);
|
|
default_values->SetAt(i, *param->default_value);
|
|
param++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Populate the parameter type array and parameter name array of the function
|
|
// with the formal parameter types and names.
|
|
void Parser::AddFormalParamsToFunction(const ParamList* params,
|
|
const Function& func) {
|
|
ASSERT((params != NULL) && (params->parameters != NULL));
|
|
ASSERT((params->num_optional_parameters > 0) ==
|
|
(params->has_optional_positional_parameters ||
|
|
params->has_optional_named_parameters));
|
|
if (!Utils::IsInt(16, params->num_fixed_parameters) ||
|
|
!Utils::IsInt(16, params->num_optional_parameters)) {
|
|
const Script& script = Script::Handle(Class::Handle(func.Owner()).script());
|
|
Report::MessageF(Report::kError, script, func.token_pos(),
|
|
"too many formal parameters");
|
|
}
|
|
func.set_num_fixed_parameters(params->num_fixed_parameters);
|
|
func.SetNumOptionalParameters(params->num_optional_parameters,
|
|
params->has_optional_positional_parameters);
|
|
const int num_parameters = params->parameters->length();
|
|
ASSERT(num_parameters == func.NumParameters());
|
|
func.set_parameter_types(Array::Handle(Array::New(num_parameters,
|
|
Heap::kOld)));
|
|
func.set_parameter_names(Array::Handle(Array::New(num_parameters,
|
|
Heap::kOld)));
|
|
for (int i = 0; i < num_parameters; i++) {
|
|
ParamDesc& param_desc = (*params->parameters)[i];
|
|
func.SetParameterTypeAt(i, *param_desc.type);
|
|
func.SetParameterNameAt(i, *param_desc.name);
|
|
}
|
|
}
|
|
|
|
|
|
// Populate local scope with the formal parameters.
|
|
void Parser::AddFormalParamsToScope(const ParamList* params,
|
|
LocalScope* scope) {
|
|
ASSERT((params != NULL) && (params->parameters != NULL));
|
|
ASSERT(scope != NULL);
|
|
const int num_parameters = params->parameters->length();
|
|
for (int i = 0; i < num_parameters; i++) {
|
|
ParamDesc& param_desc = (*params->parameters)[i];
|
|
ASSERT(!is_top_level_ || param_desc.type->IsResolved());
|
|
const String* name = param_desc.name;
|
|
LocalVariable* parameter = new(Z) LocalVariable(
|
|
param_desc.name_pos, *name, *param_desc.type);
|
|
if (!scope->InsertParameterAt(i, parameter)) {
|
|
ReportError(param_desc.name_pos,
|
|
"name '%s' already exists in scope",
|
|
param_desc.name->ToCString());
|
|
}
|
|
param_desc.var = parameter;
|
|
if (param_desc.is_final) {
|
|
parameter->set_is_final();
|
|
}
|
|
if (param_desc.is_field_initializer) {
|
|
parameter->set_invisible(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Builds ReturnNode/NativeBodyNode for a native function.
|
|
void Parser::ParseNativeFunctionBlock(const ParamList* params,
|
|
const Function& func) {
|
|
ASSERT(func.is_native());
|
|
TRACE_PARSER("ParseNativeFunctionBlock");
|
|
const Class& cls = Class::Handle(Z, func.Owner());
|
|
const Library& library = Library::Handle(Z, cls.library());
|
|
ASSERT(func.NumParameters() == params->parameters->length());
|
|
|
|
// Parse the function name out.
|
|
const intptr_t native_pos = TokenPos();
|
|
const String& native_name = ParseNativeDeclaration();
|
|
|
|
// Now resolve the native function to the corresponding native entrypoint.
|
|
const int num_params = NativeArguments::ParameterCountForResolution(func);
|
|
bool auto_setup_scope = true;
|
|
NativeFunction native_function = NativeEntry::ResolveNative(
|
|
library, native_name, num_params, &auto_setup_scope);
|
|
if (native_function == NULL) {
|
|
ReportError(native_pos,
|
|
"native function '%s' (%" Pd " arguments) cannot be found",
|
|
native_name.ToCString(), func.NumParameters());
|
|
}
|
|
func.SetIsNativeAutoSetupScope(auto_setup_scope);
|
|
|
|
// Now add the NativeBodyNode and return statement.
|
|
Dart_NativeEntryResolver resolver = library.native_entry_resolver();
|
|
bool is_bootstrap_native = Bootstrap::IsBootstapResolver(resolver);
|
|
current_block_->statements->Add(new(Z) ReturnNode(
|
|
TokenPos(),
|
|
new(Z) NativeBodyNode(
|
|
TokenPos(),
|
|
Function::ZoneHandle(Z, func.raw()),
|
|
native_name,
|
|
native_function,
|
|
current_block_->scope,
|
|
is_bootstrap_native)));
|
|
}
|
|
|
|
|
|
LocalVariable* Parser::LookupReceiver(LocalScope* from_scope, bool test_only) {
|
|
ASSERT(!current_function().is_static());
|
|
return from_scope->LookupVariable(Symbols::This(), test_only);
|
|
}
|
|
|
|
|
|
LocalVariable* Parser::LookupTypeArgumentsParameter(LocalScope* from_scope,
|
|
bool test_only) {
|
|
ASSERT(current_function().IsInFactoryScope());
|
|
return from_scope->LookupVariable(Symbols::TypeArgumentsParameter(),
|
|
test_only);
|
|
}
|
|
|
|
|
|
LocalVariable* Parser::LookupPhaseParameter() {
|
|
const bool kTestOnly = false;
|
|
return current_block_->scope->LookupVariable(Symbols::PhaseParameter(),
|
|
kTestOnly);
|
|
}
|
|
|
|
|
|
void Parser::CaptureInstantiator() {
|
|
ASSERT(current_block_->scope->function_level() > 0);
|
|
bool found = false;
|
|
if (current_function().IsInFactoryScope()) {
|
|
found = current_block_->scope->CaptureVariable(
|
|
Symbols::TypeArgumentsParameter());
|
|
} else {
|
|
found = current_block_->scope->CaptureVariable(Symbols::This());
|
|
}
|
|
ASSERT(found);
|
|
}
|
|
|
|
|
|
AstNode* Parser::LoadReceiver(intptr_t token_pos) {
|
|
// A nested function may access 'this', referring to the receiver of the
|
|
// outermost enclosing function.
|
|
const bool kTestOnly = false;
|
|
LocalVariable* receiver = LookupReceiver(current_block_->scope, kTestOnly);
|
|
if (receiver == NULL) {
|
|
ReportError(token_pos, "illegal implicit access to receiver 'this'");
|
|
}
|
|
return new(Z) LoadLocalNode(TokenPos(), receiver);
|
|
}
|
|
|
|
|
|
AstNode* Parser::CallGetter(intptr_t token_pos,
|
|
AstNode* object,
|
|
const String& name) {
|
|
return new(Z) InstanceGetterNode(token_pos, object, name);
|
|
}
|
|
|
|
|
|
// Returns ast nodes of the variable initialization.
|
|
AstNode* Parser::ParseVariableDeclaration(const AbstractType& type,
|
|
bool is_final,
|
|
bool is_const,
|
|
SequenceNode** await_preamble) {
|
|
TRACE_PARSER("ParseVariableDeclaration");
|
|
ASSERT(IsIdentifier());
|
|
const intptr_t ident_pos = TokenPos();
|
|
const String& ident = *CurrentLiteral();
|
|
LocalVariable* variable = new(Z) LocalVariable(
|
|
ident_pos, ident, type);
|
|
ConsumeToken(); // Variable identifier.
|
|
AstNode* initialization = NULL;
|
|
if (CurrentToken() == Token::kASSIGN) {
|
|
// Variable initialization.
|
|
const intptr_t assign_pos = TokenPos();
|
|
ConsumeToken();
|
|
AstNode* expr = ParseAwaitableExpr(
|
|
is_const, kConsumeCascades, await_preamble);
|
|
initialization = new(Z) StoreLocalNode(
|
|
assign_pos, variable, expr);
|
|
if (is_const) {
|
|
ASSERT(expr->IsLiteralNode());
|
|
variable->SetConstValue(expr->AsLiteralNode()->literal());
|
|
}
|
|
} else if (is_final || is_const) {
|
|
ReportError(ident_pos,
|
|
"missing initialization of 'final' or 'const' variable");
|
|
} else {
|
|
// Initialize variable with null.
|
|
AstNode* null_expr = new(Z) LiteralNode(
|
|
ident_pos, Instance::ZoneHandle(Z));
|
|
initialization = new(Z) StoreLocalNode(
|
|
ident_pos, variable, null_expr);
|
|
}
|
|
|
|
ASSERT(current_block_ != NULL);
|
|
const intptr_t previous_pos =
|
|
current_block_->scope->PreviousReferencePos(ident);
|
|
if (previous_pos >= 0) {
|
|
ASSERT(!script_.IsNull());
|
|
if (previous_pos > ident_pos) {
|
|
ReportError(ident_pos,
|
|
"initializer of '%s' may not refer to itself",
|
|
ident.ToCString());
|
|
|
|
} else {
|
|
intptr_t line_number;
|
|
script_.GetTokenLocation(previous_pos, &line_number, NULL);
|
|
ReportError(ident_pos,
|
|
"identifier '%s' previously used in line %" Pd "",
|
|
ident.ToCString(),
|
|
line_number);
|
|
}
|
|
}
|
|
|
|
// Add variable to scope after parsing the initalizer expression.
|
|
// The expression must not be able to refer to the variable.
|
|
if (!current_block_->scope->AddVariable(variable)) {
|
|
LocalVariable* existing_var =
|
|
current_block_->scope->LookupVariable(variable->name(), true);
|
|
ASSERT(existing_var != NULL);
|
|
if (existing_var->owner() == current_block_->scope) {
|
|
ReportError(ident_pos, "identifier '%s' already defined",
|
|
variable->name().ToCString());
|
|
} else {
|
|
ReportError(ident_pos, "'%s' from outer scope has already been used, "
|
|
"cannot redefine",
|
|
variable->name().ToCString());
|
|
}
|
|
}
|
|
if (is_final || is_const) {
|
|
variable->set_is_final();
|
|
}
|
|
return initialization;
|
|
}
|
|
|
|
|
|
// Parses ('var' | 'final' [type] | 'const' [type] | type).
|
|
// The presence of 'final' or 'const' must be detected and remembered
|
|
// before the call. If a type is parsed, it may be resolved and finalized
|
|
// according to the given type finalization mode.
|
|
RawAbstractType* Parser::ParseConstFinalVarOrType(
|
|
ClassFinalizer::FinalizationKind finalization) {
|
|
TRACE_PARSER("ParseConstFinalVarOrType");
|
|
if (CurrentToken() == Token::kVAR) {
|
|
ConsumeToken();
|
|
return Type::DynamicType();
|
|
}
|
|
bool type_is_optional = false;
|
|
if ((CurrentToken() == Token::kFINAL) || (CurrentToken() == Token::kCONST)) {
|
|
ConsumeToken();
|
|
type_is_optional = true;
|
|
}
|
|
if (CurrentToken() != Token::kIDENT) {
|
|
if (type_is_optional) {
|
|
return Type::DynamicType();
|
|
} else {
|
|
ReportError("type name expected");
|
|
}
|
|
}
|
|
if (type_is_optional) {
|
|
Token::Kind follower = LookaheadToken(1);
|
|
// We have an identifier followed by a 'follower' token.
|
|
// We either parse a type or return now.
|
|
if ((follower != Token::kLT) && // Parameterized type.
|
|
(follower != Token::kPERIOD) && // Qualified class name of type.
|
|
!Token::IsIdentifier(follower) && // Variable name following a type.
|
|
(follower != Token::kTHIS)) { // Field parameter following a type.
|
|
return Type::DynamicType();
|
|
}
|
|
}
|
|
return ParseType(finalization);
|
|
}
|
|
|
|
|
|
// Returns ast nodes of the variable initialization. Variables without an
|
|
// explicit initializer are initialized to null. If several variables are
|
|
// declared, the individual initializers are collected in a sequence node.
|
|
AstNode* Parser::ParseVariableDeclarationList() {
|
|
TRACE_PARSER("ParseVariableDeclarationList");
|
|
SkipMetadata();
|
|
bool is_final = (CurrentToken() == Token::kFINAL);
|
|
bool is_const = (CurrentToken() == Token::kCONST);
|
|
const AbstractType& type = AbstractType::ZoneHandle(Z,
|
|
ParseConstFinalVarOrType(FLAG_enable_type_checks ?
|
|
ClassFinalizer::kCanonicalize : ClassFinalizer::kIgnore));
|
|
if (!IsIdentifier()) {
|
|
ReportError("identifier expected");
|
|
}
|
|
|
|
SequenceNode* preamble = NULL;
|
|
AstNode* initializers =
|
|
ParseVariableDeclaration(type, is_final, is_const, &preamble);
|
|
ASSERT(initializers != NULL);
|
|
if (preamble != NULL) {
|
|
preamble->Add(initializers);
|
|
initializers = preamble;
|
|
}
|
|
while (CurrentToken() == Token::kCOMMA) {
|
|
ConsumeToken();
|
|
if (!IsIdentifier()) {
|
|
ReportError("identifier expected after comma");
|
|
}
|
|
// We have a second initializer. Allocate a sequence node now.
|
|
// The sequence does not own the current scope. Set its own scope to NULL.
|
|
SequenceNode* sequence = NodeAsSequenceNode(initializers->token_pos(),
|
|
initializers,
|
|
NULL);
|
|
preamble = NULL;
|
|
AstNode* declaration = ParseVariableDeclaration(
|
|
type, is_final, is_const, &preamble);
|
|
if (preamble != NULL) {
|
|
sequence->Add(preamble);
|
|
}
|
|
sequence->Add(declaration);
|
|
initializers = sequence;
|
|
}
|
|
return initializers;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseFunctionStatement(bool is_literal) {
|
|
TRACE_PARSER("ParseFunctionStatement");
|
|
AbstractType& result_type = AbstractType::Handle(Z);
|
|
const String* variable_name = NULL;
|
|
const String* function_name = NULL;
|
|
|
|
result_type = Type::DynamicType();
|
|
|
|
const intptr_t function_pos = TokenPos();
|
|
if (is_literal) {
|
|
ASSERT(CurrentToken() == Token::kLPAREN);
|
|
function_name = &Symbols::AnonymousClosure();
|
|
} else {
|
|
if (CurrentToken() == Token::kVOID) {
|
|
ConsumeToken();
|
|
result_type = Type::VoidType();
|
|
} else if ((CurrentToken() == Token::kIDENT) &&
|
|
(LookaheadToken(1) != Token::kLPAREN)) {
|
|
result_type = ParseType(ClassFinalizer::kCanonicalize);
|
|
}
|
|
const intptr_t name_pos = TokenPos();
|
|
variable_name = ExpectIdentifier("function name expected");
|
|
function_name = variable_name;
|
|
|
|
// Check that the function name has not been referenced
|
|
// before this declaration.
|
|
ASSERT(current_block_ != NULL);
|
|
const intptr_t previous_pos =
|
|
current_block_->scope->PreviousReferencePos(*function_name);
|
|
if (previous_pos >= 0) {
|
|
ASSERT(!script_.IsNull());
|
|
intptr_t line_number;
|
|
script_.GetTokenLocation(previous_pos, &line_number, NULL);
|
|
ReportError(name_pos,
|
|
"identifier '%s' previously used in line %" Pd "",
|
|
function_name->ToCString(),
|
|
line_number);
|
|
}
|
|
}
|
|
CheckToken(Token::kLPAREN);
|
|
|
|
// Check whether we have parsed this closure function before, in a previous
|
|
// compilation. If so, reuse the function object, else create a new one
|
|
// and register it in the current class.
|
|
// Note that we cannot share the same closure function between the closurized
|
|
// and non-closurized versions of the same parent function.
|
|
Function& function = Function::ZoneHandle(Z);
|
|
bool is_new_closure = false;
|
|
// TODO(hausner): There could be two different closures at the given
|
|
// function_pos, one enclosed in a closurized function and one enclosed in the
|
|
// non-closurized version of this same function.
|
|
function = current_class().LookupClosureFunction(function_pos);
|
|
if (function.IsNull() || (function.token_pos() != function_pos) ||
|
|
(function.parent_function() != innermost_function().raw())) {
|
|
// The function will be registered in the lookup table by the
|
|
// EffectGraphVisitor::VisitClosureNode when the newly allocated closure
|
|
// function has been properly setup.
|
|
is_new_closure = true;
|
|
function = Function::NewClosureFunction(*function_name,
|
|
innermost_function(),
|
|
function_pos);
|
|
function.set_result_type(result_type);
|
|
}
|
|
|
|
// The function type needs to be finalized at compile time, since the closure
|
|
// may be type checked at run time when assigned to a function variable,
|
|
// passed as a function argument, or returned as a function result.
|
|
|
|
LocalVariable* function_variable = NULL;
|
|
Type& function_type = Type::ZoneHandle(Z);
|
|
if (variable_name != NULL) {
|
|
// Since the function type depends on the signature of the closure function,
|
|
// it cannot be determined before the formal parameter list of the closure
|
|
// function is parsed. Therefore, we set the function type to a new
|
|
// parameterized type to be patched after the actual type is known.
|
|
// We temporarily use the class of the Function interface.
|
|
const Class& unknown_signature_class = Class::Handle(Z,
|
|
Type::Handle(Z, Type::Function()).type_class());
|
|
function_type = Type::New(unknown_signature_class,
|
|
TypeArguments::Handle(Z), function_pos);
|
|
function_type.SetIsFinalized(); // No finalization needed.
|
|
|
|
// Add the function variable to the scope before parsing the function in
|
|
// order to allow self reference from inside the function.
|
|
function_variable = new(Z) LocalVariable(function_pos,
|
|
*variable_name,
|
|
function_type);
|
|
function_variable->set_is_final();
|
|
ASSERT(current_block_ != NULL);
|
|
ASSERT(current_block_->scope != NULL);
|
|
if (!current_block_->scope->AddVariable(function_variable)) {
|
|
LocalVariable* existing_var =
|
|
current_block_->scope->LookupVariable(function_variable->name(),
|
|
true);
|
|
ASSERT(existing_var != NULL);
|
|
if (existing_var->owner() == current_block_->scope) {
|
|
ReportError(function_pos, "identifier '%s' already defined",
|
|
function_variable->name().ToCString());
|
|
} else {
|
|
ReportError(function_pos,
|
|
"'%s' from outer scope has already been used, "
|
|
"cannot redefine",
|
|
function_variable->name().ToCString());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse the local function.
|
|
Array& default_parameter_values = Array::Handle(Z);
|
|
SequenceNode* statements = Parser::ParseFunc(function,
|
|
&default_parameter_values);
|
|
|
|
// Now that the local function has formal parameters, lookup the signature
|
|
// class in the current library (but not in its imports) and only create a new
|
|
// canonical signature class if it does not exist yet.
|
|
const String& signature = String::Handle(Z, function.Signature());
|
|
Class& signature_class = Class::ZoneHandle(Z);
|
|
if (!is_new_closure) {
|
|
signature_class = function.signature_class();
|
|
}
|
|
if (signature_class.IsNull()) {
|
|
signature_class = library_.LookupLocalClass(signature);
|
|
}
|
|
if (signature_class.IsNull()) {
|
|
// If we don't have a signature class yet, this must be a closure we
|
|
// have not parsed before.
|
|
ASSERT(is_new_closure);
|
|
signature_class = Class::NewSignatureClass(signature,
|
|
function,
|
|
script_,
|
|
function.token_pos());
|
|
// Record the function signature class in the current library.
|
|
library_.AddClass(signature_class);
|
|
} else if (is_new_closure) {
|
|
function.set_signature_class(signature_class);
|
|
}
|
|
ASSERT(function.signature_class() == signature_class.raw());
|
|
|
|
// Local functions are registered in the enclosing class, but
|
|
// ignored during class finalization. The enclosing class has
|
|
// already been finalized.
|
|
ASSERT(current_class().is_finalized());
|
|
|
|
// Make sure that the instantiator is captured.
|
|
if ((signature_class.NumTypeParameters() > 0) &&
|
|
(current_block_->scope->function_level() > 0)) {
|
|
CaptureInstantiator();
|
|
}
|
|
|
|
// Since the signature type is cached by the signature class, it may have
|
|
// been finalized already.
|
|
Type& signature_type = Type::Handle(Z,
|
|
signature_class.SignatureType());
|
|
TypeArguments& signature_type_arguments = TypeArguments::Handle(Z,
|
|
signature_type.arguments());
|
|
|
|
if (!signature_type.IsFinalized()) {
|
|
signature_type ^= ClassFinalizer::FinalizeType(
|
|
signature_class, signature_type, ClassFinalizer::kCanonicalize);
|
|
|
|
// The call to ClassFinalizer::FinalizeType may have
|
|
// extended the vector of type arguments.
|
|
signature_type_arguments = signature_type.arguments();
|
|
ASSERT(signature_type_arguments.IsNull() ||
|
|
(signature_type_arguments.Length() ==
|
|
signature_class.NumTypeArguments()));
|
|
|
|
// The signature_class should not have changed.
|
|
ASSERT(signature_type.type_class() == signature_class.raw());
|
|
}
|
|
|
|
// A signature type itself cannot be malformed or malbounded, only its
|
|
// signature function's result type or parameter types may be.
|
|
ASSERT(!signature_type.IsMalformed());
|
|
ASSERT(!signature_type.IsMalbounded());
|
|
|
|
if (variable_name != NULL) {
|
|
// Patch the function type of the variable now that the signature is known.
|
|
function_type.set_type_class(signature_class);
|
|
function_type.set_arguments(signature_type_arguments);
|
|
|
|
// The function type was initially marked as instantiated, but it may
|
|
// actually be uninstantiated.
|
|
function_type.ResetIsFinalized();
|
|
|
|
// The function variable type should have been patched above.
|
|
ASSERT((function_variable == NULL) ||
|
|
(function_variable->type().raw() == function_type.raw()));
|
|
}
|
|
|
|
// The code generator does not compile the closure function when visiting
|
|
// a ClosureNode. The generated code allocates a new Closure object containing
|
|
// the current context. The type of the Closure object refers to the closure
|
|
// function, which will be compiled on first invocation of the closure object.
|
|
// Therefore, we ignore the parsed default_parameter_values and the
|
|
// node_sequence representing the body of the closure function, which will be
|
|
// parsed again when compiled later.
|
|
// The only purpose of parsing the function now (besides reporting obvious
|
|
// errors) is to mark referenced variables of the enclosing scopes as
|
|
// captured. The captured variables will be recorded along with their
|
|
// allocation information in a Scope object stored in the function object.
|
|
// This Scope object is then provided to the compiler when compiling the local
|
|
// function. It would be too early to record the captured variables here,
|
|
// since further closure functions may capture more variables.
|
|
// This Scope object is constructed after all variables have been allocated.
|
|
// The local scope of the parsed function can be pruned, since contained
|
|
// variables are not relevant for the compilation of the enclosing function.
|
|
// This pruning is done by omitting to hook the local scope in its parent
|
|
// scope in the constructor of LocalScope.
|
|
AstNode* closure = new(Z) ClosureNode(
|
|
function_pos, function, NULL, statements->scope());
|
|
|
|
if (function_variable == NULL) {
|
|
ASSERT(is_literal);
|
|
return closure;
|
|
} else {
|
|
AstNode* initialization = new(Z) StoreLocalNode(
|
|
function_pos, function_variable, closure);
|
|
return initialization;
|
|
}
|
|
}
|
|
|
|
|
|
// Returns true if the current and next tokens can be parsed as type
|
|
// parameters. Current token position is not saved and restored.
|
|
bool Parser::TryParseTypeParameters() {
|
|
if (CurrentToken() == Token::kLT) {
|
|
// We are possibly looking at type parameters. Find closing ">".
|
|
int nesting_level = 0;
|
|
do {
|
|
if (CurrentToken() == Token::kLT) {
|
|
nesting_level++;
|
|
} else if (CurrentToken() == Token::kGT) {
|
|
nesting_level--;
|
|
} else if (CurrentToken() == Token::kSHR) {
|
|
nesting_level -= 2;
|
|
} else if (CurrentToken() == Token::kIDENT) {
|
|
// Check to see if it is a qualified identifier.
|
|
if (LookaheadToken(1) == Token::kPERIOD) {
|
|
// Consume the identifier, the period will be consumed below.
|
|
ConsumeToken();
|
|
}
|
|
} else if (CurrentToken() != Token::kCOMMA &&
|
|
CurrentToken() != Token::kEXTENDS) {
|
|
// We are looking at something other than type parameters.
|
|
return false;
|
|
}
|
|
ConsumeToken();
|
|
} while (nesting_level > 0);
|
|
if (nesting_level < 0) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Parser::IsSimpleLiteral(const AbstractType& type, Instance* value) {
|
|
// Assigning null never causes a type error.
|
|
if (CurrentToken() == Token::kNULL) {
|
|
*value = Instance::null();
|
|
return true;
|
|
}
|
|
// If the type of the const field is guaranteed to be instantiated once
|
|
// resolved at class finalization time, and if the type of the literal is one
|
|
// of int, double, String, or bool, then preset the field with the value and
|
|
// perform the type check (in checked mode only) at finalization time.
|
|
if (type.IsTypeParameter() || (type.arguments() != TypeArguments::null())) {
|
|
// Type parameters are always resolved eagerly by the parser and never
|
|
// resolved later by the class finalizer. Therefore, we know here that if
|
|
// 'type' is not a type parameter (an unresolved type will not get resolved
|
|
// to a type parameter later) and if 'type' has no type arguments, then it
|
|
// will be instantiated at class finalization time. Otherwise, we return
|
|
// false, since the type test would not be possible at finalization time for
|
|
// an uninstantiated type.
|
|
return false;
|
|
}
|
|
if (CurrentToken() == Token::kINTEGER) {
|
|
*value = CurrentIntegerLiteral();
|
|
return true;
|
|
} else if (CurrentToken() == Token::kDOUBLE) {
|
|
*value = CurrentDoubleLiteral();
|
|
return true;
|
|
} else if (CurrentToken() == Token::kSTRING) {
|
|
*value = CurrentLiteral()->raw();
|
|
return true;
|
|
} else if (CurrentToken() == Token::kTRUE) {
|
|
*value = Bool::True().raw();
|
|
return true;
|
|
} else if (CurrentToken() == Token::kFALSE) {
|
|
*value = Bool::False().raw();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// Returns true if the current token is kIDENT or a pseudo-keyword.
|
|
bool Parser::IsIdentifier() {
|
|
return Token::IsIdentifier(CurrentToken()) && !IsAwaitKeyword();
|
|
}
|
|
|
|
|
|
// Returns true if the next tokens can be parsed as a an optionally
|
|
// qualified identifier: [ident '.'] ident.
|
|
// Current token position is not restored.
|
|
bool Parser::TryParseQualIdent() {
|
|
if (CurrentToken() != Token::kIDENT) {
|
|
return false;
|
|
}
|
|
ConsumeToken();
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
if (CurrentToken() != Token::kIDENT) {
|
|
return false;
|
|
}
|
|
ConsumeToken();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// Returns true if the next tokens can be parsed as a type with optional
|
|
// type parameters. Current token position is not restored.
|
|
bool Parser::TryParseOptionalType() {
|
|
if (CurrentToken() == Token::kIDENT) {
|
|
if (!TryParseQualIdent()) {
|
|
return false;
|
|
}
|
|
if ((CurrentToken() == Token::kLT) && !TryParseTypeParameters()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// Returns true if the next tokens can be parsed as a type with optional
|
|
// type parameters, or keyword "void".
|
|
// Current token position is not restored.
|
|
bool Parser::TryParseReturnType() {
|
|
if (CurrentToken() == Token::kVOID) {
|
|
ConsumeToken();
|
|
return true;
|
|
} else if (CurrentToken() == Token::kIDENT) {
|
|
return TryParseOptionalType();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// Look ahead to detect whether the next tokens should be parsed as
|
|
// a variable declaration. Ignores optional metadata.
|
|
// Returns true if we detect the token pattern:
|
|
// 'var'
|
|
// | 'final'
|
|
// | const [type] ident (';' | '=' | ',')
|
|
// | type ident (';' | '=' | ',')
|
|
// Token position remains unchanged.
|
|
bool Parser::IsVariableDeclaration() {
|
|
if ((CurrentToken() == Token::kVAR) ||
|
|
(CurrentToken() == Token::kFINAL)) {
|
|
return true;
|
|
}
|
|
// Skip optional metadata.
|
|
if (CurrentToken() == Token::kAT) {
|
|
const intptr_t saved_pos = TokenPos();
|
|
SkipMetadata();
|
|
const bool is_var_decl = IsVariableDeclaration();
|
|
SetPosition(saved_pos);
|
|
return is_var_decl;
|
|
}
|
|
if ((CurrentToken() != Token::kIDENT) && (CurrentToken() != Token::kCONST)) {
|
|
// Not a legal type identifier or const keyword or metadata.
|
|
return false;
|
|
}
|
|
const intptr_t saved_pos = TokenPos();
|
|
bool is_var_decl = false;
|
|
bool have_type = false;
|
|
if (CurrentToken() == Token::kCONST) {
|
|
ConsumeToken();
|
|
have_type = true; // Type is dynamic.
|
|
}
|
|
if (IsIdentifier()) { // Type or variable name.
|
|
Token::Kind follower = LookaheadToken(1);
|
|
if ((follower == Token::kLT) || // Parameterized type.
|
|
(follower == Token::kPERIOD) || // Qualified class name of type.
|
|
Token::IsIdentifier(follower)) { // Variable name following a type.
|
|
// We see the beginning of something that could be a type.
|
|
const intptr_t type_pos = TokenPos();
|
|
if (TryParseOptionalType()) {
|
|
have_type = true;
|
|
} else {
|
|
SetPosition(type_pos);
|
|
}
|
|
}
|
|
if (have_type && IsIdentifier()) {
|
|
ConsumeToken();
|
|
if ((CurrentToken() == Token::kSEMICOLON) ||
|
|
(CurrentToken() == Token::kCOMMA) ||
|
|
(CurrentToken() == Token::kASSIGN)) {
|
|
is_var_decl = true;
|
|
}
|
|
}
|
|
}
|
|
SetPosition(saved_pos);
|
|
return is_var_decl;
|
|
}
|
|
|
|
|
|
// Look ahead to detect whether the next tokens should be parsed as
|
|
// a function declaration. Token position remains unchanged.
|
|
bool Parser::IsFunctionDeclaration() {
|
|
const intptr_t saved_pos = TokenPos();
|
|
bool is_external = false;
|
|
if (is_top_level_) {
|
|
if (is_patch_source() &&
|
|
(CurrentToken() == Token::kIDENT) &&
|
|
CurrentLiteral()->Equals("patch") &&
|
|
(LookaheadToken(1) != Token::kLPAREN)) {
|
|
// Skip over 'patch' for top-level function declarations in patch sources.
|
|
ConsumeToken();
|
|
} else if (CurrentToken() == Token::kEXTERNAL) {
|
|
// Skip over 'external' for top-level function declarations.
|
|
is_external = true;
|
|
ConsumeToken();
|
|
}
|
|
}
|
|
if (IsIdentifier() && (LookaheadToken(1) == Token::kLPAREN)) {
|
|
// Possibly a function without explicit return type.
|
|
ConsumeToken(); // Consume function identifier.
|
|
} else if (TryParseReturnType()) {
|
|
if (!IsIdentifier()) {
|
|
SetPosition(saved_pos);
|
|
return false;
|
|
}
|
|
ConsumeToken(); // Consume function identifier.
|
|
} else {
|
|
SetPosition(saved_pos);
|
|
return false;
|
|
}
|
|
// Check parameter list and the following token.
|
|
if (CurrentToken() == Token::kLPAREN) {
|
|
SkipToMatchingParenthesis();
|
|
if ((CurrentToken() == Token::kLBRACE) ||
|
|
(CurrentToken() == Token::kARROW) ||
|
|
(is_top_level_ && IsLiteral("native")) ||
|
|
is_external ||
|
|
(CurrentLiteral()->raw() == Symbols::Async().raw())) {
|
|
SetPosition(saved_pos);
|
|
return true;
|
|
}
|
|
}
|
|
SetPosition(saved_pos);
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Parser::IsTopLevelAccessor() {
|
|
const intptr_t saved_pos = TokenPos();
|
|
if (is_patch_source() &&
|
|
(CurrentToken() == Token::kIDENT) &&
|
|
(CurrentLiteral()->Equals("patch"))) {
|
|
ConsumeToken();
|
|
} else if (CurrentToken() == Token::kEXTERNAL) {
|
|
ConsumeToken();
|
|
}
|
|
if ((CurrentToken() == Token::kGET) || (CurrentToken() == Token::kSET)) {
|
|
SetPosition(saved_pos);
|
|
return true;
|
|
}
|
|
if (TryParseReturnType()) {
|
|
if ((CurrentToken() == Token::kGET) || (CurrentToken() == Token::kSET)) {
|
|
if (Token::IsIdentifier(LookaheadToken(1))) { // Accessor name.
|
|
SetPosition(saved_pos);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
SetPosition(saved_pos);
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Parser::IsFunctionLiteral() {
|
|
if (CurrentToken() != Token::kLPAREN || !allow_function_literals_) {
|
|
return false;
|
|
}
|
|
const intptr_t saved_pos = TokenPos();
|
|
bool is_function_literal = false;
|
|
SkipToMatchingParenthesis();
|
|
ParseFunctionModifier();
|
|
if ((CurrentToken() == Token::kLBRACE) ||
|
|
(CurrentToken() == Token::kARROW)) {
|
|
is_function_literal = true;
|
|
}
|
|
SetPosition(saved_pos);
|
|
return is_function_literal;
|
|
}
|
|
|
|
|
|
// Current token position is the token after the opening ( of the for
|
|
// statement. Returns true if we recognize a for ( .. in expr)
|
|
// statement.
|
|
bool Parser::IsForInStatement() {
|
|
const intptr_t saved_pos = TokenPos();
|
|
bool result = false;
|
|
// Allow const modifier as well when recognizing a for-in statement
|
|
// pattern. We will get an error later if the loop variable is
|
|
// declared with const.
|
|
if (CurrentToken() == Token::kVAR ||
|
|
CurrentToken() == Token::kFINAL ||
|
|
CurrentToken() == Token::kCONST) {
|
|
ConsumeToken();
|
|
}
|
|
if (IsIdentifier()) {
|
|
if (LookaheadToken(1) == Token::kIN) {
|
|
result = true;
|
|
} else if (TryParseOptionalType()) {
|
|
if (IsIdentifier()) {
|
|
ConsumeToken();
|
|
}
|
|
result = (CurrentToken() == Token::kIN);
|
|
}
|
|
}
|
|
SetPosition(saved_pos);
|
|
return result;
|
|
}
|
|
|
|
|
|
static bool ContainsAbruptCompletingStatement(SequenceNode* seq);
|
|
|
|
static bool IsAbruptCompleting(AstNode* statement) {
|
|
return statement->IsReturnNode() ||
|
|
statement->IsJumpNode() ||
|
|
statement->IsThrowNode() ||
|
|
(statement->IsSequenceNode() &&
|
|
ContainsAbruptCompletingStatement(statement->AsSequenceNode()));
|
|
}
|
|
|
|
|
|
static bool ContainsAbruptCompletingStatement(SequenceNode* seq) {
|
|
for (int i = 0; i < seq->length(); i++) {
|
|
if (IsAbruptCompleting(seq->NodeAt(i))) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void Parser::ParseStatementSequence() {
|
|
TRACE_PARSER("ParseStatementSequence");
|
|
const bool dead_code_allowed = true;
|
|
bool abrupt_completing_seen = false;
|
|
while (CurrentToken() != Token::kRBRACE) {
|
|
const intptr_t statement_pos = TokenPos();
|
|
AstNode* statement = ParseStatement();
|
|
// Do not add statements with no effect (e.g., LoadLocalNode).
|
|
if ((statement != NULL) && statement->IsLoadLocalNode()) {
|
|
// Skip load local.
|
|
continue;
|
|
}
|
|
if (statement != NULL) {
|
|
if (!dead_code_allowed && abrupt_completing_seen) {
|
|
ReportError(statement_pos,
|
|
"dead code after abrupt completing statement");
|
|
}
|
|
current_block_->statements->Add(statement);
|
|
abrupt_completing_seen |= IsAbruptCompleting(statement);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Parse nested statement of if, while, for, etc. We automatically generate
|
|
// a sequence of one statement if there are no curly braces.
|
|
// The argument 'parsing_loop_body' indicates the parsing of a loop statement.
|
|
SequenceNode* Parser::ParseNestedStatement(bool parsing_loop_body,
|
|
SourceLabel* label) {
|
|
TRACE_PARSER("ParseNestedStatement");
|
|
if (parsing_loop_body) {
|
|
OpenLoopBlock();
|
|
} else {
|
|
OpenBlock();
|
|
}
|
|
if (label != NULL) {
|
|
current_block_->scope->AddLabel(label);
|
|
}
|
|
if (CurrentToken() == Token::kLBRACE) {
|
|
ConsumeToken();
|
|
ParseStatementSequence();
|
|
ExpectToken(Token::kRBRACE);
|
|
} else {
|
|
AstNode* statement = ParseStatement();
|
|
if (statement != NULL) {
|
|
current_block_->statements->Add(statement);
|
|
}
|
|
}
|
|
SequenceNode* sequence = CloseBlock();
|
|
return sequence;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseIfStatement(String* label_name) {
|
|
TRACE_PARSER("ParseIfStatement");
|
|
ASSERT(CurrentToken() == Token::kIF);
|
|
const intptr_t if_pos = TokenPos();
|
|
SourceLabel* label = NULL;
|
|
if (label_name != NULL) {
|
|
label = SourceLabel::New(if_pos, label_name, SourceLabel::kStatement);
|
|
OpenBlock();
|
|
current_block_->scope->AddLabel(label);
|
|
}
|
|
ConsumeToken();
|
|
ExpectToken(Token::kLPAREN);
|
|
AstNode* cond_expr = ParseAwaitableExpr(kAllowConst, kConsumeCascades, NULL);
|
|
ExpectToken(Token::kRPAREN);
|
|
const bool parsing_loop_body = false;
|
|
SequenceNode* true_branch = ParseNestedStatement(parsing_loop_body, NULL);
|
|
SequenceNode* false_branch = NULL;
|
|
if (CurrentToken() == Token::kELSE) {
|
|
ConsumeToken();
|
|
false_branch = ParseNestedStatement(parsing_loop_body, NULL);
|
|
}
|
|
AstNode* if_node = new(Z) IfNode(
|
|
if_pos, cond_expr, true_branch, false_branch);
|
|
if (label != NULL) {
|
|
current_block_->statements->Add(if_node);
|
|
SequenceNode* sequence = CloseBlock();
|
|
sequence->set_label(label);
|
|
if_node = sequence;
|
|
}
|
|
return if_node;
|
|
}
|
|
|
|
|
|
// Return true if the type class of the given value implements the
|
|
// == operator.
|
|
static bool ImplementsEqualOperator(const Instance& value) {
|
|
Class& cls = Class::Handle(value.clazz());
|
|
const Function& equal_op = Function::Handle(
|
|
Resolver::ResolveDynamicAnyArgs(cls, Symbols::EqualOperator()));
|
|
ASSERT(!equal_op.IsNull());
|
|
cls = equal_op.Owner();
|
|
return !cls.IsObjectClass();
|
|
}
|
|
|
|
|
|
// Check that all case expressions are of the same type, either int, String,
|
|
// or any other class that does not override the == operator.
|
|
// The expressions are compile-time constants and are thus in the form
|
|
// of a LiteralNode.
|
|
RawClass* Parser::CheckCaseExpressions(
|
|
const GrowableArray<LiteralNode*>& values) {
|
|
const intptr_t num_expressions = values.length();
|
|
if (num_expressions == 0) {
|
|
return Object::dynamic_class();
|
|
}
|
|
const Instance& first_value = values[0]->literal();
|
|
for (intptr_t i = 0; i < num_expressions; i++) {
|
|
const Instance& val = values[i]->literal();
|
|
const intptr_t val_pos = values[i]->token_pos();
|
|
if (first_value.IsInteger()) {
|
|
if (!val.IsInteger()) {
|
|
ReportError(val_pos, "expected case expression of type int");
|
|
}
|
|
continue;
|
|
}
|
|
if (first_value.IsString()) {
|
|
if (!val.IsString()) {
|
|
ReportError(val_pos, "expected case expression of type String");
|
|
}
|
|
continue;
|
|
}
|
|
if (val.IsDouble()) {
|
|
ReportError(val_pos, "case expression may not be of type double");
|
|
}
|
|
if (val.clazz() != first_value.clazz()) {
|
|
ReportError(val_pos, "all case expressions must be of same type");
|
|
}
|
|
if (val.clazz() == I->object_store()->symbol_class()) {
|
|
continue;
|
|
}
|
|
if (i == 0) {
|
|
// The value is of some type other than int, String or double.
|
|
// Check that the type class does not override the == operator.
|
|
// Check this only in the first loop iteration since all values
|
|
// are of the same type, which we check above.
|
|
if (ImplementsEqualOperator(val)) {
|
|
ReportError(val_pos,
|
|
"type class of case expression must not "
|
|
"implement operator ==");
|
|
}
|
|
}
|
|
}
|
|
if (first_value.IsInteger()) {
|
|
return Type::Handle(Z, Type::IntType()).type_class();
|
|
} else if (first_value.IsString()) {
|
|
return Type::Handle(Z, Type::StringType()).type_class();
|
|
}
|
|
return first_value.clazz();
|
|
}
|
|
|
|
|
|
CaseNode* Parser::ParseCaseClause(LocalVariable* switch_expr_value,
|
|
GrowableArray<LiteralNode*>* case_expr_values,
|
|
SourceLabel* case_label) {
|
|
TRACE_PARSER("ParseCaseClause");
|
|
bool default_seen = false;
|
|
const intptr_t case_pos = TokenPos();
|
|
// The case expressions node sequence does not own the enclosing scope.
|
|
SequenceNode* case_expressions = new(Z) SequenceNode(case_pos, NULL);
|
|
while (CurrentToken() == Token::kCASE || CurrentToken() == Token::kDEFAULT) {
|
|
if (CurrentToken() == Token::kCASE) {
|
|
if (default_seen) {
|
|
ReportError("default clause must be last case");
|
|
}
|
|
ConsumeToken(); // Keyword case.
|
|
const intptr_t expr_pos = TokenPos();
|
|
AstNode* expr = ParseExpr(kRequireConst, kConsumeCascades);
|
|
ASSERT(expr->IsLiteralNode());
|
|
case_expr_values->Add(expr->AsLiteralNode());
|
|
|
|
AstNode* switch_expr_load = new(Z) LoadLocalNode(
|
|
case_pos, switch_expr_value);
|
|
AstNode* case_comparison = new(Z) ComparisonNode(
|
|
expr_pos, Token::kEQ, expr, switch_expr_load);
|
|
case_expressions->Add(case_comparison);
|
|
} else {
|
|
if (default_seen) {
|
|
ReportError("only one default clause is allowed");
|
|
}
|
|
ConsumeToken(); // Keyword default.
|
|
default_seen = true;
|
|
// The default case always succeeds.
|
|
}
|
|
ExpectToken(Token::kCOLON);
|
|
}
|
|
|
|
OpenBlock();
|
|
bool abrupt_completing_seen = false;
|
|
while (true) {
|
|
// Check whether the next statement still belongs to the current case
|
|
// clause. If we see 'case' or 'default', optionally preceeded by
|
|
// a label, or closing brace, we stop parsing statements.
|
|
Token::Kind next_token;
|
|
if (IsIdentifier() && LookaheadToken(1) == Token::kCOLON) {
|
|
next_token = LookaheadToken(2);
|
|
} else {
|
|
next_token = CurrentToken();
|
|
}
|
|
if (next_token == Token::kRBRACE) {
|
|
// End of switch statement.
|
|
break;
|
|
}
|
|
if ((next_token == Token::kCASE) || (next_token == Token::kDEFAULT)) {
|
|
// End of this case clause. If there is a possible fall-through to
|
|
// the next case clause, throw an implicit FallThroughError.
|
|
if (!abrupt_completing_seen) {
|
|
ArgumentListNode* arguments = new(Z) ArgumentListNode(TokenPos());
|
|
arguments->Add(new(Z) LiteralNode(
|
|
TokenPos(), Integer::ZoneHandle(Z, Integer::New(TokenPos()))));
|
|
current_block_->statements->Add(
|
|
MakeStaticCall(Symbols::FallThroughError(),
|
|
Library::PrivateCoreLibName(Symbols::ThrowNew()),
|
|
arguments));
|
|
}
|
|
break;
|
|
}
|
|
// The next statement still belongs to this case.
|
|
AstNode* statement = ParseStatement();
|
|
if (statement != NULL) {
|
|
current_block_->statements->Add(statement);
|
|
abrupt_completing_seen |= IsAbruptCompleting(statement);
|
|
}
|
|
}
|
|
SequenceNode* statements = CloseBlock();
|
|
return new(Z) CaseNode(case_pos, case_label,
|
|
case_expressions, default_seen, switch_expr_value, statements);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseSwitchStatement(String* label_name) {
|
|
TRACE_PARSER("ParseSwitchStatement");
|
|
ASSERT(CurrentToken() == Token::kSWITCH);
|
|
const intptr_t switch_pos = TokenPos();
|
|
SourceLabel* label =
|
|
SourceLabel::New(switch_pos, label_name, SourceLabel::kSwitch);
|
|
ConsumeToken();
|
|
ExpectToken(Token::kLPAREN);
|
|
const intptr_t expr_pos = TokenPos();
|
|
AstNode* switch_expr = ParseAwaitableExpr(
|
|
kAllowConst, kConsumeCascades, NULL);
|
|
ExpectToken(Token::kRPAREN);
|
|
ExpectToken(Token::kLBRACE);
|
|
OpenBlock();
|
|
current_block_->scope->AddLabel(label);
|
|
|
|
// Store switch expression in temporary local variable. The type of the
|
|
// variable is set to dynamic. It will later be patched to match the
|
|
// type of the case clause expressions. Therefore, we have to allocate
|
|
// a new type representing dynamic and can't reuse the canonical
|
|
// type object for dynamic.
|
|
const Type& temp_var_type = Type::ZoneHandle(Z,
|
|
Type::New(Class::Handle(Z, Object::dynamic_class()),
|
|
TypeArguments::Handle(Z),
|
|
expr_pos));
|
|
temp_var_type.SetIsFinalized();
|
|
LocalVariable* temp_variable = new(Z) LocalVariable(
|
|
expr_pos, Symbols::SwitchExpr(), temp_var_type);
|
|
current_block_->scope->AddVariable(temp_variable);
|
|
AstNode* save_switch_expr = new(Z) StoreLocalNode(
|
|
expr_pos, temp_variable, switch_expr);
|
|
current_block_->statements->Add(save_switch_expr);
|
|
|
|
// Parse case clauses
|
|
bool default_seen = false;
|
|
GrowableArray<LiteralNode*> case_expr_values;
|
|
while (true) {
|
|
// Check for statement label
|
|
SourceLabel* case_label = NULL;
|
|
if (IsIdentifier() && LookaheadToken(1) == Token::kCOLON) {
|
|
// Case statements start with a label.
|
|
String* label_name = CurrentLiteral();
|
|
const intptr_t label_pos = TokenPos();
|
|
ConsumeToken(); // Consume label identifier.
|
|
ConsumeToken(); // Consume colon.
|
|
case_label = current_block_->scope->LocalLookupLabel(*label_name);
|
|
if (case_label == NULL) {
|
|
// Label does not exist yet. Add it to scope of switch statement.
|
|
case_label = new(Z) SourceLabel(
|
|
label_pos, *label_name, SourceLabel::kCase);
|
|
current_block_->scope->AddLabel(case_label);
|
|
} else if (case_label->kind() == SourceLabel::kForward) {
|
|
// We have seen a 'continue' with this label name. Resolve
|
|
// the forward reference.
|
|
case_label->ResolveForwardReference();
|
|
} else {
|
|
ReportError(label_pos, "label '%s' already exists in scope",
|
|
label_name->ToCString());
|
|
}
|
|
ASSERT(case_label->kind() == SourceLabel::kCase);
|
|
}
|
|
if (CurrentToken() == Token::kCASE ||
|
|
CurrentToken() == Token::kDEFAULT) {
|
|
if (default_seen) {
|
|
ReportError("no case clauses allowed after default clause");
|
|
}
|
|
CaseNode* case_clause =
|
|
ParseCaseClause(temp_variable, &case_expr_values, case_label);
|
|
default_seen = case_clause->contains_default();
|
|
current_block_->statements->Add(case_clause);
|
|
} else if (CurrentToken() != Token::kRBRACE) {
|
|
ReportError("'case' or '}' expected");
|
|
} else if (case_label != NULL) {
|
|
ReportError("expecting at least one case clause after label");
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check that all expressions in case clauses are of the same class,
|
|
// or implement int, double or String. Patch the type of the temporary
|
|
// variable holding the switch expression to match the type of the
|
|
// case clause constants.
|
|
temp_var_type.set_type_class(
|
|
Class::Handle(Z, CheckCaseExpressions(case_expr_values)));
|
|
|
|
// Check for unresolved label references.
|
|
SourceLabel* unresolved_label =
|
|
current_block_->scope->CheckUnresolvedLabels();
|
|
if (unresolved_label != NULL) {
|
|
ReportError("unresolved reference to label '%s'",
|
|
unresolved_label->name().ToCString());
|
|
}
|
|
|
|
SequenceNode* switch_body = CloseBlock();
|
|
ExpectToken(Token::kRBRACE);
|
|
return new(Z) SwitchNode(switch_pos, label, switch_body);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseWhileStatement(String* label_name) {
|
|
TRACE_PARSER("ParseWhileStatement");
|
|
const intptr_t while_pos = TokenPos();
|
|
SourceLabel* label =
|
|
SourceLabel::New(while_pos, label_name, SourceLabel::kWhile);
|
|
ConsumeToken();
|
|
ExpectToken(Token::kLPAREN);
|
|
SequenceNode* await_preamble = NULL;
|
|
AstNode* cond_expr = ParseAwaitableExpr(
|
|
kAllowConst, kConsumeCascades, &await_preamble);
|
|
ExpectToken(Token::kRPAREN);
|
|
const bool parsing_loop_body = true;
|
|
SequenceNode* while_body = ParseNestedStatement(parsing_loop_body, label);
|
|
WhileNode* while_node = new (Z) WhileNode(while_pos,
|
|
label,
|
|
cond_expr,
|
|
await_preamble,
|
|
while_body);
|
|
return while_node;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseDoWhileStatement(String* label_name) {
|
|
TRACE_PARSER("ParseDoWhileStatement");
|
|
const intptr_t do_pos = TokenPos();
|
|
SourceLabel* label =
|
|
SourceLabel::New(do_pos, label_name, SourceLabel::kDoWhile);
|
|
ConsumeToken();
|
|
const bool parsing_loop_body = true;
|
|
SequenceNode* dowhile_body = ParseNestedStatement(parsing_loop_body, label);
|
|
ExpectToken(Token::kWHILE);
|
|
ExpectToken(Token::kLPAREN);
|
|
SequenceNode* await_preamble = NULL;
|
|
AstNode* cond_expr = ParseAwaitableExpr(
|
|
kAllowConst, kConsumeCascades, &await_preamble);
|
|
// No need for special handling of the await preamble as we can just append o
|
|
// it to the loop body.
|
|
if (await_preamble != NULL) {
|
|
dowhile_body->Add(await_preamble);
|
|
}
|
|
ExpectToken(Token::kRPAREN);
|
|
ExpectSemicolon();
|
|
return new(Z) DoWhileNode(do_pos, label, cond_expr, dowhile_body);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseAwaitForStatement(String* label_name) {
|
|
TRACE_PARSER("ParseAwaitForStatement");
|
|
ASSERT(IsAwaitKeyword());
|
|
const intptr_t await_for_pos = TokenPos();
|
|
ConsumeToken(); // await.
|
|
ASSERT(CurrentToken() == Token::kFOR);
|
|
ConsumeToken(); // for.
|
|
ExpectToken(Token::kLPAREN);
|
|
|
|
// Parse loop variable.
|
|
bool loop_var_is_final = (CurrentToken() == Token::kFINAL);
|
|
if (CurrentToken() == Token::kCONST) {
|
|
ReportError("Loop variable cannot be 'const'");
|
|
}
|
|
bool new_loop_var = false;
|
|
AbstractType& loop_var_type = AbstractType::ZoneHandle(Z);
|
|
if (LookaheadToken(1) != Token::kIN) {
|
|
// Declaration of a new loop variable.
|
|
// Delay creation of the local variable until we know its actual
|
|
// position, which is inside the loop body.
|
|
new_loop_var = true;
|
|
loop_var_type = ParseConstFinalVarOrType(
|
|
FLAG_enable_type_checks ? ClassFinalizer::kCanonicalize :
|
|
ClassFinalizer::kIgnore);
|
|
}
|
|
intptr_t loop_var_pos = TokenPos();
|
|
const String* loop_var_name = ExpectIdentifier("variable name expected");
|
|
|
|
// Parse stream expression.
|
|
ExpectToken(Token::kIN);
|
|
const intptr_t stream_pos = TokenPos();
|
|
AstNode* stream_expr =
|
|
ParseAwaitableExpr(kAllowConst, kConsumeCascades, NULL);
|
|
ExpectToken(Token::kRPAREN);
|
|
|
|
OpenBlock();
|
|
|
|
// Build creation of implicit StreamIterator.
|
|
// var :for-in-iter = new StreamIterator(stream_expr).
|
|
const Class& stream_iterator_cls =
|
|
Class::ZoneHandle(Z, I->object_store()->stream_iterator_class());
|
|
ASSERT(!stream_iterator_cls.IsNull());
|
|
const Function& iterator_ctor =
|
|
Function::ZoneHandle(Z, stream_iterator_cls.LookupFunction(
|
|
Symbols::StreamIteratorConstructor()));
|
|
ASSERT(!iterator_ctor.IsNull());
|
|
ArgumentListNode* ctor_args = new (Z) ArgumentListNode(Scanner::kNoSourcePos);
|
|
ctor_args->Add(stream_expr);
|
|
ConstructorCallNode* ctor_call =
|
|
new (Z) ConstructorCallNode(Scanner::kNoSourcePos,
|
|
TypeArguments::ZoneHandle(Z),
|
|
iterator_ctor,
|
|
ctor_args);
|
|
const AbstractType& iterator_type = Type::ZoneHandle(Z, Type::DynamicType());
|
|
LocalVariable* iterator_var = new(Z) LocalVariable(
|
|
stream_pos, Symbols::ForInIter(), iterator_type);
|
|
current_block_->scope->AddVariable(iterator_var);
|
|
AstNode* iterator_init =
|
|
new(Z) StoreLocalNode(stream_pos, iterator_var, ctor_call);
|
|
current_block_->statements->Add(iterator_init);
|
|
|
|
// Build while loop condition.
|
|
// while (await :for-in-iter.moveNext())
|
|
ArgumentListNode* no_args = new(Z) ArgumentListNode(stream_pos);
|
|
AstNode* iterator_moveNext = new(Z) InstanceCallNode(
|
|
stream_pos,
|
|
new(Z) LoadLocalNode(stream_pos, iterator_var),
|
|
Symbols::MoveNext(),
|
|
no_args);
|
|
AstNode* await_moveNext = new (Z) AwaitNode(stream_pos, iterator_moveNext);
|
|
OpenBlock();
|
|
AwaitTransformer at(current_block_->statements,
|
|
*parsed_function(),
|
|
async_temp_scope_);
|
|
AstNode* transformed_await = at.Transform(await_moveNext);
|
|
SequenceNode* await_preamble = CloseBlock();
|
|
|
|
// Parse the for loop body. Ideally, we would use ParseNestedStatement()
|
|
// here, but that does not work well because we have to insert an implicit
|
|
// variable assignment and potentially a variable declaration in the
|
|
// loop body.
|
|
OpenLoopBlock();
|
|
SourceLabel* label =
|
|
SourceLabel::New(await_for_pos, label_name, SourceLabel::kFor);
|
|
current_block_->scope->AddLabel(label);
|
|
const intptr_t loop_var_assignment_pos = TokenPos();
|
|
|
|
AstNode* iterator_current = new(Z) InstanceGetterNode(
|
|
loop_var_assignment_pos,
|
|
new(Z) LoadLocalNode(loop_var_assignment_pos, iterator_var),
|
|
Symbols::Current());
|
|
|
|
// Generate assignment of next iterator value to loop variable.
|
|
AstNode* loop_var_assignment = NULL;
|
|
if (new_loop_var) {
|
|
// The for loop variable is new for each iteration.
|
|
// Create a variable and add it to the loop body scope.
|
|
// Note that the variable token position needs to be inside the
|
|
// loop block, so it gets put in the loop context level.
|
|
LocalVariable* loop_var =
|
|
new(Z) LocalVariable(loop_var_assignment_pos,
|
|
*loop_var_name,
|
|
loop_var_type);;
|
|
if (loop_var_is_final) {
|
|
loop_var->set_is_final();
|
|
}
|
|
current_block_->scope->AddVariable(loop_var);
|
|
loop_var_assignment = new(Z) StoreLocalNode(
|
|
loop_var_assignment_pos, loop_var, iterator_current);
|
|
} else {
|
|
AstNode* loop_var_primary =
|
|
ResolveIdent(loop_var_pos, *loop_var_name, false);
|
|
ASSERT(!loop_var_primary->IsPrimaryNode());
|
|
loop_var_assignment = CreateAssignmentNode(loop_var_primary,
|
|
iterator_current,
|
|
loop_var_name,
|
|
loop_var_assignment_pos);
|
|
ASSERT(loop_var_assignment != NULL);
|
|
}
|
|
current_block_->statements->Add(loop_var_assignment);
|
|
|
|
// Now parse the for-in loop statement or block.
|
|
if (CurrentToken() == Token::kLBRACE) {
|
|
ConsumeToken();
|
|
ParseStatementSequence();
|
|
ExpectToken(Token::kRBRACE);
|
|
} else {
|
|
AstNode* statement = ParseStatement();
|
|
if (statement != NULL) {
|
|
current_block_->statements->Add(statement);
|
|
}
|
|
}
|
|
SequenceNode* for_loop_statement = CloseBlock();
|
|
|
|
WhileNode* while_node = new (Z) WhileNode(await_for_pos,
|
|
label,
|
|
transformed_await,
|
|
await_preamble,
|
|
for_loop_statement);
|
|
current_block_->statements->Add(while_node);
|
|
|
|
return CloseBlock(); // Implicit block around while loop.
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseForInStatement(intptr_t forin_pos,
|
|
SourceLabel* label) {
|
|
TRACE_PARSER("ParseForInStatement");
|
|
bool loop_var_is_final = (CurrentToken() == Token::kFINAL);
|
|
if (CurrentToken() == Token::kCONST) {
|
|
ReportError("Loop variable cannot be 'const'");
|
|
}
|
|
const String* loop_var_name = NULL;
|
|
intptr_t loop_var_pos = 0;
|
|
bool new_loop_var = false;
|
|
AbstractType& loop_var_type = AbstractType::ZoneHandle(Z);
|
|
if (LookaheadToken(1) == Token::kIN) {
|
|
loop_var_pos = TokenPos();
|
|
loop_var_name = ExpectIdentifier("variable name expected");
|
|
} else {
|
|
// The case without a type is handled above, so require a type here.
|
|
// Delay creation of the local variable until we know its actual
|
|
// position, which is inside the loop body.
|
|
new_loop_var = true;
|
|
loop_var_type = ParseConstFinalVarOrType(
|
|
FLAG_enable_type_checks ? ClassFinalizer::kCanonicalize :
|
|
ClassFinalizer::kIgnore);
|
|
loop_var_name = ExpectIdentifier("variable name expected");
|
|
}
|
|
ExpectToken(Token::kIN);
|
|
const intptr_t collection_pos = TokenPos();
|
|
AstNode* collection_expr =
|
|
ParseAwaitableExpr(kAllowConst, kConsumeCascades, NULL);
|
|
ExpectToken(Token::kRPAREN);
|
|
|
|
OpenBlock(); // Implicit block around while loop.
|
|
|
|
// Generate implicit iterator variable and add to scope.
|
|
// We could set the type of the implicit iterator variable to Iterator<T>
|
|
// where T is the type of the for loop variable. However, the type error
|
|
// would refer to the compiler generated iterator and could confuse the user.
|
|
// It is better to leave the iterator untyped and postpone the type error
|
|
// until the loop variable is assigned to.
|
|
const AbstractType& iterator_type = Type::ZoneHandle(Z, Type::DynamicType());
|
|
LocalVariable* iterator_var = new(Z) LocalVariable(
|
|
collection_pos, Symbols::ForInIter(), iterator_type);
|
|
current_block_->scope->AddVariable(iterator_var);
|
|
|
|
// Generate initialization of iterator variable.
|
|
ArgumentListNode* no_args = new(Z) ArgumentListNode(collection_pos);
|
|
AstNode* get_iterator = new(Z) InstanceGetterNode(
|
|
collection_pos, collection_expr, Symbols::GetIterator());
|
|
AstNode* iterator_init =
|
|
new(Z) StoreLocalNode(collection_pos, iterator_var, get_iterator);
|
|
current_block_->statements->Add(iterator_init);
|
|
|
|
// Generate while loop condition.
|
|
AstNode* iterator_moveNext = new(Z) InstanceCallNode(
|
|
collection_pos,
|
|
new(Z) LoadLocalNode(collection_pos, iterator_var),
|
|
Symbols::MoveNext(),
|
|
no_args);
|
|
|
|
// Parse the for loop body. Ideally, we would use ParseNestedStatement()
|
|
// here, but that does not work well because we have to insert an implicit
|
|
// variable assignment and potentially a variable declaration in the
|
|
// loop body.
|
|
OpenLoopBlock();
|
|
current_block_->scope->AddLabel(label);
|
|
const intptr_t loop_var_assignment_pos = TokenPos();
|
|
|
|
AstNode* iterator_current = new(Z) InstanceGetterNode(
|
|
loop_var_assignment_pos,
|
|
new(Z) LoadLocalNode(loop_var_assignment_pos, iterator_var),
|
|
Symbols::Current());
|
|
|
|
// Generate assignment of next iterator value to loop variable.
|
|
AstNode* loop_var_assignment = NULL;
|
|
if (new_loop_var) {
|
|
// The for loop variable is new for each iteration.
|
|
// Create a variable and add it to the loop body scope.
|
|
LocalVariable* loop_var =
|
|
new(Z) LocalVariable(loop_var_assignment_pos,
|
|
*loop_var_name,
|
|
loop_var_type);;
|
|
if (loop_var_is_final) {
|
|
loop_var->set_is_final();
|
|
}
|
|
current_block_->scope->AddVariable(loop_var);
|
|
loop_var_assignment = new(Z) StoreLocalNode(
|
|
loop_var_assignment_pos, loop_var, iterator_current);
|
|
} else {
|
|
AstNode* loop_var_primary =
|
|
ResolveIdent(loop_var_pos, *loop_var_name, false);
|
|
ASSERT(!loop_var_primary->IsPrimaryNode());
|
|
loop_var_assignment = CreateAssignmentNode(loop_var_primary,
|
|
iterator_current,
|
|
loop_var_name,
|
|
loop_var_assignment_pos);
|
|
ASSERT(loop_var_assignment != NULL);
|
|
}
|
|
current_block_->statements->Add(loop_var_assignment);
|
|
|
|
// Now parse the for-in loop statement or block.
|
|
if (CurrentToken() == Token::kLBRACE) {
|
|
ConsumeToken();
|
|
ParseStatementSequence();
|
|
ExpectToken(Token::kRBRACE);
|
|
} else {
|
|
AstNode* statement = ParseStatement();
|
|
if (statement != NULL) {
|
|
current_block_->statements->Add(statement);
|
|
}
|
|
}
|
|
|
|
SequenceNode* for_loop_statement = CloseBlock();
|
|
|
|
AstNode* while_statement = new(Z) WhileNode(
|
|
forin_pos, label, iterator_moveNext, NULL, for_loop_statement);
|
|
current_block_->statements->Add(while_statement);
|
|
|
|
return CloseBlock(); // Implicit block around while loop.
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseForStatement(String* label_name) {
|
|
TRACE_PARSER("ParseForStatement");
|
|
const intptr_t for_pos = TokenPos();
|
|
ConsumeToken();
|
|
ExpectToken(Token::kLPAREN);
|
|
SourceLabel* label = SourceLabel::New(for_pos, label_name, SourceLabel::kFor);
|
|
if (IsForInStatement()) {
|
|
return ParseForInStatement(for_pos, label);
|
|
}
|
|
// Open a block that contains the loop variable. Make it a loop block so
|
|
// that we allocate a new context if the loop variable is captured.
|
|
OpenLoopBlock();
|
|
AstNode* initializer = NULL;
|
|
const intptr_t init_pos = TokenPos();
|
|
LocalScope* init_scope = current_block_->scope;
|
|
if (CurrentToken() != Token::kSEMICOLON) {
|
|
if (IsVariableDeclaration()) {
|
|
initializer = ParseVariableDeclarationList();
|
|
} else {
|
|
initializer = ParseAwaitableExpr(kAllowConst, kConsumeCascades, NULL);
|
|
}
|
|
}
|
|
ExpectSemicolon();
|
|
AstNode* condition = NULL;
|
|
SequenceNode* condition_preamble = NULL;
|
|
if (CurrentToken() != Token::kSEMICOLON) {
|
|
condition = ParseAwaitableExpr(
|
|
kAllowConst, kConsumeCascades, &condition_preamble);
|
|
}
|
|
ExpectSemicolon();
|
|
AstNode* increment = NULL;
|
|
const intptr_t incr_pos = TokenPos();
|
|
if (CurrentToken() != Token::kRPAREN) {
|
|
increment = ParseAwaitableExprList();
|
|
}
|
|
ExpectToken(Token::kRPAREN);
|
|
const bool parsing_loop_body = true;
|
|
SequenceNode* body = ParseNestedStatement(parsing_loop_body, label);
|
|
|
|
// Check whether any of the variables in the initializer part of
|
|
// the for statement are captured by a closure. If so, we insert a
|
|
// node that creates a new Context for the loop variable before
|
|
// the increment expression is evaluated.
|
|
for (int i = 0; i < init_scope->num_variables(); i++) {
|
|
if (init_scope->VariableAt(i)->is_captured() &&
|
|
(init_scope->VariableAt(i)->owner() == init_scope)) {
|
|
SequenceNode* incr_sequence = new(Z) SequenceNode(incr_pos, NULL);
|
|
incr_sequence->Add(new(Z) CloneContextNode(for_pos));
|
|
if (increment != NULL) {
|
|
incr_sequence->Add(increment);
|
|
}
|
|
increment = incr_sequence;
|
|
break;
|
|
}
|
|
}
|
|
AstNode* for_node = new(Z) ForNode(
|
|
for_pos,
|
|
label,
|
|
NodeAsSequenceNode(init_pos, initializer, NULL),
|
|
condition,
|
|
condition_preamble,
|
|
NodeAsSequenceNode(incr_pos, increment, NULL),
|
|
body);
|
|
current_block_->statements->Add(for_node);
|
|
return CloseBlock();
|
|
}
|
|
|
|
|
|
// Calling VM-internal helpers, uses implementation core library.
|
|
AstNode* Parser::MakeStaticCall(const String& cls_name,
|
|
const String& func_name,
|
|
ArgumentListNode* arguments) {
|
|
const Class& cls = Class::Handle(Z, Library::LookupCoreClass(cls_name));
|
|
ASSERT(!cls.IsNull());
|
|
const Function& func = Function::ZoneHandle(Z,
|
|
Resolver::ResolveStatic(cls,
|
|
func_name,
|
|
arguments->length(),
|
|
arguments->names()));
|
|
ASSERT(!func.IsNull());
|
|
return new(Z) StaticCallNode(arguments->token_pos(), func, arguments);
|
|
}
|
|
|
|
|
|
AstNode* Parser::MakeAssertCall(intptr_t begin, intptr_t end) {
|
|
ArgumentListNode* arguments = new(Z) ArgumentListNode(begin);
|
|
arguments->Add(new(Z) LiteralNode(begin,
|
|
Integer::ZoneHandle(Z, Integer::New(begin))));
|
|
arguments->Add(new(Z) LiteralNode(end,
|
|
Integer::ZoneHandle(Z, Integer::New(end))));
|
|
return MakeStaticCall(Symbols::AssertionError(),
|
|
Library::PrivateCoreLibName(Symbols::ThrowNew()),
|
|
arguments);
|
|
}
|
|
|
|
|
|
AstNode* Parser::InsertClosureCallNodes(AstNode* condition) {
|
|
if (condition->IsClosureNode() ||
|
|
(condition->IsStoreLocalNode() &&
|
|
condition->AsStoreLocalNode()->value()->IsClosureNode())) {
|
|
// Function literal in assert implies a call.
|
|
const intptr_t pos = condition->token_pos();
|
|
condition = BuildClosureCall(pos,
|
|
condition,
|
|
new(Z) ArgumentListNode(pos));
|
|
} else if (condition->IsConditionalExprNode()) {
|
|
ConditionalExprNode* cond_expr = condition->AsConditionalExprNode();
|
|
cond_expr->set_true_expr(InsertClosureCallNodes(cond_expr->true_expr()));
|
|
cond_expr->set_false_expr(InsertClosureCallNodes(cond_expr->false_expr()));
|
|
}
|
|
return condition;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseAssertStatement() {
|
|
TRACE_PARSER("ParseAssertStatement");
|
|
ConsumeToken(); // Consume assert keyword.
|
|
ExpectToken(Token::kLPAREN);
|
|
const intptr_t condition_pos = TokenPos();
|
|
if (!FLAG_enable_asserts && !FLAG_enable_type_checks) {
|
|
SkipExpr();
|
|
ExpectToken(Token::kRPAREN);
|
|
return NULL;
|
|
}
|
|
AstNode* condition = ParseExpr(kAllowConst, kConsumeCascades);
|
|
const intptr_t condition_end = TokenPos();
|
|
ExpectToken(Token::kRPAREN);
|
|
condition = InsertClosureCallNodes(condition);
|
|
condition = new(Z) UnaryOpNode(condition_pos, Token::kNOT, condition);
|
|
AstNode* assert_throw = MakeAssertCall(condition_pos, condition_end);
|
|
return new(Z) IfNode(
|
|
condition_pos,
|
|
condition,
|
|
NodeAsSequenceNode(condition_pos, assert_throw, NULL),
|
|
NULL);
|
|
}
|
|
|
|
|
|
// Populate local scope of the catch block with the catch parameters.
|
|
void Parser::AddCatchParamsToScope(CatchParamDesc* exception_param,
|
|
CatchParamDesc* stack_trace_param,
|
|
LocalScope* scope) {
|
|
if (exception_param->name != NULL) {
|
|
LocalVariable* var = new(Z) LocalVariable(
|
|
exception_param->token_pos,
|
|
*exception_param->name,
|
|
*exception_param->type);
|
|
var->set_is_final();
|
|
bool added_to_scope = scope->AddVariable(var);
|
|
ASSERT(added_to_scope);
|
|
exception_param->var = var;
|
|
}
|
|
if (stack_trace_param->name != NULL) {
|
|
LocalVariable* var = new(Z) LocalVariable(
|
|
stack_trace_param->token_pos,
|
|
*stack_trace_param->name,
|
|
*stack_trace_param->type);
|
|
var->set_is_final();
|
|
bool added_to_scope = scope->AddVariable(var);
|
|
if (!added_to_scope) {
|
|
ReportError(stack_trace_param->token_pos,
|
|
"name '%s' already exists in scope",
|
|
stack_trace_param->name->ToCString());
|
|
}
|
|
stack_trace_param->var = var;
|
|
}
|
|
}
|
|
|
|
|
|
SequenceNode* Parser::ParseFinallyBlock() {
|
|
TRACE_PARSER("ParseFinallyBlock");
|
|
OpenBlock();
|
|
ExpectToken(Token::kLBRACE);
|
|
|
|
// In case of async closures we need to restore the saved try index of an
|
|
// outer try block (if it exists). The current try block has already been
|
|
// removed from the stack of try blocks.
|
|
if ((innermost_function().is_async_closure() ||
|
|
innermost_function().IsAsyncFunction()) &&
|
|
(try_blocks_list_ != NULL)) {
|
|
// We need two unchain two scopes: finally clause, and the try block level.
|
|
RestoreSavedTryContext(current_block_->scope->parent()->parent(),
|
|
try_blocks_list_->try_index(),
|
|
current_block_->statements);
|
|
} else {
|
|
parsed_function()->reset_saved_try_ctx_vars();
|
|
}
|
|
|
|
ParseStatementSequence();
|
|
ExpectToken(Token::kRBRACE);
|
|
SequenceNode* finally_block = CloseBlock();
|
|
return finally_block;
|
|
}
|
|
|
|
|
|
void Parser::PushTryBlock(Block* try_block) {
|
|
intptr_t try_index = AllocateTryIndex();
|
|
TryBlocks* block = new(Z) TryBlocks(
|
|
try_block, try_blocks_list_, try_index);
|
|
try_blocks_list_ = block;
|
|
}
|
|
|
|
|
|
Parser::TryBlocks* Parser::PopTryBlock() {
|
|
TryBlocks* innermost_try_block = try_blocks_list_;
|
|
try_blocks_list_ = try_blocks_list_->outer_try_block();
|
|
return innermost_try_block;
|
|
}
|
|
|
|
|
|
void Parser::AddNodeForFinallyInlining(AstNode* node) {
|
|
if (node == NULL) {
|
|
return;
|
|
}
|
|
ASSERT(node->IsReturnNode() || node->IsJumpNode());
|
|
TryBlocks* iterator = try_blocks_list_;
|
|
while (iterator != NULL) {
|
|
// For continue and break node check if the target label is in scope.
|
|
if (node->IsJumpNode()) {
|
|
SourceLabel* label = node->AsJumpNode()->label();
|
|
ASSERT(label != NULL);
|
|
LocalScope* try_scope = iterator->try_block()->scope;
|
|
// If the label is defined in a scope which is a child (nested scope)
|
|
// of the try scope then we are not breaking out of this try block
|
|
// so we do not need to inline the finally code. Otherwise we need
|
|
// to inline the finally code of this try block and then move on to the
|
|
// next outer try block.
|
|
if (label->owner()->IsNestedWithin(try_scope)) {
|
|
break;
|
|
}
|
|
}
|
|
iterator->AddNodeForFinallyInlining(node);
|
|
iterator = iterator->outer_try_block();
|
|
}
|
|
}
|
|
|
|
|
|
// Add the inlined finally block to the specified node.
|
|
void Parser::AddFinallyBlockToNode(AstNode* node,
|
|
InlinedFinallyNode* finally_node) {
|
|
ReturnNode* return_node = node->AsReturnNode();
|
|
if (return_node != NULL) {
|
|
parsed_function()->EnsureFinallyReturnTemp();
|
|
return_node->AddInlinedFinallyNode(finally_node);
|
|
return;
|
|
}
|
|
JumpNode* jump_node = node->AsJumpNode();
|
|
ASSERT(jump_node != NULL);
|
|
jump_node->AddInlinedFinallyNode(finally_node);
|
|
}
|
|
|
|
|
|
SequenceNode* Parser::ParseCatchClauses(
|
|
intptr_t handler_pos,
|
|
LocalVariable* exception_var,
|
|
LocalVariable* stack_trace_var,
|
|
const GrowableObjectArray& handler_types,
|
|
bool* needs_stack_trace) {
|
|
// All catch blocks are merged into an if-then-else sequence of the
|
|
// different types specified using the 'is' operator. While parsing
|
|
// record the type tests (either a ComparisonNode or else the LiteralNode
|
|
// true for a generic catch) and the catch bodies in a pair of parallel
|
|
// lists. Afterward, construct the nested if-then-else.
|
|
bool generic_catch_seen = false;
|
|
GrowableArray<AstNode*> type_tests;
|
|
GrowableArray<SequenceNode*> catch_blocks;
|
|
while ((CurrentToken() == Token::kCATCH) || IsLiteral("on")) {
|
|
// Open a block that contains the if or an unconditional body. It's
|
|
// closed in the loop that builds the if-then-else nest.
|
|
OpenBlock();
|
|
const intptr_t catch_pos = TokenPos();
|
|
CatchParamDesc exception_param;
|
|
CatchParamDesc stack_trace_param;
|
|
if (IsLiteral("on")) {
|
|
ConsumeToken();
|
|
exception_param.type = &AbstractType::ZoneHandle(Z,
|
|
ParseType(ClassFinalizer::kCanonicalize));
|
|
} else {
|
|
exception_param.type = &AbstractType::ZoneHandle(Z, Type::DynamicType());
|
|
}
|
|
if (CurrentToken() == Token::kCATCH) {
|
|
ConsumeToken(); // Consume the 'catch'.
|
|
ExpectToken(Token::kLPAREN);
|
|
exception_param.token_pos = TokenPos();
|
|
exception_param.name = ExpectIdentifier("identifier expected");
|
|
if (CurrentToken() == Token::kCOMMA) {
|
|
ConsumeToken();
|
|
// TODO(hausner): Make implicit type be StackTrace, not dynamic.
|
|
stack_trace_param.type =
|
|
&AbstractType::ZoneHandle(Z, Type::DynamicType());
|
|
stack_trace_param.token_pos = TokenPos();
|
|
stack_trace_param.name = ExpectIdentifier("identifier expected");
|
|
}
|
|
ExpectToken(Token::kRPAREN);
|
|
}
|
|
|
|
// Create a block containing the catch clause parameters and the
|
|
// following code:
|
|
// 1) Store exception object and stack trace object into user-defined
|
|
// variables (as needed).
|
|
// 2) Nested block with source code from catch clause block.
|
|
OpenBlock();
|
|
AddCatchParamsToScope(&exception_param, &stack_trace_param,
|
|
current_block_->scope);
|
|
|
|
if (exception_param.var != NULL) {
|
|
// Generate code to load the exception object (:exception_var) into
|
|
// the exception variable specified in this block.
|
|
ASSERT(exception_var != NULL);
|
|
current_block_->statements->Add(new(Z) StoreLocalNode(
|
|
catch_pos, exception_param.var, new(Z) LoadLocalNode(
|
|
catch_pos, exception_var)));
|
|
}
|
|
if (stack_trace_param.var != NULL) {
|
|
// A stack trace variable is specified in this block, so generate code
|
|
// to load the stack trace object (:stack_trace_var) into the stack
|
|
// trace variable specified in this block.
|
|
*needs_stack_trace = true;
|
|
ArgumentListNode* no_args = new(Z) ArgumentListNode(catch_pos);
|
|
ASSERT(stack_trace_var != NULL);
|
|
current_block_->statements->Add(new(Z) StoreLocalNode(
|
|
catch_pos, stack_trace_param.var, new(Z) LoadLocalNode(
|
|
catch_pos, stack_trace_var)));
|
|
current_block_->statements->Add(new(Z) InstanceCallNode(
|
|
catch_pos,
|
|
new(Z) LoadLocalNode(catch_pos, stack_trace_param.var),
|
|
Library::PrivateCoreLibName(Symbols::_setupFullStackTrace()),
|
|
no_args));
|
|
}
|
|
|
|
// Add nested block with user-defined code. This blocks allows
|
|
// declarations in the body to shadow the catch parameters.
|
|
CheckToken(Token::kLBRACE);
|
|
|
|
// In case of async closures we need to restore the saved try index of an
|
|
// outer try block (if it exists).
|
|
ASSERT(try_blocks_list_ != NULL);
|
|
if (innermost_function().is_async_closure() ||
|
|
innermost_function().IsAsyncFunction()) {
|
|
if ((try_blocks_list_->outer_try_block() != NULL) &&
|
|
(try_blocks_list_->outer_try_block()->try_block()
|
|
->scope->function_level() ==
|
|
current_block_->scope->function_level())) {
|
|
// We need to unchain three scope levels: catch clause, catch
|
|
// parameters, and the general try block.
|
|
RestoreSavedTryContext(
|
|
current_block_->scope->parent()->parent()->parent(),
|
|
try_blocks_list_->outer_try_block()->try_index(),
|
|
current_block_->statements);
|
|
} else {
|
|
parsed_function()->reset_saved_try_ctx_vars();
|
|
}
|
|
}
|
|
|
|
current_block_->statements->Add(ParseNestedStatement(false, NULL));
|
|
catch_blocks.Add(CloseBlock());
|
|
|
|
const bool is_bad_type =
|
|
exception_param.type->IsMalformed() ||
|
|
exception_param.type->IsMalbounded();
|
|
if (exception_param.type->IsDynamicType() || is_bad_type) {
|
|
// There is no exception type or else it is malformed or malbounded.
|
|
// In the first case, unconditionally execute the catch body. In the
|
|
// second case, unconditionally throw.
|
|
generic_catch_seen = true;
|
|
type_tests.Add(new(Z) LiteralNode(catch_pos, Bool::True()));
|
|
if (is_bad_type) {
|
|
// Replace the body with one that throws.
|
|
SequenceNode* block = new(Z) SequenceNode(catch_pos, NULL);
|
|
block->Add(ThrowTypeError(catch_pos, *exception_param.type));
|
|
catch_blocks.Last() = block;
|
|
}
|
|
// This catch clause will handle all exceptions. We can safely forget
|
|
// all previous catch clause types.
|
|
handler_types.SetLength(0);
|
|
handler_types.Add(*exception_param.type);
|
|
} else {
|
|
// Has a type specification that is not malformed or malbounded. Now
|
|
// form an 'if type check' to guard the catch handler code.
|
|
if (!exception_param.type->IsInstantiated() &&
|
|
(current_block_->scope->function_level() > 0)) {
|
|
// Make sure that the instantiator is captured.
|
|
CaptureInstantiator();
|
|
}
|
|
TypeNode* exception_type = new(Z) TypeNode(
|
|
catch_pos, *exception_param.type);
|
|
AstNode* exception_value = new(Z) LoadLocalNode(
|
|
catch_pos, exception_var);
|
|
if (!exception_type->type().IsInstantiated()) {
|
|
EnsureExpressionTemp();
|
|
}
|
|
type_tests.Add(new(Z) ComparisonNode(
|
|
catch_pos, Token::kIS, exception_value, exception_type));
|
|
|
|
// Do not add uninstantiated types (e.g. type parameter T or generic
|
|
// type List<T>), since the debugger won't be able to instantiate it
|
|
// when walking the stack.
|
|
//
|
|
// This means that the debugger is not able to determine whether an
|
|
// exception is caught if the catch clause uses generic types. It
|
|
// will report the exception as uncaught when in fact it might be
|
|
// caught and handled when we unwind the stack.
|
|
if (!generic_catch_seen && exception_param.type->IsInstantiated()) {
|
|
handler_types.Add(*exception_param.type);
|
|
}
|
|
}
|
|
|
|
ASSERT(type_tests.length() == catch_blocks.length());
|
|
}
|
|
|
|
// Build the if/then/else nest from the inside out. Keep the AST simple
|
|
// for the case of a single generic catch clause. The initial value of
|
|
// current is the last (innermost) else block if there were any catch
|
|
// clauses.
|
|
SequenceNode* current = NULL;
|
|
if (!generic_catch_seen) {
|
|
// There isn't a generic catch clause so create a clause body that
|
|
// rethrows the exception. This includes the case that there were no
|
|
// catch clauses.
|
|
current = new(Z) SequenceNode(handler_pos, NULL);
|
|
current->Add(new(Z) ThrowNode(
|
|
handler_pos,
|
|
new(Z) LoadLocalNode(handler_pos, exception_var),
|
|
new(Z) LoadLocalNode(handler_pos, stack_trace_var)));
|
|
} else if (type_tests.Last()->IsLiteralNode()) {
|
|
ASSERT(type_tests.Last()->AsLiteralNode()->literal().raw() ==
|
|
Bool::True().raw());
|
|
// The last body is entered unconditionally. Start building the
|
|
// if/then/else nest with that body as the innermost else block.
|
|
// Note that it is nested inside an extra block which we opened
|
|
// before we knew the body was entered unconditionally.
|
|
type_tests.RemoveLast();
|
|
current_block_->statements->Add(catch_blocks.RemoveLast());
|
|
current = CloseBlock();
|
|
}
|
|
// If the last body was entered conditionally and there is no need to add
|
|
// a rethrow, use an empty else body (current = NULL above).
|
|
|
|
while (!type_tests.is_empty()) {
|
|
AstNode* type_test = type_tests.RemoveLast();
|
|
SequenceNode* catch_block = catch_blocks.RemoveLast();
|
|
|
|
// In case of async closures we need to restore the saved try index of an
|
|
// outer try block (if it exists).
|
|
ASSERT(try_blocks_list_ != NULL);
|
|
if (innermost_function().is_async_closure() ||
|
|
innermost_function().IsAsyncFunction()) {
|
|
if ((try_blocks_list_->outer_try_block() != NULL) &&
|
|
(try_blocks_list_->outer_try_block()->try_block()
|
|
->scope->function_level() ==
|
|
current_block_->scope->function_level())) {
|
|
// We need to unchain three scope levels: catch clause, catch
|
|
// parameters, and the general try block.
|
|
RestoreSavedTryContext(
|
|
current_block_->scope->parent()->parent(),
|
|
try_blocks_list_->outer_try_block()->try_index(),
|
|
current_block_->statements);
|
|
} else {
|
|
parsed_function()->reset_saved_try_ctx_vars();
|
|
}
|
|
}
|
|
|
|
current_block_->statements->Add(new(Z) IfNode(
|
|
type_test->token_pos(), type_test, catch_block, current));
|
|
current = CloseBlock();
|
|
}
|
|
return current;
|
|
}
|
|
|
|
|
|
void Parser::SetupSavedTryContext(LocalVariable* saved_try_context) {
|
|
const String& async_saved_try_ctx_name =
|
|
BuildAsyncSavedTryContextName(Z, last_used_try_index_ - 1);
|
|
LocalVariable* async_saved_try_ctx = new (Z) LocalVariable(
|
|
Scanner::kNoSourcePos,
|
|
async_saved_try_ctx_name,
|
|
Type::ZoneHandle(Z, Type::DynamicType()));
|
|
async_temp_scope_->AddVariable(async_saved_try_ctx);
|
|
async_saved_try_ctx->set_is_captured();
|
|
async_saved_try_ctx = current_block_->scope->LookupVariable(
|
|
async_saved_try_ctx_name, false);
|
|
ASSERT(async_saved_try_ctx != NULL);
|
|
ASSERT(saved_try_context != NULL);
|
|
current_block_->statements->Add(new (Z) StoreLocalNode(
|
|
Scanner::kNoSourcePos, async_saved_try_ctx, new (Z) LoadLocalNode(
|
|
Scanner::kNoSourcePos, saved_try_context)));
|
|
parsed_function()->set_saved_try_ctx(saved_try_context);
|
|
parsed_function()->set_async_saved_try_ctx_name(async_saved_try_ctx_name);
|
|
}
|
|
|
|
|
|
// Set up the currently relevant :saved_try_context_var on the stack:
|
|
// * Try blocks: Set the context variable for this try block.
|
|
// * Catch/finally blocks: Set the context variable for any outer try block (if
|
|
// existent).
|
|
//
|
|
// Also save the captured variable and the stack variable to be able to set
|
|
// it after a function continues execution (await).
|
|
void Parser::RestoreSavedTryContext(LocalScope* saved_try_context_scope,
|
|
int16_t try_index,
|
|
SequenceNode* target) {
|
|
LocalVariable* saved_try_ctx = saved_try_context_scope->LookupVariable(
|
|
Symbols::SavedTryContextVar(), false);
|
|
ASSERT((saved_try_ctx != NULL) && !saved_try_ctx->is_captured());
|
|
const String& async_saved_try_ctx_name =
|
|
BuildAsyncSavedTryContextName(Z, try_index);
|
|
LocalVariable* async_saved_try_ctx =
|
|
target->scope()->LookupVariable(async_saved_try_ctx_name, false);
|
|
ASSERT(async_saved_try_ctx != NULL);
|
|
ASSERT(async_saved_try_ctx->is_captured());
|
|
target->Add(new (Z) StoreLocalNode(
|
|
Scanner::kNoSourcePos,
|
|
saved_try_ctx,
|
|
new (Z) LoadLocalNode(Scanner::kNoSourcePos, async_saved_try_ctx)));
|
|
|
|
parsed_function()->set_saved_try_ctx(saved_try_ctx);
|
|
parsed_function()->set_async_saved_try_ctx_name(async_saved_try_ctx_name);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseTryStatement(String* label_name) {
|
|
TRACE_PARSER("ParseTryStatement");
|
|
|
|
// We create three variables for exceptions here:
|
|
// ':saved_try_context_var' - Used to save the context before the start of
|
|
// the try block. The context register is
|
|
// restored from this variable before
|
|
// processing the catch block handler.
|
|
// ':exception_var' - Used to save the current exception object that was
|
|
// thrown.
|
|
// ':stack_trace_var' - Used to save the current stack trace object which
|
|
// the stack trace was copied into when an exception
|
|
// was thrown.
|
|
// :exception_var and :stack_trace_var get set with the exception object
|
|
// and the stack trace object when an exception is thrown. These three
|
|
// implicit variables can never be captured.
|
|
LocalVariable* context_var =
|
|
current_block_->scope->LocalLookupVariable(Symbols::SavedTryContextVar());
|
|
if (context_var == NULL) {
|
|
context_var = new(Z) LocalVariable(
|
|
TokenPos(),
|
|
Symbols::SavedTryContextVar(),
|
|
Type::ZoneHandle(Z, Type::DynamicType()));
|
|
current_block_->scope->AddVariable(context_var);
|
|
}
|
|
LocalVariable* exception_var =
|
|
current_block_->scope->LocalLookupVariable(Symbols::ExceptionVar());
|
|
if (exception_var == NULL) {
|
|
exception_var = new(Z) LocalVariable(
|
|
TokenPos(),
|
|
Symbols::ExceptionVar(),
|
|
Type::ZoneHandle(Z, Type::DynamicType()));
|
|
current_block_->scope->AddVariable(exception_var);
|
|
}
|
|
LocalVariable* stack_trace_var =
|
|
current_block_->scope->LocalLookupVariable(Symbols::StackTraceVar());
|
|
if (stack_trace_var == NULL) {
|
|
stack_trace_var = new(Z) LocalVariable(
|
|
TokenPos(),
|
|
Symbols::StackTraceVar(),
|
|
Type::ZoneHandle(Z, Type::DynamicType()));
|
|
current_block_->scope->AddVariable(stack_trace_var);
|
|
}
|
|
|
|
const intptr_t try_pos = TokenPos();
|
|
ConsumeToken(); // Consume the 'try'.
|
|
|
|
SourceLabel* try_label = NULL;
|
|
if (label_name != NULL) {
|
|
try_label = SourceLabel::New(try_pos, label_name, SourceLabel::kStatement);
|
|
OpenBlock();
|
|
current_block_->scope->AddLabel(try_label);
|
|
}
|
|
|
|
// Now parse the 'try' block.
|
|
OpenBlock();
|
|
PushTryBlock(current_block_);
|
|
ExpectToken(Token::kLBRACE);
|
|
|
|
if (innermost_function().is_async_closure() ||
|
|
innermost_function().IsAsyncFunction()) {
|
|
SetupSavedTryContext(context_var);
|
|
}
|
|
|
|
ParseStatementSequence();
|
|
ExpectToken(Token::kRBRACE);
|
|
SequenceNode* try_block = CloseBlock();
|
|
|
|
if ((CurrentToken() != Token::kCATCH) && !IsLiteral("on") &&
|
|
(CurrentToken() != Token::kFINALLY)) {
|
|
ReportError("catch or finally clause expected");
|
|
}
|
|
|
|
// Now parse the 'catch' blocks if any.
|
|
try_blocks_list_->enter_catch();
|
|
const intptr_t handler_pos = TokenPos();
|
|
const GrowableObjectArray& handler_types =
|
|
GrowableObjectArray::Handle(Z, GrowableObjectArray::New());
|
|
bool needs_stack_trace = false;
|
|
SequenceNode* catch_handler_list =
|
|
ParseCatchClauses(handler_pos, exception_var, stack_trace_var,
|
|
handler_types, &needs_stack_trace);
|
|
|
|
TryBlocks* inner_try_block = PopTryBlock();
|
|
const intptr_t try_index = inner_try_block->try_index();
|
|
TryBlocks* outer_try_block = try_blocks_list_;
|
|
const intptr_t outer_try_index = (outer_try_block != NULL)
|
|
? outer_try_block->try_index()
|
|
: CatchClauseNode::kInvalidTryIndex;
|
|
|
|
// Finally parse the 'finally' block.
|
|
SequenceNode* finally_block = NULL;
|
|
if (CurrentToken() == Token::kFINALLY) {
|
|
ConsumeToken(); // Consume the 'finally'.
|
|
const intptr_t finally_pos = TokenPos();
|
|
// Add the finally block to the exit points recorded so far.
|
|
intptr_t node_index = 0;
|
|
AstNode* node_to_inline =
|
|
inner_try_block->GetNodeToInlineFinally(node_index);
|
|
while (node_to_inline != NULL) {
|
|
finally_block = ParseFinallyBlock();
|
|
InlinedFinallyNode* node = new(Z) InlinedFinallyNode(finally_pos,
|
|
finally_block,
|
|
context_var,
|
|
outer_try_index);
|
|
AddFinallyBlockToNode(node_to_inline, node);
|
|
node_index += 1;
|
|
node_to_inline = inner_try_block->GetNodeToInlineFinally(node_index);
|
|
tokens_iterator_.SetCurrentPosition(finally_pos);
|
|
}
|
|
finally_block = ParseFinallyBlock();
|
|
}
|
|
|
|
CatchClauseNode* catch_clause = new(Z) CatchClauseNode(
|
|
handler_pos,
|
|
catch_handler_list,
|
|
Array::ZoneHandle(Z, Array::MakeArray(handler_types)),
|
|
context_var,
|
|
exception_var,
|
|
stack_trace_var,
|
|
(finally_block != NULL) ?
|
|
AllocateTryIndex() : CatchClauseNode::kInvalidTryIndex,
|
|
needs_stack_trace);
|
|
|
|
// Now create the try/catch ast node and return it. If there is a label
|
|
// on the try/catch, close the block that's embedding the try statement
|
|
// and attach the label to it.
|
|
AstNode* try_catch_node = new(Z) TryCatchNode(
|
|
try_pos, try_block, context_var, catch_clause, finally_block, try_index);
|
|
|
|
if (try_label != NULL) {
|
|
current_block_->statements->Add(try_catch_node);
|
|
SequenceNode* sequence = CloseBlock();
|
|
sequence->set_label(try_label);
|
|
try_catch_node = sequence;
|
|
}
|
|
|
|
return try_catch_node;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseJump(String* label_name) {
|
|
TRACE_PARSER("ParseJump");
|
|
ASSERT(CurrentToken() == Token::kBREAK || CurrentToken() == Token::kCONTINUE);
|
|
Token::Kind jump_kind = CurrentToken();
|
|
const intptr_t jump_pos = TokenPos();
|
|
SourceLabel* target = NULL;
|
|
ConsumeToken();
|
|
if (IsIdentifier()) {
|
|
// Explicit label after break/continue.
|
|
const String& target_name = *CurrentLiteral();
|
|
ConsumeToken();
|
|
// Handle pathological cases first.
|
|
if (label_name != NULL && target_name.Equals(*label_name)) {
|
|
if (jump_kind == Token::kCONTINUE) {
|
|
ReportError(jump_pos, "'continue' jump to label '%s' is illegal",
|
|
target_name.ToCString());
|
|
}
|
|
// L: break L; is a no-op.
|
|
return NULL;
|
|
}
|
|
target = current_block_->scope->LookupLabel(target_name);
|
|
if (target == NULL && jump_kind == Token::kCONTINUE) {
|
|
// Either a reference to a non-existent label, or a forward reference
|
|
// to a case label that we haven't seen yet. If we are inside a switch
|
|
// statement, create a "forward reference" label in the scope of
|
|
// the switch statement.
|
|
LocalScope* switch_scope = current_block_->scope->LookupSwitchScope();
|
|
if (switch_scope != NULL) {
|
|
// We found a switch scope. Enter a forward reference to the label.
|
|
target = new(Z) SourceLabel(
|
|
TokenPos(), target_name, SourceLabel::kForward);
|
|
switch_scope->AddLabel(target);
|
|
}
|
|
}
|
|
if (target == NULL) {
|
|
ReportError(jump_pos, "label '%s' not found", target_name.ToCString());
|
|
}
|
|
} else {
|
|
target = current_block_->scope->LookupInnermostLabel(jump_kind);
|
|
if (target == NULL) {
|
|
ReportError(jump_pos, "'%s' is illegal here", Token::Str(jump_kind));
|
|
}
|
|
}
|
|
ASSERT(target != NULL);
|
|
if (jump_kind == Token::kCONTINUE) {
|
|
if (target->kind() == SourceLabel::kSwitch) {
|
|
ReportError(jump_pos, "'continue' jump to switch statement is illegal");
|
|
} else if (target->kind() == SourceLabel::kStatement) {
|
|
ReportError(jump_pos, "'continue' jump to label '%s' is illegal",
|
|
target->name().ToCString());
|
|
}
|
|
}
|
|
if (jump_kind == Token::kBREAK && target->kind() == SourceLabel::kCase) {
|
|
ReportError(jump_pos, "'break' to case clause label is illegal");
|
|
}
|
|
if (target->FunctionLevel() != current_block_->scope->function_level()) {
|
|
ReportError(jump_pos, "'%s' target must be in same function context",
|
|
Token::Str(jump_kind));
|
|
}
|
|
return new(Z) JumpNode(jump_pos, jump_kind, target);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseStatement() {
|
|
TRACE_PARSER("ParseStatement");
|
|
AstNode* statement = NULL;
|
|
intptr_t label_pos = 0;
|
|
String* label_name = NULL;
|
|
if (IsIdentifier()) {
|
|
if (LookaheadToken(1) == Token::kCOLON) {
|
|
// Statement starts with a label.
|
|
label_name = CurrentLiteral();
|
|
label_pos = TokenPos();
|
|
ASSERT(label_pos > 0);
|
|
ConsumeToken(); // Consume identifier.
|
|
ConsumeToken(); // Consume colon.
|
|
}
|
|
}
|
|
const intptr_t statement_pos = TokenPos();
|
|
const Token::Kind token = CurrentToken();
|
|
|
|
if (token == Token::kWHILE) {
|
|
statement = ParseWhileStatement(label_name);
|
|
} else if (token == Token::kFOR) {
|
|
statement = ParseForStatement(label_name);
|
|
} else if (IsAwaitKeyword() && (LookaheadToken(1) == Token::kFOR)) {
|
|
statement = ParseAwaitForStatement(label_name);
|
|
} else if (token == Token::kDO) {
|
|
statement = ParseDoWhileStatement(label_name);
|
|
} else if (token == Token::kSWITCH) {
|
|
statement = ParseSwitchStatement(label_name);
|
|
} else if (token == Token::kTRY) {
|
|
statement = ParseTryStatement(label_name);
|
|
} else if (token == Token::kRETURN) {
|
|
const intptr_t return_pos = TokenPos();
|
|
ConsumeToken();
|
|
if (CurrentToken() != Token::kSEMICOLON) {
|
|
if (current_function().IsConstructor() &&
|
|
(current_block_->scope->function_level() == 0)) {
|
|
ReportError(return_pos,
|
|
"return of a value not allowed in constructors");
|
|
}
|
|
AstNode* expr = ParseAwaitableExpr(kAllowConst, kConsumeCascades, NULL);
|
|
statement = new(Z) ReturnNode(statement_pos, expr);
|
|
} else {
|
|
statement = new(Z) ReturnNode(statement_pos);
|
|
}
|
|
AddNodeForFinallyInlining(statement);
|
|
ExpectSemicolon();
|
|
} else if (token == Token::kIF) {
|
|
statement = ParseIfStatement(label_name);
|
|
} else if (token == Token::kASSERT) {
|
|
statement = ParseAssertStatement();
|
|
ExpectSemicolon();
|
|
} else if (IsVariableDeclaration()) {
|
|
statement = ParseVariableDeclarationList();
|
|
ExpectSemicolon();
|
|
} else if (IsFunctionDeclaration()) {
|
|
statement = ParseFunctionStatement(false);
|
|
} else if (token == Token::kLBRACE) {
|
|
SourceLabel* label = NULL;
|
|
OpenBlock();
|
|
if (label_name != NULL) {
|
|
label = SourceLabel::New(label_pos, label_name, SourceLabel::kStatement);
|
|
current_block_->scope->AddLabel(label);
|
|
}
|
|
ConsumeToken();
|
|
ParseStatementSequence();
|
|
statement = CloseBlock();
|
|
if (label != NULL) {
|
|
statement->AsSequenceNode()->set_label(label);
|
|
}
|
|
ExpectToken(Token::kRBRACE);
|
|
} else if (token == Token::kBREAK) {
|
|
statement = ParseJump(label_name);
|
|
AddNodeForFinallyInlining(statement);
|
|
ExpectSemicolon();
|
|
} else if (token == Token::kCONTINUE) {
|
|
statement = ParseJump(label_name);
|
|
AddNodeForFinallyInlining(statement);
|
|
ExpectSemicolon();
|
|
} else if (token == Token::kSEMICOLON) {
|
|
// Empty statement, nothing to do.
|
|
ConsumeToken();
|
|
} else if (token == Token::kRETHROW) {
|
|
// Rethrow of current exception.
|
|
ConsumeToken();
|
|
ExpectSemicolon();
|
|
// Check if it is ok to do a rethrow.
|
|
if ((try_blocks_list_ == NULL) || !try_blocks_list_->inside_catch()) {
|
|
ReportError(statement_pos, "rethrow of an exception is not valid here");
|
|
}
|
|
// The exception and stack trace variables are bound in the block
|
|
// containing the try.
|
|
LocalScope* scope = try_blocks_list_->try_block()->scope->parent();
|
|
ASSERT(scope != NULL);
|
|
LocalVariable* excp_var =
|
|
scope->LocalLookupVariable(Symbols::ExceptionVar());
|
|
ASSERT(excp_var != NULL);
|
|
LocalVariable* trace_var =
|
|
scope->LocalLookupVariable(Symbols::StackTraceVar());
|
|
ASSERT(trace_var != NULL);
|
|
statement = new(Z) ThrowNode(
|
|
statement_pos,
|
|
new(Z) LoadLocalNode(statement_pos, excp_var),
|
|
new(Z) LoadLocalNode(statement_pos, trace_var));
|
|
} else {
|
|
statement = ParseAwaitableExpr(kAllowConst, kConsumeCascades, NULL);
|
|
ExpectSemicolon();
|
|
}
|
|
return statement;
|
|
}
|
|
|
|
|
|
void Parser::ReportError(const Error& error) {
|
|
Report::LongJump(error);
|
|
UNREACHABLE();
|
|
}
|
|
|
|
|
|
void Parser::ReportErrors(const Error& prev_error,
|
|
const Script& script, intptr_t token_pos,
|
|
const char* format, ...) {
|
|
va_list args;
|
|
va_start(args, format);
|
|
Report::LongJumpV(prev_error, script, token_pos, format, args);
|
|
va_end(args);
|
|
UNREACHABLE();
|
|
}
|
|
|
|
|
|
void Parser::ReportError(intptr_t token_pos, const char* format, ...) const {
|
|
va_list args;
|
|
va_start(args, format);
|
|
Report::MessageV(Report::kError, script_, token_pos, format, args);
|
|
va_end(args);
|
|
UNREACHABLE();
|
|
}
|
|
|
|
|
|
void Parser::ReportError(const char* format, ...) const {
|
|
va_list args;
|
|
va_start(args, format);
|
|
Report::MessageV(Report::kError, script_, TokenPos(), format, args);
|
|
va_end(args);
|
|
UNREACHABLE();
|
|
}
|
|
|
|
|
|
void Parser::ReportWarning(intptr_t token_pos, const char* format, ...) const {
|
|
va_list args;
|
|
va_start(args, format);
|
|
Report::MessageV(Report::kWarning, script_, token_pos, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
|
|
void Parser::ReportWarning(const char* format, ...) const {
|
|
va_list args;
|
|
va_start(args, format);
|
|
Report::MessageV(Report::kWarning, script_, TokenPos(), format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
|
|
void Parser::CheckToken(Token::Kind token_expected, const char* msg) {
|
|
if (CurrentToken() != token_expected) {
|
|
if (msg != NULL) {
|
|
ReportError("%s", msg);
|
|
} else {
|
|
ReportError("'%s' expected", Token::Str(token_expected));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::ExpectToken(Token::Kind token_expected) {
|
|
if (CurrentToken() != token_expected) {
|
|
ReportError("'%s' expected", Token::Str(token_expected));
|
|
}
|
|
ConsumeToken();
|
|
}
|
|
|
|
|
|
void Parser::ExpectSemicolon() {
|
|
if (CurrentToken() != Token::kSEMICOLON) {
|
|
ReportError("semicolon expected");
|
|
}
|
|
ConsumeToken();
|
|
}
|
|
|
|
|
|
void Parser::UnexpectedToken() {
|
|
ReportError("unexpected token '%s'",
|
|
CurrentToken() == Token::kIDENT ?
|
|
CurrentLiteral()->ToCString() : Token::Str(CurrentToken()));
|
|
}
|
|
|
|
|
|
String* Parser::ExpectUserDefinedTypeIdentifier(const char* msg) {
|
|
if (CurrentToken() != Token::kIDENT) {
|
|
ReportError("%s", msg);
|
|
}
|
|
String* ident = CurrentLiteral();
|
|
if (ident->Equals("dynamic")) {
|
|
ReportError("%s", msg);
|
|
}
|
|
ConsumeToken();
|
|
return ident;
|
|
}
|
|
|
|
|
|
// Check whether current token is an identifier or a built-in identifier.
|
|
String* Parser::ExpectIdentifier(const char* msg) {
|
|
if (!IsIdentifier()) {
|
|
ReportError("%s", msg);
|
|
}
|
|
String* ident = CurrentLiteral();
|
|
ConsumeToken();
|
|
return ident;
|
|
}
|
|
|
|
|
|
bool Parser::IsLiteral(const char* literal) {
|
|
return IsIdentifier() && CurrentLiteral()->Equals(literal);
|
|
}
|
|
|
|
|
|
bool Parser::IsAwaitKeyword() {
|
|
return await_is_keyword_ &&
|
|
(CurrentLiteral()->raw() == Symbols::Await().raw());
|
|
}
|
|
|
|
|
|
static bool IsIncrementOperator(Token::Kind token) {
|
|
return token == Token::kINCR || token == Token::kDECR;
|
|
}
|
|
|
|
|
|
static bool IsPrefixOperator(Token::Kind token) {
|
|
return (token == Token::kSUB) ||
|
|
(token == Token::kNOT) ||
|
|
(token == Token::kBIT_NOT);
|
|
}
|
|
|
|
|
|
SequenceNode* Parser::NodeAsSequenceNode(intptr_t sequence_pos,
|
|
AstNode* node,
|
|
LocalScope* scope) {
|
|
if ((node == NULL) || !node->IsSequenceNode()) {
|
|
SequenceNode* sequence = new SequenceNode(sequence_pos, scope);
|
|
if (node != NULL) {
|
|
sequence->Add(node);
|
|
}
|
|
return sequence;
|
|
}
|
|
return node->AsSequenceNode();
|
|
}
|
|
|
|
|
|
AstNode* Parser::ThrowTypeError(intptr_t type_pos, const AbstractType& type) {
|
|
ArgumentListNode* arguments = new(Z) ArgumentListNode(type_pos);
|
|
// Location argument.
|
|
arguments->Add(new(Z) LiteralNode(
|
|
type_pos, Integer::ZoneHandle(Z, Integer::New(type_pos))));
|
|
// Src value argument.
|
|
arguments->Add(new(Z) LiteralNode(type_pos, Instance::ZoneHandle(Z)));
|
|
// Dst type name argument.
|
|
arguments->Add(new(Z) LiteralNode(type_pos, Symbols::Malformed()));
|
|
// Dst name argument.
|
|
arguments->Add(new(Z) LiteralNode(type_pos, Symbols::Empty()));
|
|
// Malformed type error or malbounded type error.
|
|
const Error& error = Error::Handle(Z, type.error());
|
|
ASSERT(!error.IsNull());
|
|
arguments->Add(new(Z) LiteralNode(type_pos, String::ZoneHandle(Z,
|
|
Symbols::New(error.ToErrorCString()))));
|
|
return MakeStaticCall(Symbols::TypeError(),
|
|
Library::PrivateCoreLibName(Symbols::ThrowNew()),
|
|
arguments);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ThrowNoSuchMethodError(intptr_t call_pos,
|
|
const Class& cls,
|
|
const String& function_name,
|
|
ArgumentListNode* function_arguments,
|
|
InvocationMirror::Call im_call,
|
|
InvocationMirror::Type im_type,
|
|
const Function* func) {
|
|
ArgumentListNode* arguments = new(Z) ArgumentListNode(call_pos);
|
|
// Object receiver.
|
|
// If the function is external and dynamic, pass the actual receiver,
|
|
// otherwise, pass a class literal of the unresolved method's owner.
|
|
if ((func != NULL) && !func->IsNull() &&
|
|
func->is_external() && !func->is_static()) {
|
|
arguments->Add(LoadReceiver(func->token_pos()));
|
|
} else {
|
|
Type& type = Type::ZoneHandle(Z,
|
|
Type::New(cls, TypeArguments::Handle(Z), call_pos, Heap::kOld));
|
|
type ^= ClassFinalizer::FinalizeType(
|
|
current_class(), type, ClassFinalizer::kCanonicalize);
|
|
arguments->Add(new(Z) LiteralNode(call_pos, type));
|
|
}
|
|
// String memberName.
|
|
arguments->Add(new(Z) LiteralNode(
|
|
call_pos, String::ZoneHandle(Z, Symbols::New(function_name))));
|
|
// Smi invocation_type.
|
|
if (cls.IsTopLevel()) {
|
|
ASSERT(im_call == InvocationMirror::kStatic ||
|
|
im_call == InvocationMirror::kTopLevel);
|
|
im_call = InvocationMirror::kTopLevel;
|
|
}
|
|
arguments->Add(new(Z) LiteralNode(call_pos, Smi::ZoneHandle(Z,
|
|
Smi::New(InvocationMirror::EncodeType(im_call, im_type)))));
|
|
// List arguments.
|
|
if (function_arguments == NULL) {
|
|
arguments->Add(new(Z) LiteralNode(call_pos, Array::ZoneHandle(Z)));
|
|
} else {
|
|
ArrayNode* array = new(Z) ArrayNode(
|
|
call_pos,
|
|
Type::ZoneHandle(Z, Type::ArrayType()),
|
|
function_arguments->nodes());
|
|
arguments->Add(array);
|
|
}
|
|
// List argumentNames.
|
|
if (function_arguments == NULL) {
|
|
arguments->Add(new(Z) LiteralNode(call_pos, Array::ZoneHandle(Z)));
|
|
} else {
|
|
arguments->Add(new(Z) LiteralNode(call_pos, function_arguments->names()));
|
|
}
|
|
|
|
// List existingArgumentNames.
|
|
// Check if there exists a function with the same name unless caller
|
|
// has done the lookup already. If there is a function with the same
|
|
// name but incompatible parameters, inform the NoSuchMethodError what the
|
|
// expected parameters are.
|
|
Function& function = Function::Handle(Z);
|
|
if (func != NULL) {
|
|
function = func->raw();
|
|
} else {
|
|
function = cls.LookupStaticFunction(function_name);
|
|
}
|
|
Array& array = Array::ZoneHandle(Z);
|
|
// An unpatched external function is treated as an unresolved function.
|
|
if (!function.IsNull() && !function.is_external()) {
|
|
// The constructor for NoSuchMethodError takes a list of existing
|
|
// parameter names to produce a descriptive error message explaining
|
|
// the parameter mismatch. The problem is that the array of names
|
|
// does not describe which parameters are optional positional or
|
|
// named, which can lead to confusing error messages.
|
|
// Since the NoSuchMethodError class only uses the list to produce
|
|
// a string describing the expected parameters, we construct a more
|
|
// descriptive string here and pass it as the only element of the
|
|
// "existingArgumentNames" array of the NoSuchMethodError constructor.
|
|
// TODO(13471): Separate the implementations of NoSuchMethodError
|
|
// between dart2js and VM. Update the constructor to accept a string
|
|
// describing the formal parameters of an incompatible call target.
|
|
array = Array::New(1, Heap::kOld);
|
|
array.SetAt(0, String::Handle(Z, function.UserVisibleFormalParameters()));
|
|
}
|
|
arguments->Add(new(Z) LiteralNode(call_pos, array));
|
|
|
|
return MakeStaticCall(Symbols::NoSuchMethodError(),
|
|
Library::PrivateCoreLibName(Symbols::ThrowNew()),
|
|
arguments);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseBinaryExpr(int min_preced) {
|
|
TRACE_PARSER("ParseBinaryExpr");
|
|
ASSERT(min_preced >= Token::Precedence(Token::kOR));
|
|
AstNode* left_operand = ParseUnaryExpr();
|
|
if (left_operand->IsPrimaryNode() &&
|
|
(left_operand->AsPrimaryNode()->IsSuper())) {
|
|
ReportError(left_operand->token_pos(), "illegal use of 'super'");
|
|
}
|
|
int current_preced = Token::Precedence(CurrentToken());
|
|
while (current_preced >= min_preced) {
|
|
while (Token::Precedence(CurrentToken()) == current_preced) {
|
|
Token::Kind op_kind = CurrentToken();
|
|
const intptr_t op_pos = TokenPos();
|
|
ConsumeToken();
|
|
AstNode* right_operand = NULL;
|
|
if ((op_kind != Token::kIS) && (op_kind != Token::kAS)) {
|
|
right_operand = ParseBinaryExpr(current_preced + 1);
|
|
} else {
|
|
// For 'is' and 'as' we expect the right operand to be a type.
|
|
if ((op_kind == Token::kIS) && (CurrentToken() == Token::kNOT)) {
|
|
ConsumeToken();
|
|
op_kind = Token::kISNOT;
|
|
}
|
|
const intptr_t type_pos = TokenPos();
|
|
const AbstractType& type = AbstractType::ZoneHandle(Z,
|
|
ParseType(ClassFinalizer::kCanonicalize));
|
|
if (!type.IsInstantiated() &&
|
|
(current_block_->scope->function_level() > 0)) {
|
|
// Make sure that the instantiator is captured.
|
|
CaptureInstantiator();
|
|
}
|
|
right_operand = new(Z) TypeNode(type_pos, type);
|
|
// In production mode, the type may be malformed.
|
|
// In checked mode, the type may be malformed or malbounded.
|
|
if (((op_kind == Token::kIS) || (op_kind == Token::kISNOT) ||
|
|
(op_kind == Token::kAS)) &&
|
|
type.IsMalformedOrMalbounded()) {
|
|
// Note that a type error is thrown in a type test or in
|
|
// a type cast even if the tested value is null.
|
|
// We need to evaluate the left operand for potential
|
|
// side effects.
|
|
LetNode* let = new(Z) LetNode(left_operand->token_pos());
|
|
let->AddNode(left_operand);
|
|
let->AddNode(ThrowTypeError(type_pos, type));
|
|
left_operand = let;
|
|
break; // Type checks and casts can't be chained.
|
|
}
|
|
}
|
|
if (Token::IsRelationalOperator(op_kind)
|
|
|| Token::IsTypeTestOperator(op_kind)
|
|
|| Token::IsTypeCastOperator(op_kind)
|
|
|| Token::IsEqualityOperator(op_kind)) {
|
|
if (Token::IsTypeTestOperator(op_kind) ||
|
|
Token::IsTypeCastOperator(op_kind)) {
|
|
if (!right_operand->AsTypeNode()->type().IsInstantiated()) {
|
|
EnsureExpressionTemp();
|
|
}
|
|
}
|
|
left_operand = new(Z) ComparisonNode(
|
|
op_pos, op_kind, left_operand, right_operand);
|
|
break; // Equality and relational operators cannot be chained.
|
|
} else {
|
|
left_operand = OptimizeBinaryOpNode(
|
|
op_pos, op_kind, left_operand, right_operand);
|
|
}
|
|
}
|
|
current_preced--;
|
|
}
|
|
return left_operand;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseAwaitableExprList() {
|
|
TRACE_PARSER("ParseAwaitableExprList");
|
|
SequenceNode* preamble = NULL;
|
|
AstNode* expressions = ParseAwaitableExpr(
|
|
kAllowConst, kConsumeCascades, &preamble);
|
|
if (preamble != NULL) {
|
|
preamble->Add(expressions);
|
|
expressions = preamble;
|
|
}
|
|
if (CurrentToken() == Token::kCOMMA) {
|
|
// Collect comma-separated expressions in a non scope owning sequence node.
|
|
SequenceNode* list = new(Z) SequenceNode(TokenPos(), NULL);
|
|
list->Add(expressions);
|
|
while (CurrentToken() == Token::kCOMMA) {
|
|
ConsumeToken();
|
|
preamble = NULL;
|
|
AstNode* expr = ParseAwaitableExpr(
|
|
kAllowConst, kConsumeCascades, &preamble);
|
|
if (preamble != NULL) {
|
|
list->Add(preamble);
|
|
}
|
|
list->Add(expr);
|
|
}
|
|
expressions = list;
|
|
}
|
|
return expressions;
|
|
}
|
|
|
|
|
|
void Parser::EnsureExpressionTemp() {
|
|
// Temporary used later by the flow_graph_builder.
|
|
parsed_function()->EnsureExpressionTemp();
|
|
}
|
|
|
|
|
|
LocalVariable* Parser::CreateTempConstVariable(intptr_t token_pos,
|
|
const char* s) {
|
|
char name[64];
|
|
OS::SNPrint(name, 64, ":%s%" Pd, s, token_pos);
|
|
LocalVariable* temp = new(Z) LocalVariable(
|
|
token_pos,
|
|
String::ZoneHandle(Z, Symbols::New(name)),
|
|
Type::ZoneHandle(Z, Type::DynamicType()));
|
|
temp->set_is_final();
|
|
current_block_->scope->AddVariable(temp);
|
|
return temp;
|
|
}
|
|
|
|
|
|
// TODO(srdjan): Implement other optimizations.
|
|
AstNode* Parser::OptimizeBinaryOpNode(intptr_t op_pos,
|
|
Token::Kind binary_op,
|
|
AstNode* lhs,
|
|
AstNode* rhs) {
|
|
LiteralNode* lhs_literal = lhs->AsLiteralNode();
|
|
LiteralNode* rhs_literal = rhs->AsLiteralNode();
|
|
if ((lhs_literal != NULL) && (rhs_literal != NULL)) {
|
|
if (lhs_literal->literal().IsDouble() &&
|
|
rhs_literal->literal().IsDouble()) {
|
|
double left_double = Double::Cast(lhs_literal->literal()).value();
|
|
double right_double = Double::Cast(rhs_literal->literal()).value();
|
|
if (binary_op == Token::kDIV) {
|
|
const Double& dbl_obj = Double::ZoneHandle(Z,
|
|
Double::NewCanonical((left_double / right_double)));
|
|
return new(Z) LiteralNode(op_pos, dbl_obj);
|
|
}
|
|
}
|
|
}
|
|
if ((binary_op == Token::kAND) || (binary_op == Token::kOR)) {
|
|
EnsureExpressionTemp();
|
|
}
|
|
if (binary_op == Token::kBIT_AND) {
|
|
// Normalize so that rhs is a literal if any is.
|
|
if ((rhs_literal == NULL) && (lhs_literal != NULL)) {
|
|
// Swap.
|
|
LiteralNode* temp = rhs_literal;
|
|
rhs_literal = lhs_literal;
|
|
lhs_literal = temp;
|
|
}
|
|
if ((rhs_literal != NULL) &&
|
|
(rhs_literal->literal().IsSmi() || rhs_literal->literal().IsMint())) {
|
|
const int64_t val = Integer::Cast(rhs_literal->literal()).AsInt64Value();
|
|
if ((0 <= val) && (Utils::IsUint(32, val))) {
|
|
if (lhs->IsBinaryOpNode() &&
|
|
(lhs->AsBinaryOpNode()->kind() == Token::kSHL)) {
|
|
// Merge SHL and BIT_AND into one "SHL with mask" node.
|
|
BinaryOpNode* old = lhs->AsBinaryOpNode();
|
|
BinaryOpWithMask32Node* binop = new(Z) BinaryOpWithMask32Node(
|
|
old->token_pos(), old->kind(), old->left(), old->right(), val);
|
|
return binop;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return new(Z) BinaryOpNode(op_pos, binary_op, lhs, rhs);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ExpandAssignableOp(intptr_t op_pos,
|
|
Token::Kind assignment_op,
|
|
AstNode* lhs,
|
|
AstNode* rhs) {
|
|
TRACE_PARSER("ExpandAssignableOp");
|
|
switch (assignment_op) {
|
|
case Token::kASSIGN:
|
|
return rhs;
|
|
case Token::kASSIGN_ADD:
|
|
return new(Z) BinaryOpNode(op_pos, Token::kADD, lhs, rhs);
|
|
case Token::kASSIGN_SUB:
|
|
return new(Z) BinaryOpNode(op_pos, Token::kSUB, lhs, rhs);
|
|
case Token::kASSIGN_MUL:
|
|
return new(Z) BinaryOpNode(op_pos, Token::kMUL, lhs, rhs);
|
|
case Token::kASSIGN_TRUNCDIV:
|
|
return new(Z) BinaryOpNode(op_pos, Token::kTRUNCDIV, lhs, rhs);
|
|
case Token::kASSIGN_DIV:
|
|
return new(Z) BinaryOpNode(op_pos, Token::kDIV, lhs, rhs);
|
|
case Token::kASSIGN_MOD:
|
|
return new(Z) BinaryOpNode(op_pos, Token::kMOD, lhs, rhs);
|
|
case Token::kASSIGN_SHR:
|
|
return new(Z) BinaryOpNode(op_pos, Token::kSHR, lhs, rhs);
|
|
case Token::kASSIGN_SHL:
|
|
return new(Z) BinaryOpNode(op_pos, Token::kSHL, lhs, rhs);
|
|
case Token::kASSIGN_OR:
|
|
return new(Z) BinaryOpNode(op_pos, Token::kBIT_OR, lhs, rhs);
|
|
case Token::kASSIGN_AND:
|
|
return new(Z) BinaryOpNode(op_pos, Token::kBIT_AND, lhs, rhs);
|
|
case Token::kASSIGN_XOR:
|
|
return new(Z) BinaryOpNode(op_pos, Token::kBIT_XOR, lhs, rhs);
|
|
default:
|
|
ReportError(op_pos,
|
|
"internal error: ExpandAssignableOp '%s' unimplemented",
|
|
Token::Name(assignment_op));
|
|
UNIMPLEMENTED();
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
// Evaluates the value of the compile time constant expression
|
|
// and returns a literal node for the value.
|
|
AstNode* Parser::FoldConstExpr(intptr_t expr_pos, AstNode* expr) {
|
|
if (expr->IsLiteralNode()) {
|
|
return expr;
|
|
}
|
|
if (expr->EvalConstExpr() == NULL) {
|
|
ReportError(expr_pos, "expression is not a valid compile-time constant");
|
|
}
|
|
return new(Z) LiteralNode(
|
|
expr_pos, EvaluateConstExpr(expr_pos, expr));
|
|
}
|
|
|
|
|
|
LetNode* Parser::PrepareCompoundAssignmentNodes(AstNode** expr) {
|
|
AstNode* node = *expr;
|
|
intptr_t token_pos = node->token_pos();
|
|
LetNode* result = new(Z) LetNode(token_pos);
|
|
if (node->IsLoadIndexedNode()) {
|
|
LoadIndexedNode* load_indexed = node->AsLoadIndexedNode();
|
|
AstNode* array = load_indexed->array();
|
|
AstNode* index = load_indexed->index_expr();
|
|
if (!IsSimpleLocalOrLiteralNode(load_indexed->array())) {
|
|
LocalVariable* t0 = result->AddInitializer(load_indexed->array());
|
|
array = new(Z) LoadLocalNode(token_pos, t0);
|
|
}
|
|
if (!IsSimpleLocalOrLiteralNode(load_indexed->index_expr())) {
|
|
LocalVariable* t1 = result->AddInitializer(
|
|
load_indexed->index_expr());
|
|
index = new(Z) LoadLocalNode(token_pos, t1);
|
|
}
|
|
*expr = new(Z) LoadIndexedNode(token_pos,
|
|
array,
|
|
index,
|
|
load_indexed->super_class());
|
|
return result;
|
|
}
|
|
if (node->IsInstanceGetterNode()) {
|
|
InstanceGetterNode* getter = node->AsInstanceGetterNode();
|
|
AstNode* receiver = getter->receiver();
|
|
if (!IsSimpleLocalOrLiteralNode(getter->receiver())) {
|
|
LocalVariable* t0 = result->AddInitializer(getter->receiver());
|
|
receiver = new(Z) LoadLocalNode(token_pos, t0);
|
|
}
|
|
*expr = new(Z) InstanceGetterNode(
|
|
token_pos, receiver, getter->field_name());
|
|
return result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
// Check whether the syntax of expression expr is a grammatically legal
|
|
// assignable expression. This check is used to detect situations where
|
|
// the expression itself is assignable, but the source is grammatically
|
|
// wrong. The AST representation of an expression cannot distinguish
|
|
// between x = 0 and (x) = 0. The latter is illegal.
|
|
// A syntactically legal assignable expression always ends with an
|
|
// identifier token or a ] token. We rewind the token iterator and
|
|
// check whether the token before end_pos is an identifier or ].
|
|
bool Parser::IsLegalAssignableSyntax(AstNode* expr, intptr_t end_pos) {
|
|
ASSERT(expr->token_pos() >= 0);
|
|
ASSERT(expr->token_pos() < end_pos);
|
|
SetPosition(expr->token_pos());
|
|
Token::Kind token = Token::kILLEGAL;
|
|
while (TokenPos() < end_pos) {
|
|
token = CurrentToken();
|
|
ConsumeToken();
|
|
}
|
|
ASSERT(TokenPos() == end_pos);
|
|
return Token::IsIdentifier(token) || (token == Token::kRBRACK);
|
|
}
|
|
|
|
|
|
AstNode* Parser::CreateAssignmentNode(AstNode* original,
|
|
AstNode* rhs,
|
|
const String* left_ident,
|
|
intptr_t left_pos) {
|
|
AstNode* result = original->MakeAssignmentNode(rhs);
|
|
if (result == NULL) {
|
|
String& name = String::ZoneHandle(Z);
|
|
const Class* target_cls = ¤t_class();
|
|
if (original->IsTypeNode()) {
|
|
name = Symbols::New(original->AsTypeNode()->TypeName());
|
|
} else if (original->IsLoadStaticFieldNode()) {
|
|
name = original->AsLoadStaticFieldNode()->field().name();
|
|
target_cls = &Class::Handle(Z,
|
|
original->AsLoadStaticFieldNode()->field().owner());
|
|
} else if ((left_ident != NULL) &&
|
|
(original->IsLiteralNode() ||
|
|
original->IsLoadLocalNode())) {
|
|
name = left_ident->raw();
|
|
}
|
|
if (name.IsNull()) {
|
|
ReportError(left_pos, "expression is not assignable");
|
|
}
|
|
result = ThrowNoSuchMethodError(
|
|
original->token_pos(),
|
|
*target_cls,
|
|
String::Handle(Z, Field::SetterName(name)),
|
|
NULL, // No arguments.
|
|
InvocationMirror::kStatic,
|
|
original->IsLoadLocalNode() ?
|
|
InvocationMirror::kLocalVar : InvocationMirror::kSetter,
|
|
NULL); // No existing function.
|
|
} else if (result->IsStoreIndexedNode() ||
|
|
result->IsInstanceSetterNode() ||
|
|
result->IsStaticSetterNode() ||
|
|
result->IsStoreStaticFieldNode() ||
|
|
result->IsStoreLocalNode()) {
|
|
// Ensure that the expression temp is allocated for nodes that may need it.
|
|
EnsureExpressionTemp();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseCascades(AstNode* expr) {
|
|
intptr_t cascade_pos = TokenPos();
|
|
LetNode* cascade = new(Z) LetNode(cascade_pos);
|
|
LocalVariable* cascade_receiver_var = cascade->AddInitializer(expr);
|
|
while (CurrentToken() == Token::kCASCADE) {
|
|
cascade_pos = TokenPos();
|
|
LoadLocalNode* load_cascade_receiver =
|
|
new(Z) LoadLocalNode(cascade_pos, cascade_receiver_var);
|
|
if (Token::IsIdentifier(LookaheadToken(1))) {
|
|
// Replace .. with . for ParseSelectors().
|
|
token_kind_ = Token::kPERIOD;
|
|
} else if (LookaheadToken(1) == Token::kLBRACK) {
|
|
ConsumeToken();
|
|
} else {
|
|
ReportError("identifier or [ expected after ..");
|
|
}
|
|
String* expr_ident =
|
|
Token::IsIdentifier(CurrentToken()) ? CurrentLiteral() : NULL;
|
|
const intptr_t expr_pos = TokenPos();
|
|
expr = ParseSelectors(load_cascade_receiver, true);
|
|
|
|
// Assignments after a cascade are part of the cascade. The
|
|
// assigned expression must not contain cascades.
|
|
if (Token::IsAssignmentOperator(CurrentToken())) {
|
|
Token::Kind assignment_op = CurrentToken();
|
|
const intptr_t assignment_pos = TokenPos();
|
|
ConsumeToken();
|
|
AstNode* right_expr = ParseExpr(kAllowConst, kNoCascades);
|
|
if (assignment_op != Token::kASSIGN) {
|
|
// Compound assignment: store inputs with side effects into
|
|
// temporary locals.
|
|
LetNode* let_expr = PrepareCompoundAssignmentNodes(&expr);
|
|
right_expr =
|
|
ExpandAssignableOp(assignment_pos, assignment_op, expr, right_expr);
|
|
AstNode* assign_expr =
|
|
CreateAssignmentNode(expr, right_expr, expr_ident, expr_pos);
|
|
ASSERT(assign_expr != NULL);
|
|
let_expr->AddNode(assign_expr);
|
|
expr = let_expr;
|
|
} else {
|
|
right_expr =
|
|
ExpandAssignableOp(assignment_pos, assignment_op, expr, right_expr);
|
|
AstNode* assign_expr =
|
|
CreateAssignmentNode(expr, right_expr, expr_ident, expr_pos);
|
|
ASSERT(assign_expr != NULL);
|
|
expr = assign_expr;
|
|
}
|
|
}
|
|
cascade->AddNode(expr);
|
|
}
|
|
// The result is an expression with the (side effects of the) cascade
|
|
// sequence followed by the (value of the) receiver temp variable load.
|
|
cascade->AddNode(new(Z) LoadLocalNode(cascade_pos, cascade_receiver_var));
|
|
return cascade;
|
|
}
|
|
|
|
|
|
// Convert loading of a static const field into a literal node.
|
|
static AstNode* LiteralIfStaticConst(Zone* zone, AstNode* expr) {
|
|
if (expr->IsLoadStaticFieldNode()) {
|
|
const Field& field = expr->AsLoadStaticFieldNode()->field();
|
|
if (field.is_const()) {
|
|
ASSERT(field.value() != Object::sentinel().raw());
|
|
ASSERT(field.value() != Object::transition_sentinel().raw());
|
|
return new(zone) LiteralNode(expr->token_pos(),
|
|
Instance::ZoneHandle(zone, field.value()));
|
|
}
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseAwaitableExpr(bool require_compiletime_const,
|
|
bool consume_cascades,
|
|
SequenceNode** await_preamble) {
|
|
TRACE_PARSER("ParseAwaitableExpr");
|
|
BoolScope saved_seen_await(&parsed_function()->have_seen_await_expr_, false);
|
|
AstNode* expr = ParseExpr(require_compiletime_const, consume_cascades);
|
|
if (parsed_function()->have_seen_await()) {
|
|
// Make sure we do not reuse the scope to avoid creating contexts that we
|
|
// are unaware of, i.e, creating contexts that have already been covered.
|
|
// See FlowGraphBuilder::VisitSequenceNode() for details on when contexts
|
|
// are created.
|
|
OpenBlock();
|
|
AwaitTransformer at(current_block_->statements,
|
|
*parsed_function(),
|
|
async_temp_scope_);
|
|
AstNode* result = at.Transform(expr);
|
|
SequenceNode* preamble = CloseBlock();
|
|
if (await_preamble == NULL) {
|
|
current_block_->statements->Add(preamble);
|
|
} else {
|
|
*await_preamble = preamble;
|
|
}
|
|
return result;
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseExpr(bool require_compiletime_const,
|
|
bool consume_cascades) {
|
|
TRACE_PARSER("ParseExpr");
|
|
String* expr_ident =
|
|
Token::IsIdentifier(CurrentToken()) ? CurrentLiteral() : NULL;
|
|
const intptr_t expr_pos = TokenPos();
|
|
|
|
if (CurrentToken() == Token::kTHROW) {
|
|
if (require_compiletime_const) {
|
|
ReportError("'throw expr' is not a valid compile-time constant");
|
|
}
|
|
ConsumeToken();
|
|
if (CurrentToken() == Token::kSEMICOLON) {
|
|
ReportError("expression expected after throw");
|
|
}
|
|
AstNode* expr = ParseExpr(require_compiletime_const, consume_cascades);
|
|
return new(Z) ThrowNode(expr_pos, expr, NULL);
|
|
}
|
|
AstNode* expr = ParseConditionalExpr();
|
|
if (!Token::IsAssignmentOperator(CurrentToken())) {
|
|
if ((CurrentToken() == Token::kCASCADE) && consume_cascades) {
|
|
return ParseCascades(expr);
|
|
}
|
|
if (require_compiletime_const) {
|
|
expr = FoldConstExpr(expr_pos, expr);
|
|
} else {
|
|
expr = LiteralIfStaticConst(Z, expr);
|
|
}
|
|
return expr;
|
|
}
|
|
// Assignment expressions.
|
|
if (!IsLegalAssignableSyntax(expr, TokenPos())) {
|
|
ReportError(expr_pos, "expression is not assignable");
|
|
}
|
|
const Token::Kind assignment_op = CurrentToken();
|
|
const intptr_t assignment_pos = TokenPos();
|
|
ConsumeToken();
|
|
const intptr_t right_expr_pos = TokenPos();
|
|
if (require_compiletime_const && (assignment_op != Token::kASSIGN)) {
|
|
ReportError(right_expr_pos,
|
|
"expression is not a valid compile-time constant");
|
|
}
|
|
AstNode* right_expr = ParseExpr(require_compiletime_const, consume_cascades);
|
|
if (assignment_op != Token::kASSIGN) {
|
|
// Compound assignment: store inputs with side effects into temp. locals.
|
|
LetNode* let_expr = PrepareCompoundAssignmentNodes(&expr);
|
|
AstNode* assigned_value =
|
|
ExpandAssignableOp(assignment_pos, assignment_op, expr, right_expr);
|
|
AstNode* assign_expr =
|
|
CreateAssignmentNode(expr, assigned_value, expr_ident, expr_pos);
|
|
ASSERT(assign_expr != NULL);
|
|
let_expr->AddNode(assign_expr);
|
|
return let_expr;
|
|
} else {
|
|
AstNode* assigned_value = LiteralIfStaticConst(Z, right_expr);
|
|
AstNode* assign_expr =
|
|
CreateAssignmentNode(expr, assigned_value, expr_ident, expr_pos);
|
|
ASSERT(assign_expr != NULL);
|
|
return assign_expr;
|
|
}
|
|
}
|
|
|
|
|
|
LiteralNode* Parser::ParseConstExpr() {
|
|
TRACE_PARSER("ParseConstExpr");
|
|
intptr_t expr_pos = TokenPos();
|
|
AstNode* expr = ParseExpr(kRequireConst, kNoCascades);
|
|
if (!expr->IsLiteralNode()) {
|
|
ReportError(expr_pos, "expression must be a compile-time constant");
|
|
}
|
|
return expr->AsLiteralNode();
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseConditionalExpr() {
|
|
TRACE_PARSER("ParseConditionalExpr");
|
|
const intptr_t expr_pos = TokenPos();
|
|
AstNode* expr = ParseBinaryExpr(Token::Precedence(Token::kOR));
|
|
if (CurrentToken() == Token::kCONDITIONAL) {
|
|
EnsureExpressionTemp();
|
|
ConsumeToken();
|
|
AstNode* expr1 = ParseExpr(kAllowConst, kNoCascades);
|
|
ExpectToken(Token::kCOLON);
|
|
AstNode* expr2 = ParseExpr(kAllowConst, kNoCascades);
|
|
expr = new(Z) ConditionalExprNode(expr_pos, expr, expr1, expr2);
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseUnaryExpr() {
|
|
TRACE_PARSER("ParseUnaryExpr");
|
|
AstNode* expr = NULL;
|
|
const intptr_t op_pos = TokenPos();
|
|
if (IsAwaitKeyword()) {
|
|
TRACE_PARSER("ParseAwaitExpr");
|
|
ConsumeToken();
|
|
parsed_function()->record_await();
|
|
expr = new (Z) AwaitNode(TokenPos(), ParseUnaryExpr());
|
|
} else if (IsPrefixOperator(CurrentToken())) {
|
|
Token::Kind unary_op = CurrentToken();
|
|
if (unary_op == Token::kSUB) {
|
|
unary_op = Token::kNEGATE;
|
|
}
|
|
ConsumeToken();
|
|
expr = ParseUnaryExpr();
|
|
if (expr->IsPrimaryNode() && (expr->AsPrimaryNode()->IsSuper())) {
|
|
expr = BuildUnarySuperOperator(unary_op, expr->AsPrimaryNode());
|
|
} else {
|
|
expr = UnaryOpNode::UnaryOpOrLiteral(op_pos, unary_op, expr);
|
|
}
|
|
} else if (IsIncrementOperator(CurrentToken())) {
|
|
Token::Kind incr_op = CurrentToken();
|
|
ConsumeToken();
|
|
String* expr_ident =
|
|
Token::IsIdentifier(CurrentToken()) ? CurrentLiteral() : NULL;
|
|
const intptr_t expr_pos = TokenPos();
|
|
expr = ParseUnaryExpr();
|
|
if (!IsLegalAssignableSyntax(expr, TokenPos())) {
|
|
ReportError(expr_pos, "expression is not assignable");
|
|
}
|
|
// Is prefix.
|
|
LetNode* let_expr = PrepareCompoundAssignmentNodes(&expr);
|
|
Token::Kind binary_op =
|
|
(incr_op == Token::kINCR) ? Token::kADD : Token::kSUB;
|
|
BinaryOpNode* add = new(Z) BinaryOpNode(
|
|
op_pos,
|
|
binary_op,
|
|
expr,
|
|
new(Z) LiteralNode(op_pos, Smi::ZoneHandle(Z, Smi::New(1))));
|
|
AstNode* store = CreateAssignmentNode(expr, add, expr_ident, expr_pos);
|
|
ASSERT(store != NULL);
|
|
let_expr->AddNode(store);
|
|
expr = let_expr;
|
|
} else {
|
|
expr = ParsePostfixExpr();
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
|
|
ArgumentListNode* Parser::ParseActualParameters(
|
|
ArgumentListNode* implicit_arguments,
|
|
bool require_const) {
|
|
TRACE_PARSER("ParseActualParameters");
|
|
ASSERT(CurrentToken() == Token::kLPAREN);
|
|
const bool saved_mode = SetAllowFunctionLiterals(true);
|
|
ArgumentListNode* arguments;
|
|
if (implicit_arguments == NULL) {
|
|
arguments = new(Z) ArgumentListNode(TokenPos());
|
|
} else {
|
|
arguments = implicit_arguments;
|
|
}
|
|
const GrowableObjectArray& names = GrowableObjectArray::Handle(Z,
|
|
GrowableObjectArray::New(Heap::kOld));
|
|
bool named_argument_seen = false;
|
|
if (LookaheadToken(1) != Token::kRPAREN) {
|
|
String& arg_name = String::Handle(Z);
|
|
do {
|
|
ASSERT((CurrentToken() == Token::kLPAREN) ||
|
|
(CurrentToken() == Token::kCOMMA));
|
|
ConsumeToken();
|
|
if (IsIdentifier() && (LookaheadToken(1) == Token::kCOLON)) {
|
|
named_argument_seen = true;
|
|
// The canonicalization of the arguments descriptor array built in
|
|
// the code generator requires that the names are symbols, i.e.
|
|
// canonicalized strings.
|
|
ASSERT(CurrentLiteral()->IsSymbol());
|
|
for (int i = 0; i < names.Length(); i++) {
|
|
arg_name ^= names.At(i);
|
|
if (CurrentLiteral()->Equals(arg_name)) {
|
|
ReportError("duplicate named argument");
|
|
}
|
|
}
|
|
names.Add(*CurrentLiteral());
|
|
ConsumeToken(); // ident.
|
|
ConsumeToken(); // colon.
|
|
} else if (named_argument_seen) {
|
|
ReportError("named argument expected");
|
|
}
|
|
arguments->Add(ParseExpr(require_const, kConsumeCascades));
|
|
} while (CurrentToken() == Token::kCOMMA);
|
|
} else {
|
|
ConsumeToken();
|
|
}
|
|
ExpectToken(Token::kRPAREN);
|
|
SetAllowFunctionLiterals(saved_mode);
|
|
if (named_argument_seen) {
|
|
arguments->set_names(Array::Handle(Z, Array::MakeArray(names)));
|
|
}
|
|
return arguments;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseStaticCall(const Class& cls,
|
|
const String& func_name,
|
|
intptr_t ident_pos) {
|
|
TRACE_PARSER("ParseStaticCall");
|
|
const intptr_t call_pos = TokenPos();
|
|
ASSERT(CurrentToken() == Token::kLPAREN);
|
|
ArgumentListNode* arguments = ParseActualParameters(NULL, kAllowConst);
|
|
const int num_arguments = arguments->length();
|
|
const Function& func = Function::ZoneHandle(Z,
|
|
Resolver::ResolveStatic(cls,
|
|
func_name,
|
|
num_arguments,
|
|
arguments->names()));
|
|
if (func.IsNull()) {
|
|
// Check if there is a static field of the same name, it could be a closure
|
|
// and so we try and invoke the closure.
|
|
AstNode* closure = NULL;
|
|
const Field& field = Field::ZoneHandle(Z, cls.LookupStaticField(func_name));
|
|
Function& func = Function::ZoneHandle(Z);
|
|
if (field.IsNull()) {
|
|
// No field, check if we have an explicit getter function.
|
|
const String& getter_name =
|
|
String::ZoneHandle(Z, Field::GetterName(func_name));
|
|
const int kNumArguments = 0; // no arguments.
|
|
func = Resolver::ResolveStatic(cls,
|
|
getter_name,
|
|
kNumArguments,
|
|
Object::empty_array());
|
|
if (!func.IsNull()) {
|
|
ASSERT(func.kind() != RawFunction::kImplicitStaticFinalGetter);
|
|
closure = new(Z) StaticGetterNode(
|
|
call_pos,
|
|
NULL,
|
|
false,
|
|
Class::ZoneHandle(Z, cls.raw()),
|
|
func_name);
|
|
return BuildClosureCall(call_pos, closure, arguments);
|
|
}
|
|
} else {
|
|
closure = GenerateStaticFieldLookup(field, call_pos);
|
|
return BuildClosureCall(call_pos, closure, arguments);
|
|
}
|
|
// Could not resolve static method: throw a NoSuchMethodError.
|
|
return ThrowNoSuchMethodError(ident_pos,
|
|
cls,
|
|
func_name,
|
|
arguments,
|
|
InvocationMirror::kStatic,
|
|
InvocationMirror::kMethod,
|
|
NULL); // No existing function.
|
|
} else if (cls.IsTopLevel() &&
|
|
(cls.library() == Library::CoreLibrary()) &&
|
|
(func.name() == Symbols::Identical().raw())) {
|
|
// This is the predefined toplevel function identical(a,b).
|
|
// Create a comparison node instead of a static call to the function, unless
|
|
// javascript warnings are desired and identical is not invoked from a patch
|
|
// source.
|
|
if (!FLAG_warn_on_javascript_compatibility || is_patch_source()) {
|
|
ASSERT(num_arguments == 2);
|
|
return new(Z) ComparisonNode(ident_pos,
|
|
Token::kEQ_STRICT,
|
|
arguments->NodeAt(0),
|
|
arguments->NodeAt(1));
|
|
}
|
|
}
|
|
return new(Z) StaticCallNode(call_pos, func, arguments);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseInstanceCall(AstNode* receiver, const String& func_name) {
|
|
TRACE_PARSER("ParseInstanceCall");
|
|
const intptr_t call_pos = TokenPos();
|
|
CheckToken(Token::kLPAREN);
|
|
ArgumentListNode* arguments = ParseActualParameters(NULL, kAllowConst);
|
|
return new(Z) InstanceCallNode(call_pos, receiver, func_name, arguments);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseClosureCall(AstNode* closure) {
|
|
TRACE_PARSER("ParseClosureCall");
|
|
const intptr_t call_pos = TokenPos();
|
|
ASSERT(CurrentToken() == Token::kLPAREN);
|
|
ArgumentListNode* arguments = ParseActualParameters(NULL, kAllowConst);
|
|
return BuildClosureCall(call_pos, closure, arguments);
|
|
}
|
|
|
|
|
|
AstNode* Parser::GenerateStaticFieldLookup(const Field& field,
|
|
intptr_t ident_pos) {
|
|
// If the static field has an initializer, initialize the field at compile
|
|
// time, which is only possible if the field is const.
|
|
AstNode* initializing_getter = RunStaticFieldInitializer(field, ident_pos);
|
|
if (initializing_getter != NULL) {
|
|
// The field is not yet initialized and could not be initialized at compile
|
|
// time. The getter will initialize the field.
|
|
return initializing_getter;
|
|
}
|
|
// The field is initialized.
|
|
ASSERT(field.is_static());
|
|
const Class& field_owner = Class::ZoneHandle(Z, field.owner());
|
|
const String& field_name = String::ZoneHandle(Z, field.name());
|
|
const String& getter_name = String::Handle(Z, Field::GetterName(field_name));
|
|
const Function& getter = Function::Handle(Z,
|
|
field_owner.LookupStaticFunction(getter_name));
|
|
// Never load field directly if there is a getter (deterministic AST).
|
|
if (getter.IsNull() || field.is_const()) {
|
|
return new(Z) LoadStaticFieldNode(
|
|
ident_pos, Field::ZoneHandle(Z, field.raw()));
|
|
} else {
|
|
ASSERT(getter.kind() == RawFunction::kImplicitStaticFinalGetter);
|
|
return new(Z) StaticGetterNode(ident_pos,
|
|
NULL, // Receiver.
|
|
false, // is_super_getter.
|
|
field_owner,
|
|
field_name);
|
|
}
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseStaticFieldAccess(const Class& cls,
|
|
const String& field_name,
|
|
intptr_t ident_pos,
|
|
bool consume_cascades) {
|
|
TRACE_PARSER("ParseStaticFieldAccess");
|
|
AstNode* access = NULL;
|
|
const Field& field = Field::ZoneHandle(Z, cls.LookupStaticField(field_name));
|
|
Function& func = Function::ZoneHandle(Z);
|
|
if (field.IsNull()) {
|
|
// No field, check if we have an explicit getter function.
|
|
const String& getter_name =
|
|
String::ZoneHandle(Z, Field::GetterName(field_name));
|
|
const int kNumArguments = 0; // no arguments.
|
|
func = Resolver::ResolveStatic(cls,
|
|
getter_name,
|
|
kNumArguments,
|
|
Object::empty_array());
|
|
if (func.IsNull()) {
|
|
// We might be referring to an implicit closure, check to see if
|
|
// there is a function of the same name.
|
|
func = cls.LookupStaticFunction(field_name);
|
|
if (!func.IsNull()) {
|
|
access = CreateImplicitClosureNode(func, ident_pos, NULL);
|
|
} else {
|
|
// No function to closurize found found.
|
|
// This field access may turn out to be a call to the setter.
|
|
// Create a getter call, which may later be turned into
|
|
// a setter call, or else the backend will generate
|
|
// a throw NoSuchMethodError().
|
|
access = new(Z) StaticGetterNode(ident_pos,
|
|
NULL,
|
|
false,
|
|
Class::ZoneHandle(Z, cls.raw()),
|
|
field_name);
|
|
}
|
|
} else {
|
|
ASSERT(func.kind() != RawFunction::kImplicitStaticFinalGetter);
|
|
access = new(Z) StaticGetterNode(
|
|
ident_pos, NULL, false, Class::ZoneHandle(Z, cls.raw()), field_name);
|
|
}
|
|
} else {
|
|
access = GenerateStaticFieldLookup(field, ident_pos);
|
|
}
|
|
return access;
|
|
}
|
|
|
|
|
|
AstNode* Parser::LoadFieldIfUnresolved(AstNode* node) {
|
|
if (!node->IsPrimaryNode()) {
|
|
return node;
|
|
}
|
|
PrimaryNode* primary = node->AsPrimaryNode();
|
|
if (primary->primary().IsString()) {
|
|
if (primary->IsSuper()) {
|
|
return primary;
|
|
}
|
|
// In a static method, evaluation of an unresolved identifier causes a
|
|
// NoSuchMethodError to be thrown.
|
|
// In an instance method, we convert this into a getter call
|
|
// for a field (which may be defined in a subclass.)
|
|
String& name = String::CheckedZoneHandle(primary->primary().raw());
|
|
if (current_function().is_static() ||
|
|
current_function().IsInFactoryScope()) {
|
|
StaticGetterNode* getter = new(Z) StaticGetterNode(
|
|
primary->token_pos(),
|
|
NULL, // No receiver.
|
|
false, // Not a super getter.
|
|
Class::ZoneHandle(Z, current_class().raw()),
|
|
name);
|
|
getter->set_is_deferred(primary->is_deferred_reference());
|
|
return getter;
|
|
} else {
|
|
AstNode* receiver = LoadReceiver(primary->token_pos());
|
|
return CallGetter(node->token_pos(), receiver, name);
|
|
}
|
|
}
|
|
return primary;
|
|
}
|
|
|
|
|
|
AstNode* Parser::LoadClosure(PrimaryNode* primary) {
|
|
ASSERT(primary->primary().IsFunction());
|
|
const Function& func =
|
|
Function::CheckedZoneHandle(primary->primary().raw());
|
|
const String& funcname = String::ZoneHandle(Z, func.name());
|
|
if (func.is_static()) {
|
|
// Static function access.
|
|
ClosureNode* closure =
|
|
CreateImplicitClosureNode(func, primary->token_pos(), NULL);
|
|
closure->set_is_deferred(primary->is_deferred_reference());
|
|
return closure;
|
|
} else {
|
|
// Instance function access.
|
|
if (current_function().is_static() ||
|
|
current_function().IsInFactoryScope()) {
|
|
ReportError(primary->token_pos(),
|
|
"cannot access instance method '%s' from static method",
|
|
funcname.ToCString());
|
|
}
|
|
AstNode* receiver = LoadReceiver(primary->token_pos());
|
|
return CallGetter(primary->token_pos(), receiver, funcname);
|
|
}
|
|
UNREACHABLE();
|
|
return NULL;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseSelectors(AstNode* primary, bool is_cascade) {
|
|
AstNode* left = primary;
|
|
while (true) {
|
|
AstNode* selector = NULL;
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
if (left->IsPrimaryNode()) {
|
|
PrimaryNode* primary_node = left->AsPrimaryNode();
|
|
const intptr_t primary_pos = primary_node->token_pos();
|
|
if (primary_node->primary().IsFunction()) {
|
|
left = LoadClosure(primary_node);
|
|
} else if (primary_node->primary().IsTypeParameter()) {
|
|
if (current_function().is_static()) {
|
|
const String& name = String::ZoneHandle(Z,
|
|
TypeParameter::Cast(primary_node->primary()).name());
|
|
ReportError(primary_pos,
|
|
"cannot access type parameter '%s' "
|
|
"from static function",
|
|
name.ToCString());
|
|
}
|
|
if (current_block_->scope->function_level() > 0) {
|
|
// Make sure that the instantiator is captured.
|
|
CaptureInstantiator();
|
|
}
|
|
TypeParameter& type_parameter = TypeParameter::ZoneHandle(Z);
|
|
type_parameter ^= ClassFinalizer::FinalizeType(
|
|
current_class(),
|
|
TypeParameter::Cast(primary_node->primary()),
|
|
ClassFinalizer::kCanonicalize);
|
|
ASSERT(!type_parameter.IsMalformed());
|
|
left = new(Z) TypeNode(primary->token_pos(), type_parameter);
|
|
} else {
|
|
// Super field access handled in ParseSuperFieldAccess(),
|
|
// super calls handled in ParseSuperCall().
|
|
ASSERT(!primary_node->IsSuper());
|
|
left = LoadFieldIfUnresolved(left);
|
|
}
|
|
}
|
|
const intptr_t ident_pos = TokenPos();
|
|
String* ident = ExpectIdentifier("identifier expected");
|
|
if (CurrentToken() == Token::kLPAREN) {
|
|
// Identifier followed by a opening paren: method call.
|
|
if (left->IsPrimaryNode() &&
|
|
left->AsPrimaryNode()->primary().IsClass()) {
|
|
// Static method call prefixed with class name.
|
|
const Class& cls = Class::Cast(left->AsPrimaryNode()->primary());
|
|
selector = ParseStaticCall(cls, *ident, ident_pos);
|
|
} else {
|
|
selector = ParseInstanceCall(left, *ident);
|
|
}
|
|
} else {
|
|
// Field access.
|
|
Class& cls = Class::Handle(Z);
|
|
if (left->IsPrimaryNode()) {
|
|
PrimaryNode* primary_node = left->AsPrimaryNode();
|
|
if (primary_node->primary().IsClass()) {
|
|
// If the primary node referred to a class we are loading a
|
|
// qualified static field.
|
|
cls ^= primary_node->primary().raw();
|
|
}
|
|
}
|
|
if (cls.IsNull()) {
|
|
// Instance field access.
|
|
selector = CallGetter(ident_pos, left, *ident);
|
|
} else {
|
|
// Static field access.
|
|
selector =
|
|
ParseStaticFieldAccess(cls, *ident, ident_pos, !is_cascade);
|
|
}
|
|
}
|
|
} else if (CurrentToken() == Token::kLBRACK) {
|
|
// Super index operator handled in ParseSuperOperator().
|
|
ASSERT(!left->IsPrimaryNode() || !left->AsPrimaryNode()->IsSuper());
|
|
|
|
const intptr_t bracket_pos = TokenPos();
|
|
ConsumeToken();
|
|
left = LoadFieldIfUnresolved(left);
|
|
const bool saved_mode = SetAllowFunctionLiterals(true);
|
|
AstNode* index = ParseExpr(kAllowConst, kConsumeCascades);
|
|
SetAllowFunctionLiterals(saved_mode);
|
|
ExpectToken(Token::kRBRACK);
|
|
AstNode* array = left;
|
|
if (left->IsPrimaryNode()) {
|
|
PrimaryNode* primary_node = left->AsPrimaryNode();
|
|
const intptr_t primary_pos = primary_node->token_pos();
|
|
if (primary_node->primary().IsFunction()) {
|
|
array = LoadClosure(primary_node);
|
|
} else if (primary_node->primary().IsClass()) {
|
|
const Class& type_class = Class::Cast(primary_node->primary());
|
|
AbstractType& type = Type::ZoneHandle(Z,
|
|
Type::New(type_class, TypeArguments::Handle(Z),
|
|
primary_pos, Heap::kOld));
|
|
type ^= ClassFinalizer::FinalizeType(
|
|
current_class(), type, ClassFinalizer::kCanonicalize);
|
|
// Type may be malbounded, but not malformed.
|
|
ASSERT(!type.IsMalformed());
|
|
array = new(Z) TypeNode(primary_pos, type);
|
|
} else if (primary_node->primary().IsTypeParameter()) {
|
|
if (current_function().is_static()) {
|
|
const String& name = String::ZoneHandle(Z,
|
|
TypeParameter::Cast(primary_node->primary()).name());
|
|
ReportError(primary_pos,
|
|
"cannot access type parameter '%s' "
|
|
"from static function",
|
|
name.ToCString());
|
|
}
|
|
if (current_block_->scope->function_level() > 0) {
|
|
// Make sure that the instantiator is captured.
|
|
CaptureInstantiator();
|
|
}
|
|
TypeParameter& type_parameter = TypeParameter::ZoneHandle(Z);
|
|
type_parameter ^= ClassFinalizer::FinalizeType(
|
|
current_class(),
|
|
TypeParameter::Cast(primary_node->primary()),
|
|
ClassFinalizer::kCanonicalize);
|
|
ASSERT(!type_parameter.IsMalformed());
|
|
array = new(Z) TypeNode(primary_pos, type_parameter);
|
|
} else {
|
|
UNREACHABLE(); // Internal parser error.
|
|
}
|
|
}
|
|
selector = new(Z) LoadIndexedNode(
|
|
bracket_pos, array, index, Class::ZoneHandle(Z));
|
|
} else if (CurrentToken() == Token::kLPAREN) {
|
|
if (left->IsPrimaryNode()) {
|
|
PrimaryNode* primary_node = left->AsPrimaryNode();
|
|
const intptr_t primary_pos = primary_node->token_pos();
|
|
if (primary_node->primary().IsFunction()) {
|
|
const Function& func = Function::Cast(primary_node->primary());
|
|
const String& func_name = String::ZoneHandle(Z, func.name());
|
|
if (func.is_static()) {
|
|
// Parse static function call.
|
|
Class& cls = Class::Handle(Z, func.Owner());
|
|
selector = ParseStaticCall(cls, func_name, primary_pos);
|
|
} else {
|
|
// Dynamic function call on implicit "this" parameter.
|
|
if (current_function().is_static()) {
|
|
ReportError(primary_pos,
|
|
"cannot access instance method '%s' "
|
|
"from static function",
|
|
func_name.ToCString());
|
|
}
|
|
selector = ParseInstanceCall(LoadReceiver(primary_pos), func_name);
|
|
}
|
|
} else if (primary_node->primary().IsString()) {
|
|
// Primary is an unresolved name.
|
|
if (primary_node->IsSuper()) {
|
|
ReportError(primary_pos, "illegal use of super");
|
|
}
|
|
String& name = String::CheckedZoneHandle(
|
|
primary_node->primary().raw());
|
|
if (current_function().is_static()) {
|
|
selector = ThrowNoSuchMethodError(primary_pos,
|
|
current_class(),
|
|
name,
|
|
NULL, // No arguments.
|
|
InvocationMirror::kStatic,
|
|
InvocationMirror::kMethod,
|
|
NULL); // No existing function.
|
|
} else {
|
|
// Treat as call to unresolved (instance) method.
|
|
selector = ParseInstanceCall(LoadReceiver(primary_pos), name);
|
|
}
|
|
} else if (primary_node->primary().IsTypeParameter()) {
|
|
const String& name = String::ZoneHandle(Z,
|
|
TypeParameter::Cast(primary_node->primary()).name());
|
|
if (current_function().is_static()) {
|
|
// Treat as this.T(), because T is in scope.
|
|
ReportError(primary_pos,
|
|
"cannot access type parameter '%s' "
|
|
"from static function",
|
|
name.ToCString());
|
|
} else {
|
|
// Treat as call to unresolved (instance) method.
|
|
selector = ParseInstanceCall(LoadReceiver(primary_pos), name);
|
|
}
|
|
} else if (primary_node->primary().IsClass()) {
|
|
const Class& type_class = Class::Cast(primary_node->primary());
|
|
AbstractType& type = Type::ZoneHandle(Z, Type::New(
|
|
type_class, TypeArguments::Handle(Z), primary_pos));
|
|
type ^= ClassFinalizer::FinalizeType(
|
|
current_class(), type, ClassFinalizer::kCanonicalize);
|
|
// Type may be malbounded, but not malformed.
|
|
ASSERT(!type.IsMalformed());
|
|
selector = new(Z) TypeNode(primary_pos, type);
|
|
} else {
|
|
UNREACHABLE(); // Internal parser error.
|
|
}
|
|
} else {
|
|
// Left is not a primary node; this must be a closure call.
|
|
AstNode* closure = left;
|
|
selector = ParseClosureCall(closure);
|
|
}
|
|
} else {
|
|
// No (more) selectors to parse.
|
|
left = LoadFieldIfUnresolved(left);
|
|
if (left->IsPrimaryNode()) {
|
|
PrimaryNode* primary_node = left->AsPrimaryNode();
|
|
const intptr_t primary_pos = primary->token_pos();
|
|
if (primary_node->primary().IsFunction()) {
|
|
// Treat as implicit closure.
|
|
left = LoadClosure(primary_node);
|
|
} else if (primary_node->primary().IsClass()) {
|
|
const Class& type_class = Class::Cast(primary_node->primary());
|
|
AbstractType& type = Type::ZoneHandle(Z, Type::New(
|
|
type_class, TypeArguments::Handle(Z), primary_pos));
|
|
type = ClassFinalizer::FinalizeType(
|
|
current_class(), type, ClassFinalizer::kCanonicalize);
|
|
// Type may be malbounded, but not malformed.
|
|
ASSERT(!type.IsMalformed());
|
|
left = new(Z) TypeNode(primary_pos, type);
|
|
} else if (primary_node->primary().IsTypeParameter()) {
|
|
if (current_function().is_static()) {
|
|
const String& name = String::ZoneHandle(Z,
|
|
TypeParameter::Cast(primary_node->primary()).name());
|
|
ReportError(primary_pos,
|
|
"cannot access type parameter '%s' "
|
|
"from static function",
|
|
name.ToCString());
|
|
}
|
|
if (current_block_->scope->function_level() > 0) {
|
|
// Make sure that the instantiator is captured.
|
|
CaptureInstantiator();
|
|
}
|
|
TypeParameter& type_parameter = TypeParameter::ZoneHandle(Z);
|
|
type_parameter ^= ClassFinalizer::FinalizeType(
|
|
current_class(),
|
|
TypeParameter::Cast(primary_node->primary()),
|
|
ClassFinalizer::kCanonicalize);
|
|
ASSERT(!type_parameter.IsMalformed());
|
|
left = new(Z) TypeNode(primary_pos, type_parameter);
|
|
} else if (primary_node->IsSuper()) {
|
|
// Return "super" to handle unary super operator calls,
|
|
// or to report illegal use of "super" otherwise.
|
|
left = primary_node;
|
|
} else {
|
|
UNREACHABLE(); // Internal parser error.
|
|
}
|
|
}
|
|
// Done parsing selectors.
|
|
return left;
|
|
}
|
|
ASSERT(selector != NULL);
|
|
left = selector;
|
|
}
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParsePostfixExpr() {
|
|
TRACE_PARSER("ParsePostfixExpr");
|
|
String* expr_ident =
|
|
Token::IsIdentifier(CurrentToken()) ? CurrentLiteral() : NULL;
|
|
const intptr_t expr_pos = TokenPos();
|
|
AstNode* expr = ParsePrimary();
|
|
expr = ParseSelectors(expr, false);
|
|
if (IsIncrementOperator(CurrentToken())) {
|
|
TRACE_PARSER("IncrementOperator");
|
|
if (!IsLegalAssignableSyntax(expr, TokenPos())) {
|
|
ReportError(expr_pos, "expression is not assignable");
|
|
}
|
|
Token::Kind incr_op = CurrentToken();
|
|
ConsumeToken();
|
|
// Not prefix.
|
|
LetNode* let_expr = PrepareCompoundAssignmentNodes(&expr);
|
|
LocalVariable* temp = let_expr->AddInitializer(expr);
|
|
Token::Kind binary_op =
|
|
(incr_op == Token::kINCR) ? Token::kADD : Token::kSUB;
|
|
BinaryOpNode* add = new(Z) BinaryOpNode(
|
|
expr_pos,
|
|
binary_op,
|
|
new(Z) LoadLocalNode(expr_pos, temp),
|
|
new(Z) LiteralNode(expr_pos, Smi::ZoneHandle(Z, Smi::New(1))));
|
|
AstNode* store = CreateAssignmentNode(expr, add, expr_ident, expr_pos);
|
|
ASSERT(store != NULL);
|
|
// The result is a pair of the (side effects of the) store followed by
|
|
// the (value of the) initial value temp variable load.
|
|
let_expr->AddNode(store);
|
|
let_expr->AddNode(new(Z) LoadLocalNode(expr_pos, temp));
|
|
return let_expr;
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
|
|
// Resolve the given type and its type arguments from the given scope class
|
|
// according to the given type finalization mode.
|
|
// If the given scope class is null, use the current library, but do not try to
|
|
// resolve type parameters.
|
|
// Not all involved type classes may get resolved yet, but at least the type
|
|
// parameters of the given class will get resolved, thereby relieving the class
|
|
// finalizer from resolving type parameters out of context.
|
|
void Parser::ResolveTypeFromClass(const Class& scope_class,
|
|
ClassFinalizer::FinalizationKind finalization,
|
|
AbstractType* type) {
|
|
ASSERT(finalization >= ClassFinalizer::kResolveTypeParameters);
|
|
ASSERT(type != NULL);
|
|
if (type->IsResolved()) {
|
|
return;
|
|
}
|
|
// Resolve class.
|
|
if (!type->HasResolvedTypeClass()) {
|
|
const UnresolvedClass& unresolved_class =
|
|
UnresolvedClass::Handle(Z, type->unresolved_class());
|
|
const String& unresolved_class_name =
|
|
String::Handle(Z, unresolved_class.ident());
|
|
Class& resolved_type_class = Class::Handle(Z);
|
|
if (unresolved_class.library_prefix() == LibraryPrefix::null()) {
|
|
if (!scope_class.IsNull()) {
|
|
// First check if the type is a type parameter of the given scope class.
|
|
const TypeParameter& type_parameter = TypeParameter::Handle(Z,
|
|
scope_class.LookupTypeParameter(unresolved_class_name));
|
|
if (!type_parameter.IsNull()) {
|
|
// A type parameter is considered to be a malformed type when
|
|
// referenced by a static member.
|
|
if (ParsingStaticMember()) {
|
|
ASSERT(scope_class.raw() == current_class().raw());
|
|
*type = ClassFinalizer::NewFinalizedMalformedType(
|
|
Error::Handle(Z), // No previous error.
|
|
script_,
|
|
type->token_pos(),
|
|
"type parameter '%s' cannot be referenced "
|
|
"from static member",
|
|
String::Handle(Z, type_parameter.name()).ToCString());
|
|
return;
|
|
}
|
|
// A type parameter cannot be parameterized, so make the type
|
|
// malformed if type arguments have previously been parsed.
|
|
if (!TypeArguments::Handle(Z, type->arguments()).IsNull()) {
|
|
*type = ClassFinalizer::NewFinalizedMalformedType(
|
|
Error::Handle(Z), // No previous error.
|
|
script_,
|
|
type_parameter.token_pos(),
|
|
"type parameter '%s' cannot be parameterized",
|
|
String::Handle(Z, type_parameter.name()).ToCString());
|
|
return;
|
|
}
|
|
*type = type_parameter.raw();
|
|
return;
|
|
}
|
|
}
|
|
// The referenced class may not have been parsed yet. It would be wrong
|
|
// to resolve it too early to an imported class of the same name.
|
|
if (finalization > ClassFinalizer::kResolveTypeParameters) {
|
|
// Resolve classname in the scope of the current library.
|
|
resolved_type_class = ResolveClassInCurrentLibraryScope(
|
|
unresolved_class_name);
|
|
}
|
|
} else {
|
|
LibraryPrefix& lib_prefix =
|
|
LibraryPrefix::Handle(Z, unresolved_class.library_prefix());
|
|
// Resolve class name in the scope of the library prefix.
|
|
resolved_type_class =
|
|
ResolveClassInPrefixScope(lib_prefix, unresolved_class_name);
|
|
}
|
|
// At this point, we can only have a parameterized_type.
|
|
const Type& parameterized_type = Type::Cast(*type);
|
|
if (!resolved_type_class.IsNull()) {
|
|
// Replace unresolved class with resolved type class.
|
|
parameterized_type.set_type_class(resolved_type_class);
|
|
} else if (finalization >= ClassFinalizer::kCanonicalize) {
|
|
ClassFinalizer::FinalizeMalformedType(
|
|
Error::Handle(Z), // No previous error.
|
|
script_,
|
|
parameterized_type,
|
|
"type '%s' is not loaded",
|
|
String::Handle(Z, parameterized_type.UserVisibleName()).ToCString());
|
|
return;
|
|
}
|
|
}
|
|
// Resolve type arguments, if any.
|
|
const TypeArguments& arguments = TypeArguments::Handle(Z, type->arguments());
|
|
TypeArguments::Handle(Z, type->arguments());
|
|
if (!arguments.IsNull()) {
|
|
const intptr_t num_arguments = arguments.Length();
|
|
for (intptr_t i = 0; i < num_arguments; i++) {
|
|
AbstractType& type_argument = AbstractType::Handle(Z,
|
|
arguments.TypeAt(i));
|
|
ResolveTypeFromClass(scope_class, finalization, &type_argument);
|
|
arguments.SetTypeAt(i, type_argument);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
LocalVariable* Parser::LookupLocalScope(const String& ident) {
|
|
if (current_block_ == NULL) {
|
|
return NULL;
|
|
}
|
|
// A found name is treated as accessed and possibly marked as captured.
|
|
const bool kTestOnly = false;
|
|
return current_block_->scope->LookupVariable(ident, kTestOnly);
|
|
}
|
|
|
|
|
|
void Parser::CheckInstanceFieldAccess(intptr_t field_pos,
|
|
const String& field_name) {
|
|
// Fields are not accessible from a static function, except from a
|
|
// constructor, which is considered as non-static by the compiler.
|
|
if (current_function().is_static()) {
|
|
ReportError(field_pos,
|
|
"cannot access instance field '%s' from a static function",
|
|
field_name.ToCString());
|
|
}
|
|
}
|
|
|
|
|
|
bool Parser::ParsingStaticMember() const {
|
|
if (is_top_level_) {
|
|
return (current_member_ != NULL) &&
|
|
current_member_->has_static && !current_member_->has_factory;
|
|
}
|
|
ASSERT(!current_function().IsNull());
|
|
return
|
|
current_function().is_static() && !current_function().IsInFactoryScope();
|
|
}
|
|
|
|
|
|
const AbstractType* Parser::ReceiverType(const Class& cls) {
|
|
ASSERT(!cls.IsNull());
|
|
const TypeArguments& type_arguments = TypeArguments::Handle(
|
|
Z,
|
|
(cls.NumTypeParameters() > 0) ?
|
|
cls.type_parameters() : TypeArguments::null());
|
|
AbstractType& type = AbstractType::ZoneHandle(
|
|
Z, Type::New(cls, type_arguments, cls.token_pos()));
|
|
if (cls.is_type_finalized()) {
|
|
type ^= ClassFinalizer::FinalizeType(
|
|
cls, type, ClassFinalizer::kCanonicalizeWellFormed);
|
|
// Note that the receiver type may now be a malbounded type.
|
|
}
|
|
return &type;
|
|
}
|
|
|
|
|
|
bool Parser::IsInstantiatorRequired() const {
|
|
ASSERT(!current_function().IsNull());
|
|
if (current_function().is_static() &&
|
|
!current_function().IsInFactoryScope()) {
|
|
return false;
|
|
}
|
|
return current_class().NumTypeParameters() > 0;
|
|
}
|
|
|
|
|
|
RawInstance* Parser::TryCanonicalize(const Instance& instance,
|
|
intptr_t token_pos) {
|
|
if (instance.IsNull()) {
|
|
return instance.raw();
|
|
}
|
|
const char* error_str = NULL;
|
|
Instance& result =
|
|
Instance::Handle(Z, instance.CheckAndCanonicalize(&error_str));
|
|
if (result.IsNull()) {
|
|
ReportError(token_pos, "Invalid const object %s", error_str);
|
|
}
|
|
return result.raw();
|
|
}
|
|
|
|
|
|
// If the field is already initialized, return no ast (NULL).
|
|
// Otherwise, if the field is constant, initialize the field and return no ast.
|
|
// If the field is not initialized and not const, return the ast for the getter.
|
|
AstNode* Parser::RunStaticFieldInitializer(const Field& field,
|
|
intptr_t field_ref_pos) {
|
|
ASSERT(field.is_static());
|
|
const Class& field_owner = Class::ZoneHandle(Z, field.owner());
|
|
const String& field_name = String::ZoneHandle(Z, field.name());
|
|
const String& getter_name = String::Handle(Z, Field::GetterName(field_name));
|
|
const Function& getter = Function::Handle(Z,
|
|
field_owner.LookupStaticFunction(getter_name));
|
|
const Instance& value = Instance::Handle(Z, field.value());
|
|
if (value.raw() == Object::transition_sentinel().raw()) {
|
|
if (field.is_const()) {
|
|
ReportError("circular dependency while initializing static field '%s'",
|
|
field_name.ToCString());
|
|
} else {
|
|
// The implicit static getter will throw the exception if necessary.
|
|
return new(Z) StaticGetterNode(
|
|
field_ref_pos, NULL, false, field_owner, field_name);
|
|
}
|
|
} else if (value.raw() == Object::sentinel().raw()) {
|
|
// This field has not been referenced yet and thus the value has
|
|
// not been evaluated. If the field is const, call the static getter method
|
|
// to evaluate the expression and canonicalize the value.
|
|
if (field.is_const()) {
|
|
field.set_value(Object::transition_sentinel());
|
|
const int kNumArguments = 0; // no arguments.
|
|
const Function& func = Function::Handle(Z,
|
|
Resolver::ResolveStatic(field_owner,
|
|
getter_name,
|
|
kNumArguments,
|
|
Object::empty_array()));
|
|
ASSERT(!func.IsNull());
|
|
ASSERT(func.kind() == RawFunction::kImplicitStaticFinalGetter);
|
|
Object& const_value = Object::Handle(Z);
|
|
{
|
|
PAUSETIMERSCOPE(I, time_compilation);
|
|
const_value = DartEntry::InvokeFunction(func, Object::empty_array());
|
|
}
|
|
if (const_value.IsError()) {
|
|
const Error& error = Error::Cast(const_value);
|
|
if (error.IsUnhandledException()) {
|
|
// An exception may not occur in every parse attempt, i.e., the
|
|
// generated AST is not deterministic. Therefore mark the function as
|
|
// not optimizable.
|
|
current_function().SetIsOptimizable(false);
|
|
field.set_value(Object::null_instance());
|
|
// It is a compile-time error if evaluation of a compile-time constant
|
|
// would raise an exception.
|
|
const String& field_name = String::Handle(Z, field.name());
|
|
ReportErrors(error,
|
|
script_, field_ref_pos,
|
|
"error initializing const field '%s'",
|
|
field_name.ToCString());
|
|
} else {
|
|
ReportError(error);
|
|
}
|
|
UNREACHABLE();
|
|
}
|
|
ASSERT(const_value.IsNull() || const_value.IsInstance());
|
|
Instance& instance = Instance::Handle(Z);
|
|
instance ^= const_value.raw();
|
|
instance = TryCanonicalize(instance, field_ref_pos);
|
|
field.set_value(instance);
|
|
return NULL; // Constant
|
|
} else {
|
|
return new(Z) StaticGetterNode(
|
|
field_ref_pos, NULL, false, field_owner, field_name);
|
|
}
|
|
}
|
|
if (getter.IsNull() ||
|
|
(getter.kind() == RawFunction::kImplicitStaticFinalGetter)) {
|
|
return NULL;
|
|
}
|
|
ASSERT(getter.kind() == RawFunction::kImplicitGetter);
|
|
return new(Z) StaticGetterNode(
|
|
field_ref_pos, NULL, false, field_owner, field_name);
|
|
}
|
|
|
|
|
|
RawObject* Parser::EvaluateConstConstructorCall(
|
|
const Class& type_class,
|
|
const TypeArguments& type_arguments,
|
|
const Function& constructor,
|
|
ArgumentListNode* arguments) {
|
|
// Factories have one extra argument: the type arguments.
|
|
// Constructors have 2 extra arguments: rcvr and construction phase.
|
|
const int kNumExtraArgs = constructor.IsFactory() ? 1 : 2;
|
|
const int num_arguments = arguments->length() + kNumExtraArgs;
|
|
const Array& arg_values = Array::Handle(Z, Array::New(num_arguments));
|
|
Instance& instance = Instance::Handle(Z);
|
|
if (!constructor.IsFactory()) {
|
|
instance = Instance::New(type_class, Heap::kOld);
|
|
if (!type_arguments.IsNull()) {
|
|
if (!type_arguments.IsInstantiated()) {
|
|
ReportError("type must be constant in const constructor");
|
|
}
|
|
instance.SetTypeArguments(
|
|
TypeArguments::Handle(Z, type_arguments.Canonicalize()));
|
|
}
|
|
arg_values.SetAt(0, instance);
|
|
arg_values.SetAt(1, Smi::Handle(Z, Smi::New(Function::kCtorPhaseAll)));
|
|
} else {
|
|
// Prepend type_arguments to list of arguments to factory.
|
|
ASSERT(type_arguments.IsZoneHandle());
|
|
arg_values.SetAt(0, type_arguments);
|
|
}
|
|
for (int i = 0; i < arguments->length(); i++) {
|
|
AstNode* arg = arguments->NodeAt(i);
|
|
// Arguments have been evaluated to a literal value already.
|
|
ASSERT(arg->IsLiteralNode());
|
|
arg_values.SetAt((i + kNumExtraArgs), arg->AsLiteralNode()->literal());
|
|
}
|
|
const Array& args_descriptor = Array::Handle(Z,
|
|
ArgumentsDescriptor::New(num_arguments, arguments->names()));
|
|
Object& result = Object::Handle(Z);
|
|
{
|
|
PAUSETIMERSCOPE(I, time_compilation);
|
|
result = DartEntry::InvokeFunction(
|
|
constructor, arg_values, args_descriptor);
|
|
}
|
|
if (result.IsError()) {
|
|
// An exception may not occur in every parse attempt, i.e., the
|
|
// generated AST is not deterministic. Therefore mark the function as
|
|
// not optimizable.
|
|
current_function().SetIsOptimizable(false);
|
|
if (result.IsUnhandledException()) {
|
|
return result.raw();
|
|
} else {
|
|
I->long_jump_base()->Jump(1, Error::Cast(result));
|
|
UNREACHABLE();
|
|
return Object::null();
|
|
}
|
|
} else {
|
|
if (constructor.IsFactory()) {
|
|
// The factory method returns the allocated object.
|
|
instance ^= result.raw();
|
|
}
|
|
return TryCanonicalize(instance, TokenPos());
|
|
}
|
|
}
|
|
|
|
|
|
// Do a lookup for the identifier in the block scope and the class scope
|
|
// return true if the identifier is found, false otherwise.
|
|
// If node is non NULL return an AST node corresponding to the identifier.
|
|
bool Parser::ResolveIdentInLocalScope(intptr_t ident_pos,
|
|
const String &ident,
|
|
AstNode** node) {
|
|
TRACE_PARSER("ResolveIdentInLocalScope");
|
|
// First try to find the identifier in the nested local scopes.
|
|
LocalVariable* local = LookupLocalScope(ident);
|
|
if (current_block_ != NULL) {
|
|
current_block_->scope->AddReferencedName(ident_pos, ident);
|
|
}
|
|
if (local != NULL) {
|
|
if (node != NULL) {
|
|
*node = new(Z) LoadLocalNode(ident_pos, local);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Try to find the identifier in the class scope of the current class.
|
|
// If the current class is the result of a mixin application, we must
|
|
// use the class scope of the class from which the function originates.
|
|
Class& cls = Class::Handle(Z);
|
|
if (!current_class().IsMixinApplication()) {
|
|
cls = current_class().raw();
|
|
} else {
|
|
cls = parsed_function()->function().origin();
|
|
}
|
|
Function& func = Function::Handle(Z, Function::null());
|
|
Field& field = Field::Handle(Z, Field::null());
|
|
|
|
// First check if a field exists.
|
|
field = cls.LookupField(ident);
|
|
if (!field.IsNull()) {
|
|
if (node != NULL) {
|
|
if (!field.is_static()) {
|
|
CheckInstanceFieldAccess(ident_pos, ident);
|
|
*node = CallGetter(ident_pos, LoadReceiver(ident_pos), ident);
|
|
} else {
|
|
*node = GenerateStaticFieldLookup(field, ident_pos);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if an instance/static function exists.
|
|
func = cls.LookupFunction(ident);
|
|
if (!func.IsNull() &&
|
|
(func.IsDynamicFunction() ||
|
|
func.IsStaticFunction() ||
|
|
func.is_abstract())) {
|
|
if (node != NULL) {
|
|
*node = new(Z) PrimaryNode(
|
|
ident_pos, Function::ZoneHandle(Z, func.raw()));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Now check if a getter/setter method exists for it in which case
|
|
// it is still a field.
|
|
func = cls.LookupGetterFunction(ident);
|
|
if (!func.IsNull()) {
|
|
if (func.IsDynamicFunction() || func.is_abstract()) {
|
|
if (node != NULL) {
|
|
CheckInstanceFieldAccess(ident_pos, ident);
|
|
ASSERT(AbstractType::Handle(Z, func.result_type()).IsResolved());
|
|
*node = CallGetter(ident_pos, LoadReceiver(ident_pos), ident);
|
|
}
|
|
return true;
|
|
} else if (func.IsStaticFunction()) {
|
|
if (node != NULL) {
|
|
ASSERT(AbstractType::Handle(Z, func.result_type()).IsResolved());
|
|
// The static getter may later be changed into a dynamically
|
|
// resolved instance setter if no static setter can
|
|
// be found.
|
|
AstNode* receiver = NULL;
|
|
const bool kTestOnly = true;
|
|
if (!current_function().is_static() &&
|
|
(LookupReceiver(current_block_->scope, kTestOnly) != NULL)) {
|
|
receiver = LoadReceiver(ident_pos);
|
|
}
|
|
*node = new(Z) StaticGetterNode(ident_pos,
|
|
receiver,
|
|
false,
|
|
Class::ZoneHandle(Z, cls.raw()),
|
|
ident);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
func = cls.LookupSetterFunction(ident);
|
|
if (!func.IsNull()) {
|
|
if (func.IsDynamicFunction() || func.is_abstract()) {
|
|
if (node != NULL) {
|
|
// We create a getter node even though a getter doesn't exist as
|
|
// it could be followed by an assignment which will convert it to
|
|
// a setter node. If there is no assignment we will get an error
|
|
// when we try to invoke the getter.
|
|
CheckInstanceFieldAccess(ident_pos, ident);
|
|
ASSERT(AbstractType::Handle(Z, func.result_type()).IsResolved());
|
|
*node = CallGetter(ident_pos, LoadReceiver(ident_pos), ident);
|
|
}
|
|
return true;
|
|
} else if (func.IsStaticFunction()) {
|
|
if (node != NULL) {
|
|
// We create a getter node even though a getter doesn't exist as
|
|
// it could be followed by an assignment which will convert it to
|
|
// a setter node. If there is no assignment we will get an error
|
|
// when we try to invoke the getter.
|
|
*node = new(Z) StaticGetterNode(
|
|
ident_pos,
|
|
NULL,
|
|
false,
|
|
Class::ZoneHandle(Z, cls.raw()),
|
|
ident);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Nothing found in scope of current class.
|
|
if (node != NULL) {
|
|
*node = NULL;
|
|
}
|
|
return false; // Not an unqualified identifier.
|
|
}
|
|
|
|
|
|
RawClass* Parser::ResolveClassInCurrentLibraryScope(const String& name) {
|
|
HANDLESCOPE(I);
|
|
const Object& obj = Object::Handle(Z, library_.ResolveName(name));
|
|
if (obj.IsClass()) {
|
|
return Class::Cast(obj).raw();
|
|
}
|
|
return Class::null();
|
|
}
|
|
|
|
|
|
// Resolve an identifier by checking the global scope of the current
|
|
// library. If not found in the current library, then look in the scopes
|
|
// of all libraries that are imported without a library prefix.
|
|
AstNode* Parser::ResolveIdentInCurrentLibraryScope(intptr_t ident_pos,
|
|
const String& ident) {
|
|
TRACE_PARSER("ResolveIdentInCurrentLibraryScope");
|
|
HANDLESCOPE(I);
|
|
const Object& obj = Object::Handle(Z, library_.ResolveName(ident));
|
|
if (obj.IsClass()) {
|
|
const Class& cls = Class::Cast(obj);
|
|
return new(Z) PrimaryNode(ident_pos, Class::ZoneHandle(Z, cls.raw()));
|
|
} else if (obj.IsField()) {
|
|
const Field& field = Field::Cast(obj);
|
|
ASSERT(field.is_static());
|
|
return GenerateStaticFieldLookup(field, ident_pos);
|
|
} else if (obj.IsFunction()) {
|
|
const Function& func = Function::Cast(obj);
|
|
ASSERT(func.is_static());
|
|
if (func.IsGetterFunction() || func.IsSetterFunction()) {
|
|
return new(Z) StaticGetterNode(ident_pos,
|
|
/* receiver */ NULL,
|
|
/* is_super_getter */ false,
|
|
Class::ZoneHandle(Z, func.Owner()),
|
|
ident);
|
|
|
|
} else {
|
|
return new(Z) PrimaryNode(ident_pos, Function::ZoneHandle(Z, func.raw()));
|
|
}
|
|
} else {
|
|
ASSERT(obj.IsNull() || obj.IsLibraryPrefix());
|
|
}
|
|
// Lexically unresolved primary identifiers are referenced by their name.
|
|
return new(Z) PrimaryNode(ident_pos, ident);
|
|
}
|
|
|
|
|
|
RawClass* Parser::ResolveClassInPrefixScope(const LibraryPrefix& prefix,
|
|
const String& name) {
|
|
HANDLESCOPE(I);
|
|
const Object& obj = Object::Handle(Z, prefix.LookupObject(name));
|
|
if (obj.IsClass()) {
|
|
return Class::Cast(obj).raw();
|
|
}
|
|
return Class::null();
|
|
}
|
|
|
|
|
|
// Do a lookup for the identifier in the scope of the specified
|
|
// library prefix. This means trying to resolve it locally in all of the
|
|
// libraries present in the library prefix.
|
|
AstNode* Parser::ResolveIdentInPrefixScope(intptr_t ident_pos,
|
|
const LibraryPrefix& prefix,
|
|
const String& ident) {
|
|
TRACE_PARSER("ResolveIdentInPrefixScope");
|
|
HANDLESCOPE(I);
|
|
if (ident.CharAt(0) == Library::kPrivateIdentifierStart) {
|
|
// Private names are not exported by libraries. The name mangling
|
|
// of private names with a library-specific suffix usually ensures
|
|
// that _x in library A is not found when looked up from library B.
|
|
// In the pathological case where a library includes itself with
|
|
// a prefix, the name mangling would not help in hiding the private
|
|
// name, so we need to explicitly reject private names here.
|
|
return NULL;
|
|
}
|
|
Object& obj = Object::Handle(Z);
|
|
if (prefix.is_loaded()) {
|
|
obj = prefix.LookupObject(ident);
|
|
} else {
|
|
// Remember that this function depends on an import prefix of an
|
|
// unloaded deferred library. Note that parsed_function() can be
|
|
// NULL when parsing expressions outside the scope of a function.
|
|
if (parsed_function() != NULL) {
|
|
parsed_function()->AddDeferredPrefix(prefix);
|
|
}
|
|
}
|
|
const bool is_deferred = prefix.is_deferred_load();
|
|
if (obj.IsNull()) {
|
|
// Unresolved prefixed primary identifier.
|
|
return NULL;
|
|
} else if (obj.IsClass()) {
|
|
const Class& cls = Class::Cast(obj);
|
|
PrimaryNode* primary =
|
|
new(Z) PrimaryNode(ident_pos, Class::ZoneHandle(Z, cls.raw()));
|
|
primary->set_is_deferred(is_deferred);
|
|
return primary;
|
|
} else if (obj.IsField()) {
|
|
const Field& field = Field::Cast(obj);
|
|
ASSERT(field.is_static());
|
|
AstNode* get_field = GenerateStaticFieldLookup(field, ident_pos);
|
|
ASSERT(get_field != NULL);
|
|
ASSERT(get_field->IsLoadStaticFieldNode() ||
|
|
get_field->IsStaticGetterNode());
|
|
if (get_field->IsLoadStaticFieldNode()) {
|
|
get_field->AsLoadStaticFieldNode()->set_is_deferred(is_deferred);
|
|
} else if (get_field->IsStaticGetterNode()) {
|
|
get_field->AsStaticGetterNode()->set_is_deferred(is_deferred);
|
|
}
|
|
return get_field;
|
|
} else if (obj.IsFunction()) {
|
|
const Function& func = Function::Cast(obj);
|
|
ASSERT(func.is_static());
|
|
if (func.IsGetterFunction() || func.IsSetterFunction()) {
|
|
StaticGetterNode* getter = new(Z) StaticGetterNode(
|
|
ident_pos,
|
|
/* receiver */ NULL,
|
|
/* is_super_getter */ false,
|
|
Class::ZoneHandle(Z, func.Owner()),
|
|
ident);
|
|
getter->set_is_deferred(is_deferred);
|
|
return getter;
|
|
} else {
|
|
PrimaryNode* primary = new(Z) PrimaryNode(
|
|
ident_pos, Function::ZoneHandle(Z, func.raw()));
|
|
primary->set_is_deferred(is_deferred);
|
|
return primary;
|
|
}
|
|
}
|
|
// All possible object types are handled above.
|
|
UNREACHABLE();
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// Resolve identifier. Issue an error message if
|
|
// the ident refers to a method and allow_closure_names is false.
|
|
// If the name cannot be resolved, turn it into an instance field access
|
|
// if we're compiling an instance method, or generate
|
|
// throw NoSuchMethodError if we're compiling a static method.
|
|
AstNode* Parser::ResolveIdent(intptr_t ident_pos,
|
|
const String& ident,
|
|
bool allow_closure_names) {
|
|
TRACE_PARSER("ResolveIdent");
|
|
// First try to find the variable in the local scope (block scope or
|
|
// class scope).
|
|
AstNode* resolved = NULL;
|
|
ResolveIdentInLocalScope(ident_pos, ident, &resolved);
|
|
if (resolved == NULL) {
|
|
// Check whether the identifier is a type parameter.
|
|
if (!current_class().IsNull()) {
|
|
TypeParameter& type_parameter = TypeParameter::ZoneHandle(Z,
|
|
current_class().LookupTypeParameter(ident));
|
|
if (!type_parameter.IsNull()) {
|
|
if (current_block_->scope->function_level() > 0) {
|
|
// Make sure that the instantiator is captured.
|
|
CaptureInstantiator();
|
|
}
|
|
type_parameter ^= ClassFinalizer::FinalizeType(
|
|
current_class(), type_parameter, ClassFinalizer::kCanonicalize);
|
|
ASSERT(!type_parameter.IsMalformed());
|
|
return new(Z) TypeNode(ident_pos, type_parameter);
|
|
}
|
|
}
|
|
// Not found in the local scope, and the name is not a type parameter.
|
|
// Try finding the variable in the library scope (current library
|
|
// and all libraries imported by it without a library prefix).
|
|
resolved = ResolveIdentInCurrentLibraryScope(ident_pos, ident);
|
|
}
|
|
if (resolved->IsPrimaryNode()) {
|
|
PrimaryNode* primary = resolved->AsPrimaryNode();
|
|
const intptr_t primary_pos = primary->token_pos();
|
|
if (primary->primary().IsString()) {
|
|
// We got an unresolved name. If we are compiling a static
|
|
// method, evaluation of an unresolved identifier causes a
|
|
// NoSuchMethodError to be thrown. In an instance method, we convert
|
|
// the unresolved name to an instance field access, since a
|
|
// subclass might define a field with this name.
|
|
if (current_function().is_static()) {
|
|
resolved = ThrowNoSuchMethodError(ident_pos,
|
|
current_class(),
|
|
ident,
|
|
NULL, // No arguments.
|
|
InvocationMirror::kStatic,
|
|
InvocationMirror::kField,
|
|
NULL); // No existing function.
|
|
} else {
|
|
// Treat as call to unresolved instance field.
|
|
resolved = CallGetter(ident_pos, LoadReceiver(ident_pos), ident);
|
|
}
|
|
} else if (primary->primary().IsFunction()) {
|
|
if (allow_closure_names) {
|
|
resolved = LoadClosure(primary);
|
|
} else {
|
|
ReportError(ident_pos, "illegal reference to method '%s'",
|
|
ident.ToCString());
|
|
}
|
|
} else if (primary->primary().IsClass()) {
|
|
const Class& type_class = Class::Cast(primary->primary());
|
|
AbstractType& type = Type::ZoneHandle(Z,
|
|
Type::New(type_class, TypeArguments::Handle(Z), primary_pos));
|
|
type ^= ClassFinalizer::FinalizeType(
|
|
current_class(), type, ClassFinalizer::kCanonicalize);
|
|
// Type may be malbounded, but not malformed.
|
|
ASSERT(!type.IsMalformed());
|
|
resolved = new(Z) TypeNode(primary_pos, type);
|
|
}
|
|
}
|
|
return resolved;
|
|
}
|
|
|
|
|
|
// Parses type = [ident "."] ident ["<" type { "," type } ">"], then resolve and
|
|
// finalize it according to the given type finalization mode.
|
|
RawAbstractType* Parser::ParseType(
|
|
ClassFinalizer::FinalizationKind finalization,
|
|
bool allow_deferred_type,
|
|
bool consume_unresolved_prefix) {
|
|
TRACE_PARSER("ParseType");
|
|
CheckToken(Token::kIDENT, "type name expected");
|
|
intptr_t ident_pos = TokenPos();
|
|
LibraryPrefix& prefix = LibraryPrefix::Handle(Z);
|
|
String& type_name = String::Handle(Z);;
|
|
|
|
if (finalization == ClassFinalizer::kIgnore) {
|
|
if (!is_top_level_ && (current_block_ != NULL)) {
|
|
// Add the library prefix or type class name to the list of referenced
|
|
// names of this scope, even if the type is ignored.
|
|
current_block_->scope->AddReferencedName(TokenPos(), *CurrentLiteral());
|
|
}
|
|
SkipQualIdent();
|
|
} else {
|
|
prefix = ParsePrefix();
|
|
type_name = CurrentLiteral()->raw();
|
|
ConsumeToken();
|
|
|
|
// Check whether we have a malformed qualified type name if the caller
|
|
// requests to consume unresolved prefix names:
|
|
// If we didn't see a valid prefix but the identifier is followed by
|
|
// a period and another identifier, consume the qualified identifier
|
|
// and create a malformed type.
|
|
if (consume_unresolved_prefix &&
|
|
prefix.IsNull() &&
|
|
(CurrentToken() == Token::kPERIOD) &&
|
|
(Token::IsIdentifier(LookaheadToken(1)))) {
|
|
if (!is_top_level_ && (current_block_ != NULL)) {
|
|
// Add the unresolved prefix name to the list of referenced
|
|
// names of this scope.
|
|
current_block_->scope->AddReferencedName(TokenPos(), type_name);
|
|
}
|
|
ConsumeToken(); // Period token.
|
|
ASSERT(IsIdentifier());
|
|
String& qualified_name = String::Handle(Z, type_name.raw());
|
|
qualified_name = String::Concat(qualified_name, Symbols::Dot());
|
|
qualified_name = String::Concat(qualified_name, *CurrentLiteral());
|
|
ConsumeToken();
|
|
// The type is malformed. Skip over its type arguments.
|
|
ParseTypeArguments(ClassFinalizer::kIgnore);
|
|
return ClassFinalizer::NewFinalizedMalformedType(
|
|
Error::Handle(Z), // No previous error.
|
|
script_,
|
|
ident_pos,
|
|
"qualified name '%s' does not refer to a type",
|
|
qualified_name.ToCString());
|
|
}
|
|
|
|
// If parsing inside a local scope, check whether the type name
|
|
// is shadowed by a local declaration.
|
|
if (!is_top_level_ &&
|
|
(prefix.IsNull()) &&
|
|
ResolveIdentInLocalScope(ident_pos, type_name, NULL)) {
|
|
// The type is malformed. Skip over its type arguments.
|
|
ParseTypeArguments(ClassFinalizer::kIgnore);
|
|
return ClassFinalizer::NewFinalizedMalformedType(
|
|
Error::Handle(Z), // No previous error.
|
|
script_,
|
|
ident_pos,
|
|
"using '%s' in this context is invalid",
|
|
type_name.ToCString());
|
|
}
|
|
if (!prefix.IsNull() && prefix.is_deferred_load() && !allow_deferred_type) {
|
|
ParseTypeArguments(ClassFinalizer::kIgnore);
|
|
return ClassFinalizer::NewFinalizedMalformedType(
|
|
Error::Handle(Z), // No previous error.
|
|
script_,
|
|
ident_pos,
|
|
"using deferred type '%s.%s' is invalid",
|
|
String::Handle(Z, prefix.name()).ToCString(),
|
|
type_name.ToCString());
|
|
}
|
|
}
|
|
Object& type_class = Object::Handle(Z);
|
|
// Leave type_class as null if type finalization mode is kIgnore.
|
|
if (finalization != ClassFinalizer::kIgnore) {
|
|
type_class = UnresolvedClass::New(prefix, type_name, ident_pos);
|
|
}
|
|
TypeArguments& type_arguments = TypeArguments::Handle(
|
|
Z, ParseTypeArguments(finalization));
|
|
if (finalization == ClassFinalizer::kIgnore) {
|
|
return Type::DynamicType();
|
|
}
|
|
AbstractType& type = AbstractType::Handle(
|
|
Z, Type::New(type_class, type_arguments, ident_pos));
|
|
if (finalization >= ClassFinalizer::kResolveTypeParameters) {
|
|
ResolveTypeFromClass(current_class(), finalization, &type);
|
|
if (finalization >= ClassFinalizer::kCanonicalize) {
|
|
type ^= ClassFinalizer::FinalizeType(current_class(), type, finalization);
|
|
}
|
|
}
|
|
return type.raw();
|
|
}
|
|
|
|
|
|
void Parser::CheckConstructorCallTypeArguments(
|
|
intptr_t pos, const Function& constructor,
|
|
const TypeArguments& type_arguments) {
|
|
if (!type_arguments.IsNull()) {
|
|
const Class& constructor_class = Class::Handle(Z, constructor.Owner());
|
|
ASSERT(!constructor_class.IsNull());
|
|
ASSERT(constructor_class.is_finalized());
|
|
ASSERT(type_arguments.IsCanonical());
|
|
// Do not report the expected vs. actual number of type arguments, because
|
|
// the type argument vector is flattened and raw types are allowed.
|
|
if (type_arguments.Length() != constructor_class.NumTypeArguments()) {
|
|
ReportError(pos, "wrong number of type arguments passed to constructor");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Parse "[" [ expr { "," expr } ["," ] "]".
|
|
// Note: if the list literal is empty and the brackets have no whitespace
|
|
// between them, the scanner recognizes the opening and closing bracket
|
|
// as one token of type Token::kINDEX.
|
|
AstNode* Parser::ParseListLiteral(intptr_t type_pos,
|
|
bool is_const,
|
|
const TypeArguments& type_arguments) {
|
|
TRACE_PARSER("ParseListLiteral");
|
|
ASSERT(type_pos >= 0);
|
|
ASSERT(CurrentToken() == Token::kLBRACK || CurrentToken() == Token::kINDEX);
|
|
const intptr_t literal_pos = TokenPos();
|
|
bool is_empty_literal = CurrentToken() == Token::kINDEX;
|
|
ConsumeToken();
|
|
|
|
AbstractType& element_type = Type::ZoneHandle(Z, Type::DynamicType());
|
|
TypeArguments& list_type_arguments =
|
|
TypeArguments::ZoneHandle(Z, type_arguments.raw());
|
|
// If no type argument vector is provided, leave it as null, which is
|
|
// equivalent to using dynamic as the type argument for the element type.
|
|
if (!list_type_arguments.IsNull()) {
|
|
ASSERT(list_type_arguments.Length() > 0);
|
|
// List literals take a single type argument.
|
|
if (list_type_arguments.Length() == 1) {
|
|
element_type = list_type_arguments.TypeAt(0);
|
|
ASSERT(!element_type.IsMalformed()); // Would be mapped to dynamic.
|
|
ASSERT(!element_type.IsMalbounded()); // No declared bound in List.
|
|
if (element_type.IsDynamicType()) {
|
|
list_type_arguments = TypeArguments::null();
|
|
} else if (is_const && !element_type.IsInstantiated()) {
|
|
ReportError(type_pos,
|
|
"the type argument of a constant list literal cannot "
|
|
"include a type variable");
|
|
}
|
|
} else {
|
|
if (FLAG_error_on_bad_type) {
|
|
ReportError(type_pos,
|
|
"a list literal takes one type argument specifying "
|
|
"the element type");
|
|
}
|
|
// Ignore type arguments.
|
|
list_type_arguments = TypeArguments::null();
|
|
}
|
|
}
|
|
ASSERT(list_type_arguments.IsNull() || (list_type_arguments.Length() == 1));
|
|
const Class& array_class = Class::Handle(Z, I->object_store()->array_class());
|
|
Type& type = Type::ZoneHandle(Z,
|
|
Type::New(array_class, list_type_arguments, type_pos));
|
|
type ^= ClassFinalizer::FinalizeType(
|
|
current_class(), type, ClassFinalizer::kCanonicalize);
|
|
GrowableArray<AstNode*> element_list;
|
|
// Parse the list elements. Note: there may be an optional extra
|
|
// comma after the last element.
|
|
if (!is_empty_literal) {
|
|
const bool saved_mode = SetAllowFunctionLiterals(true);
|
|
while (CurrentToken() != Token::kRBRACK) {
|
|
const intptr_t element_pos = TokenPos();
|
|
AstNode* element = ParseExpr(is_const, kConsumeCascades);
|
|
if (FLAG_enable_type_checks &&
|
|
!is_const &&
|
|
!element_type.IsDynamicType()) {
|
|
element = new(Z) AssignableNode(element_pos,
|
|
element,
|
|
element_type,
|
|
Symbols::ListLiteralElement());
|
|
}
|
|
element_list.Add(element);
|
|
if (CurrentToken() == Token::kCOMMA) {
|
|
ConsumeToken();
|
|
} else if (CurrentToken() != Token::kRBRACK) {
|
|
ReportError("comma or ']' expected");
|
|
}
|
|
}
|
|
ExpectToken(Token::kRBRACK);
|
|
SetAllowFunctionLiterals(saved_mode);
|
|
}
|
|
|
|
if (is_const) {
|
|
// Allocate and initialize the const list at compile time.
|
|
Array& const_list =
|
|
Array::ZoneHandle(Z, Array::New(element_list.length(), Heap::kOld));
|
|
const_list.SetTypeArguments(
|
|
TypeArguments::Handle(Z, list_type_arguments.Canonicalize()));
|
|
Error& malformed_error = Error::Handle(Z);
|
|
for (int i = 0; i < element_list.length(); i++) {
|
|
AstNode* elem = element_list[i];
|
|
// Arguments have been evaluated to a literal value already.
|
|
ASSERT(elem->IsLiteralNode());
|
|
ASSERT(!is_top_level_); // We cannot check unresolved types.
|
|
if (FLAG_enable_type_checks &&
|
|
!element_type.IsDynamicType() &&
|
|
(!elem->AsLiteralNode()->literal().IsNull() &&
|
|
!elem->AsLiteralNode()->literal().IsInstanceOf(
|
|
element_type,
|
|
TypeArguments::Handle(Z),
|
|
&malformed_error))) {
|
|
// If the failure is due to a malformed type error, display it instead.
|
|
if (!malformed_error.IsNull()) {
|
|
ReportError(malformed_error);
|
|
} else {
|
|
ReportError(elem->AsLiteralNode()->token_pos(),
|
|
"list literal element at index %d must be "
|
|
"a constant of type '%s'",
|
|
i,
|
|
String::Handle(Z,
|
|
element_type.UserVisibleName()).ToCString());
|
|
}
|
|
}
|
|
const_list.SetAt(i, elem->AsLiteralNode()->literal());
|
|
}
|
|
const_list.MakeImmutable();
|
|
const_list ^= TryCanonicalize(const_list, literal_pos);
|
|
return new(Z) LiteralNode(literal_pos, const_list);
|
|
} else {
|
|
// Factory call at runtime.
|
|
const Class& factory_class =
|
|
Class::Handle(Z, Library::LookupCoreClass(Symbols::List()));
|
|
ASSERT(!factory_class.IsNull());
|
|
const Function& factory_method = Function::ZoneHandle(Z,
|
|
factory_class.LookupFactory(
|
|
Library::PrivateCoreLibName(Symbols::ListLiteralFactory())));
|
|
ASSERT(!factory_method.IsNull());
|
|
if (!list_type_arguments.IsNull() &&
|
|
!list_type_arguments.IsInstantiated() &&
|
|
(current_block_->scope->function_level() > 0)) {
|
|
// Make sure that the instantiator is captured.
|
|
CaptureInstantiator();
|
|
}
|
|
TypeArguments& factory_type_args =
|
|
TypeArguments::ZoneHandle(Z, list_type_arguments.raw());
|
|
// If the factory class extends other parameterized classes, adjust the
|
|
// type argument vector.
|
|
if (!factory_type_args.IsNull() && (factory_class.NumTypeArguments() > 1)) {
|
|
ASSERT(factory_type_args.Length() == 1);
|
|
Type& factory_type = Type::Handle(Z, Type::New(
|
|
factory_class, factory_type_args, type_pos, Heap::kNew));
|
|
factory_type ^= ClassFinalizer::FinalizeType(
|
|
current_class(), factory_type, ClassFinalizer::kFinalize);
|
|
factory_type_args = factory_type.arguments();
|
|
ASSERT(factory_type_args.Length() == factory_class.NumTypeArguments());
|
|
}
|
|
factory_type_args = factory_type_args.Canonicalize();
|
|
ArgumentListNode* factory_param = new(Z) ArgumentListNode(
|
|
literal_pos);
|
|
if (element_list.length() == 0) {
|
|
LiteralNode* empty_array_literal =
|
|
new(Z) LiteralNode(TokenPos(), Object::empty_array());
|
|
factory_param->Add(empty_array_literal);
|
|
} else {
|
|
ArrayNode* list = new(Z) ArrayNode(TokenPos(), type, element_list);
|
|
factory_param->Add(list);
|
|
}
|
|
return CreateConstructorCallNode(literal_pos,
|
|
factory_type_args,
|
|
factory_method,
|
|
factory_param);
|
|
}
|
|
}
|
|
|
|
|
|
ConstructorCallNode* Parser::CreateConstructorCallNode(
|
|
intptr_t token_pos,
|
|
const TypeArguments& type_arguments,
|
|
const Function& constructor,
|
|
ArgumentListNode* arguments) {
|
|
if (!type_arguments.IsNull() && !type_arguments.IsInstantiated()) {
|
|
EnsureExpressionTemp();
|
|
}
|
|
return new(Z) ConstructorCallNode(
|
|
token_pos, type_arguments, constructor, arguments);
|
|
}
|
|
|
|
|
|
static void AddKeyValuePair(GrowableArray<AstNode*>* pairs,
|
|
bool is_const,
|
|
AstNode* key,
|
|
AstNode* value) {
|
|
if (is_const) {
|
|
ASSERT(key->IsLiteralNode());
|
|
const Instance& new_key = key->AsLiteralNode()->literal();
|
|
for (int i = 0; i < pairs->length(); i += 2) {
|
|
const Instance& key_i = (*pairs)[i]->AsLiteralNode()->literal();
|
|
// The keys of a compile time constant map are compile time
|
|
// constants, i.e. canonicalized values. Thus, we can compare
|
|
// raw pointers to check for equality.
|
|
if (new_key.raw() == key_i.raw()) {
|
|
// Duplicate key found. The new value replaces the previously
|
|
// defined value.
|
|
(*pairs)[i + 1] = value;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
pairs->Add(key);
|
|
pairs->Add(value);
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseMapLiteral(intptr_t type_pos,
|
|
bool is_const,
|
|
const TypeArguments& type_arguments) {
|
|
TRACE_PARSER("ParseMapLiteral");
|
|
ASSERT(type_pos >= 0);
|
|
ASSERT(CurrentToken() == Token::kLBRACE);
|
|
const intptr_t literal_pos = TokenPos();
|
|
ConsumeToken();
|
|
|
|
AbstractType& key_type = Type::ZoneHandle(Z, Type::DynamicType());
|
|
AbstractType& value_type = Type::ZoneHandle(Z, Type::DynamicType());
|
|
TypeArguments& map_type_arguments =
|
|
TypeArguments::ZoneHandle(Z, type_arguments.raw());
|
|
// If no type argument vector is provided, leave it as null, which is
|
|
// equivalent to using dynamic as the type argument for the both key and value
|
|
// types.
|
|
if (!map_type_arguments.IsNull()) {
|
|
ASSERT(map_type_arguments.Length() > 0);
|
|
// Map literals take two type arguments.
|
|
if (map_type_arguments.Length() == 2) {
|
|
key_type = map_type_arguments.TypeAt(0);
|
|
value_type = map_type_arguments.TypeAt(1);
|
|
// Malformed type arguments are mapped to dynamic.
|
|
ASSERT(!key_type.IsMalformed() && !value_type.IsMalformed());
|
|
// No declared bounds in Map.
|
|
ASSERT(!key_type.IsMalbounded() && !value_type.IsMalbounded());
|
|
if (key_type.IsDynamicType() && value_type.IsDynamicType()) {
|
|
map_type_arguments = TypeArguments::null();
|
|
} else if (is_const && !type_arguments.IsInstantiated()) {
|
|
ReportError(type_pos,
|
|
"the type arguments of a constant map literal cannot "
|
|
"include a type variable");
|
|
}
|
|
} else {
|
|
if (FLAG_error_on_bad_type) {
|
|
ReportError(type_pos,
|
|
"a map literal takes two type arguments specifying "
|
|
"the key type and the value type");
|
|
}
|
|
// Ignore type arguments.
|
|
map_type_arguments = TypeArguments::null();
|
|
}
|
|
}
|
|
ASSERT(map_type_arguments.IsNull() || (map_type_arguments.Length() == 2));
|
|
map_type_arguments ^= map_type_arguments.Canonicalize();
|
|
|
|
GrowableArray<AstNode*> kv_pairs_list;
|
|
// Parse the map entries. Note: there may be an optional extra
|
|
// comma after the last entry.
|
|
while (CurrentToken() != Token::kRBRACE) {
|
|
const bool saved_mode = SetAllowFunctionLiterals(true);
|
|
const intptr_t key_pos = TokenPos();
|
|
AstNode* key = ParseExpr(is_const, kConsumeCascades);
|
|
if (FLAG_enable_type_checks &&
|
|
!is_const &&
|
|
!key_type.IsDynamicType()) {
|
|
key = new(Z) AssignableNode(
|
|
key_pos, key, key_type, Symbols::ListLiteralElement());
|
|
}
|
|
if (is_const) {
|
|
ASSERT(key->IsLiteralNode());
|
|
const Instance& key_value = key->AsLiteralNode()->literal();
|
|
if (key_value.IsDouble()) {
|
|
ReportError(key_pos, "key value must not be of type double");
|
|
}
|
|
if (!key_value.IsInteger() &&
|
|
!key_value.IsString() &&
|
|
(key_value.clazz() != I->object_store()->symbol_class()) &&
|
|
ImplementsEqualOperator(key_value)) {
|
|
ReportError(key_pos, "key value must not implement operator ==");
|
|
}
|
|
}
|
|
ExpectToken(Token::kCOLON);
|
|
const intptr_t value_pos = TokenPos();
|
|
AstNode* value = ParseExpr(is_const, kConsumeCascades);
|
|
SetAllowFunctionLiterals(saved_mode);
|
|
if (FLAG_enable_type_checks &&
|
|
!is_const &&
|
|
!value_type.IsDynamicType()) {
|
|
value = new(Z) AssignableNode(
|
|
value_pos, value, value_type, Symbols::ListLiteralElement());
|
|
}
|
|
AddKeyValuePair(&kv_pairs_list, is_const, key, value);
|
|
|
|
if (CurrentToken() == Token::kCOMMA) {
|
|
ConsumeToken();
|
|
} else if (CurrentToken() != Token::kRBRACE) {
|
|
ReportError("comma or '}' expected");
|
|
}
|
|
}
|
|
ASSERT(kv_pairs_list.length() % 2 == 0);
|
|
ExpectToken(Token::kRBRACE);
|
|
|
|
if (is_const) {
|
|
// Create the key-value pair array, canonicalize it and then create
|
|
// the immutable map object with it. This all happens at compile time.
|
|
// The resulting immutable map object is returned as a literal.
|
|
|
|
// First, create the canonicalized key-value pair array.
|
|
Array& key_value_array =
|
|
Array::ZoneHandle(Z, Array::New(kv_pairs_list.length(), Heap::kOld));
|
|
AbstractType& arg_type = Type::Handle(Z);
|
|
Error& malformed_error = Error::Handle(Z);
|
|
for (int i = 0; i < kv_pairs_list.length(); i++) {
|
|
AstNode* arg = kv_pairs_list[i];
|
|
// Arguments have been evaluated to a literal value already.
|
|
ASSERT(arg->IsLiteralNode());
|
|
ASSERT(!is_top_level_); // We cannot check unresolved types.
|
|
if (FLAG_enable_type_checks) {
|
|
if ((i % 2) == 0) {
|
|
// Check key type.
|
|
arg_type = key_type.raw();
|
|
} else {
|
|
// Check value type.
|
|
arg_type = value_type.raw();
|
|
}
|
|
if (!arg_type.IsDynamicType() &&
|
|
(!arg->AsLiteralNode()->literal().IsNull() &&
|
|
!arg->AsLiteralNode()->literal().IsInstanceOf(
|
|
arg_type,
|
|
Object::null_type_arguments(),
|
|
&malformed_error))) {
|
|
// If the failure is due to a malformed type error, display it.
|
|
if (!malformed_error.IsNull()) {
|
|
ReportError(malformed_error);
|
|
} else {
|
|
ReportError(arg->AsLiteralNode()->token_pos(),
|
|
"map literal %s at index %d must be "
|
|
"a constant of type '%s'",
|
|
((i % 2) == 0) ? "key" : "value",
|
|
i >> 1,
|
|
String::Handle(Z,
|
|
arg_type.UserVisibleName()).ToCString());
|
|
}
|
|
}
|
|
}
|
|
key_value_array.SetAt(i, arg->AsLiteralNode()->literal());
|
|
}
|
|
key_value_array.MakeImmutable();
|
|
key_value_array ^= TryCanonicalize(key_value_array, TokenPos());
|
|
|
|
// Construct the map object.
|
|
const Class& immutable_map_class = Class::Handle(Z,
|
|
Library::LookupCoreClass(Symbols::ImmutableMap()));
|
|
ASSERT(!immutable_map_class.IsNull());
|
|
// If the immutable map class extends other parameterized classes, we need
|
|
// to adjust the type argument vector. This is currently not the case.
|
|
ASSERT(immutable_map_class.NumTypeArguments() == 2);
|
|
ArgumentListNode* constr_args = new(Z) ArgumentListNode(TokenPos());
|
|
constr_args->Add(new(Z) LiteralNode(literal_pos, key_value_array));
|
|
const Function& map_constr =
|
|
Function::ZoneHandle(Z, immutable_map_class.LookupConstructor(
|
|
Library::PrivateCoreLibName(Symbols::ImmutableMapConstructor())));
|
|
ASSERT(!map_constr.IsNull());
|
|
const Object& constructor_result = Object::Handle(Z,
|
|
EvaluateConstConstructorCall(immutable_map_class,
|
|
map_type_arguments,
|
|
map_constr,
|
|
constr_args));
|
|
if (constructor_result.IsUnhandledException()) {
|
|
ReportErrors(Error::Cast(constructor_result),
|
|
script_, literal_pos,
|
|
"error executing const Map constructor");
|
|
} else {
|
|
const Instance& const_instance = Instance::Cast(constructor_result);
|
|
return new(Z) LiteralNode(
|
|
literal_pos, Instance::ZoneHandle(Z, const_instance.raw()));
|
|
}
|
|
} else {
|
|
// Factory call at runtime.
|
|
const Class& factory_class =
|
|
Class::Handle(Z, Library::LookupCoreClass(Symbols::Map()));
|
|
ASSERT(!factory_class.IsNull());
|
|
const Function& factory_method = Function::ZoneHandle(Z,
|
|
factory_class.LookupFactory(
|
|
Library::PrivateCoreLibName(Symbols::MapLiteralFactory())));
|
|
ASSERT(!factory_method.IsNull());
|
|
if (!map_type_arguments.IsNull() &&
|
|
!map_type_arguments.IsInstantiated() &&
|
|
(current_block_->scope->function_level() > 0)) {
|
|
// Make sure that the instantiator is captured.
|
|
CaptureInstantiator();
|
|
}
|
|
TypeArguments& factory_type_args =
|
|
TypeArguments::ZoneHandle(Z, map_type_arguments.raw());
|
|
// If the factory class extends other parameterized classes, adjust the
|
|
// type argument vector.
|
|
if (!factory_type_args.IsNull() && (factory_class.NumTypeArguments() > 2)) {
|
|
ASSERT(factory_type_args.Length() == 2);
|
|
Type& factory_type = Type::Handle(Z, Type::New(
|
|
factory_class, factory_type_args, type_pos, Heap::kNew));
|
|
factory_type ^= ClassFinalizer::FinalizeType(
|
|
current_class(), factory_type, ClassFinalizer::kFinalize);
|
|
factory_type_args = factory_type.arguments();
|
|
ASSERT(factory_type_args.Length() == factory_class.NumTypeArguments());
|
|
}
|
|
factory_type_args = factory_type_args.Canonicalize();
|
|
ArgumentListNode* factory_param = new(Z) ArgumentListNode(literal_pos);
|
|
// The kv_pair array is temporary and of element type dynamic. It is passed
|
|
// to the factory to initialize a properly typed map.
|
|
ArrayNode* kv_pairs = new(Z) ArrayNode(
|
|
TokenPos(),
|
|
Type::ZoneHandle(Z, Type::ArrayType()),
|
|
kv_pairs_list);
|
|
factory_param->Add(kv_pairs);
|
|
return CreateConstructorCallNode(literal_pos,
|
|
factory_type_args,
|
|
factory_method,
|
|
factory_param);
|
|
}
|
|
UNREACHABLE();
|
|
return NULL;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseCompoundLiteral() {
|
|
TRACE_PARSER("ParseCompoundLiteral");
|
|
bool is_const = false;
|
|
if (CurrentToken() == Token::kCONST) {
|
|
is_const = true;
|
|
ConsumeToken();
|
|
}
|
|
const intptr_t type_pos = TokenPos();
|
|
TypeArguments& type_arguments = TypeArguments::Handle(Z,
|
|
ParseTypeArguments(ClassFinalizer::kCanonicalize));
|
|
// Malformed type arguments are mapped to dynamic, so we will not encounter
|
|
// them here.
|
|
// Map and List interfaces do not declare bounds on their type parameters, so
|
|
// we will not see malbounded type arguments here.
|
|
AstNode* primary = NULL;
|
|
if ((CurrentToken() == Token::kLBRACK) ||
|
|
(CurrentToken() == Token::kINDEX)) {
|
|
primary = ParseListLiteral(type_pos, is_const, type_arguments);
|
|
} else if (CurrentToken() == Token::kLBRACE) {
|
|
primary = ParseMapLiteral(type_pos, is_const, type_arguments);
|
|
} else {
|
|
ReportError("unexpected token %s", Token::Str(CurrentToken()));
|
|
}
|
|
return primary;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseSymbolLiteral() {
|
|
ASSERT(CurrentToken() == Token::kHASH);
|
|
ConsumeToken();
|
|
intptr_t symbol_pos = TokenPos();
|
|
String& symbol = String::Handle(Z);
|
|
if (IsIdentifier()) {
|
|
symbol = CurrentLiteral()->raw();
|
|
ConsumeToken();
|
|
while (CurrentToken() == Token::kPERIOD) {
|
|
symbol = String::Concat(symbol, Symbols::Dot());
|
|
ConsumeToken();
|
|
symbol = String::Concat(symbol,
|
|
*ExpectIdentifier("identifier expected"));
|
|
}
|
|
} else if (Token::CanBeOverloaded(CurrentToken())) {
|
|
symbol = String::New(Token::Str(CurrentToken()));
|
|
ConsumeToken();
|
|
} else {
|
|
ReportError("illegal symbol literal");
|
|
}
|
|
|
|
// Call Symbol class constructor to create a symbol instance.
|
|
const Class& symbol_class = Class::Handle(I->object_store()->symbol_class());
|
|
ASSERT(!symbol_class.IsNull());
|
|
ArgumentListNode* constr_args = new(Z) ArgumentListNode(symbol_pos);
|
|
constr_args->Add(new(Z) LiteralNode(
|
|
symbol_pos, String::ZoneHandle(Z, Symbols::New(symbol))));
|
|
const Function& constr = Function::ZoneHandle(Z,
|
|
symbol_class.LookupConstructor(Symbols::SymbolCtor()));
|
|
ASSERT(!constr.IsNull());
|
|
const Object& result = Object::Handle(Z,
|
|
EvaluateConstConstructorCall(symbol_class,
|
|
TypeArguments::Handle(Z),
|
|
constr,
|
|
constr_args));
|
|
if (result.IsUnhandledException()) {
|
|
ReportErrors(Error::Cast(result),
|
|
script_, symbol_pos,
|
|
"error executing const Symbol constructor");
|
|
}
|
|
const Instance& instance = Instance::Cast(result);
|
|
return new(Z) LiteralNode(symbol_pos,
|
|
Instance::ZoneHandle(Z, instance.raw()));
|
|
}
|
|
|
|
|
|
static String& BuildConstructorName(const String& type_class_name,
|
|
const String* named_constructor) {
|
|
// By convention, the static function implementing a named constructor 'C'
|
|
// for class 'A' is labeled 'A.C', and the static function implementing the
|
|
// unnamed constructor for class 'A' is labeled 'A.'.
|
|
// This convention prevents users from explicitly calling constructors.
|
|
String& constructor_name =
|
|
String::Handle(String::Concat(type_class_name, Symbols::Dot()));
|
|
if (named_constructor != NULL) {
|
|
constructor_name = String::Concat(constructor_name, *named_constructor);
|
|
}
|
|
return constructor_name;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParseNewOperator(Token::Kind op_kind) {
|
|
TRACE_PARSER("ParseNewOperator");
|
|
const intptr_t new_pos = TokenPos();
|
|
ASSERT((op_kind == Token::kNEW) || (op_kind == Token::kCONST));
|
|
bool is_const = (op_kind == Token::kCONST);
|
|
if (!IsIdentifier()) {
|
|
ReportError("type name expected");
|
|
}
|
|
intptr_t type_pos = TokenPos();
|
|
// Can't allocate const objects of a deferred type.
|
|
const bool allow_deferred_type = !is_const;
|
|
const bool consume_unresolved_prefix = (LookaheadToken(3) == Token::kLT) ||
|
|
(LookaheadToken(3) == Token::kPERIOD);
|
|
AbstractType& type = AbstractType::Handle(Z,
|
|
ParseType(ClassFinalizer::kCanonicalizeWellFormed,
|
|
allow_deferred_type,
|
|
consume_unresolved_prefix));
|
|
// In case the type is malformed, throw a dynamic type error after finishing
|
|
// parsing the instance creation expression.
|
|
if (!type.IsMalformed() && (type.IsTypeParameter() || type.IsDynamicType())) {
|
|
// Replace the type with a malformed type.
|
|
type = ClassFinalizer::NewFinalizedMalformedType(
|
|
Error::Handle(Z), // No previous error.
|
|
script_,
|
|
type_pos,
|
|
"%s'%s' cannot be instantiated",
|
|
type.IsTypeParameter() ? "type parameter " : "",
|
|
type.IsTypeParameter() ?
|
|
String::Handle(Z, type.UserVisibleName()).ToCString() :
|
|
"dynamic");
|
|
}
|
|
// Attempting to instantiate an enum type is a compile-time error.
|
|
Class& type_class = Class::Handle(Z, type.type_class());
|
|
if (type_class.is_enum_class()) {
|
|
ReportError(new_pos, "enum type '%s' can not be instantiated",
|
|
String::Handle(Z, type_class.Name()).ToCString());
|
|
}
|
|
|
|
// The grammar allows for an optional ('.' identifier)? after the type, which
|
|
// is a named constructor. Note that we tell ParseType() above not to
|
|
// consume it as part of a misinterpreted qualified identifier. Only a
|
|
// valid library prefix is accepted as qualifier.
|
|
String* named_constructor = NULL;
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
named_constructor = ExpectIdentifier("name of constructor expected");
|
|
}
|
|
|
|
// Parse constructor parameters.
|
|
CheckToken(Token::kLPAREN);
|
|
intptr_t call_pos = TokenPos();
|
|
ArgumentListNode* arguments = ParseActualParameters(NULL, is_const);
|
|
|
|
// Parsing is complete, so we can return a throw in case of a malformed or
|
|
// malbounded type or report a compile-time error if the constructor is const.
|
|
if (type.IsMalformedOrMalbounded()) {
|
|
if (is_const) {
|
|
const Error& error = Error::Handle(Z, type.error());
|
|
ReportError(error);
|
|
}
|
|
return ThrowTypeError(type_pos, type);
|
|
}
|
|
|
|
// Resolve the type and optional identifier to a constructor or factory.
|
|
String& type_class_name = String::Handle(Z, type_class.Name());
|
|
TypeArguments& type_arguments =
|
|
TypeArguments::ZoneHandle(Z, type.arguments());
|
|
|
|
// A constructor has an implicit 'this' parameter (instance to construct)
|
|
// and a factory has an implicit 'this' parameter (type_arguments).
|
|
// A constructor has a second implicit 'phase' parameter.
|
|
intptr_t arguments_length = arguments->length() + 2;
|
|
|
|
// An additional type check of the result of a redirecting factory may be
|
|
// required.
|
|
AbstractType& type_bound = AbstractType::ZoneHandle(Z);
|
|
|
|
// Make sure that an appropriate constructor exists.
|
|
String& constructor_name =
|
|
BuildConstructorName(type_class_name, named_constructor);
|
|
Function& constructor = Function::ZoneHandle(Z,
|
|
type_class.LookupConstructor(constructor_name));
|
|
if (constructor.IsNull()) {
|
|
constructor = type_class.LookupFactory(constructor_name);
|
|
if (constructor.IsNull()) {
|
|
const String& external_constructor_name =
|
|
(named_constructor ? constructor_name : type_class_name);
|
|
// Replace the type with a malformed type and compile a throw or report a
|
|
// compile-time error if the constructor is const.
|
|
if (is_const) {
|
|
type = ClassFinalizer::NewFinalizedMalformedType(
|
|
Error::Handle(Z), // No previous error.
|
|
script_,
|
|
call_pos,
|
|
"class '%s' has no constructor or factory named '%s'",
|
|
String::Handle(Z, type_class.Name()).ToCString(),
|
|
external_constructor_name.ToCString());
|
|
ReportError(Error::Handle(Z, type.error()));
|
|
}
|
|
return ThrowNoSuchMethodError(call_pos,
|
|
type_class,
|
|
external_constructor_name,
|
|
arguments,
|
|
InvocationMirror::kConstructor,
|
|
InvocationMirror::kMethod,
|
|
NULL); // No existing function.
|
|
} else if (constructor.IsRedirectingFactory()) {
|
|
ClassFinalizer::ResolveRedirectingFactory(type_class, constructor);
|
|
Type& redirect_type = Type::Handle(Z, constructor.RedirectionType());
|
|
if (!redirect_type.IsMalformedOrMalbounded() &&
|
|
!redirect_type.IsInstantiated()) {
|
|
// The type arguments of the redirection type are instantiated from the
|
|
// type arguments of the parsed type of the 'new' or 'const' expression.
|
|
Error& error = Error::Handle(Z);
|
|
redirect_type ^= redirect_type.InstantiateFrom(type_arguments, &error);
|
|
if (!error.IsNull()) {
|
|
redirect_type = ClassFinalizer::NewFinalizedMalformedType(
|
|
error,
|
|
script_,
|
|
call_pos,
|
|
"redirecting factory type '%s' cannot be instantiated",
|
|
String::Handle(Z, redirect_type.UserVisibleName()).ToCString());
|
|
}
|
|
}
|
|
if (redirect_type.IsMalformedOrMalbounded()) {
|
|
if (is_const) {
|
|
ReportError(Error::Handle(Z, redirect_type.error()));
|
|
}
|
|
return ThrowTypeError(redirect_type.token_pos(), redirect_type);
|
|
}
|
|
if (FLAG_enable_type_checks && !redirect_type.IsSubtypeOf(type, NULL)) {
|
|
// Additional type checking of the result is necessary.
|
|
type_bound = type.raw();
|
|
}
|
|
type = redirect_type.raw();
|
|
type_class = type.type_class();
|
|
type_class_name = type_class.Name();
|
|
type_arguments = type.arguments();
|
|
constructor = constructor.RedirectionTarget();
|
|
constructor_name = constructor.name();
|
|
ASSERT(!constructor.IsNull());
|
|
}
|
|
if (constructor.IsFactory()) {
|
|
// A factory does not have the implicit 'phase' parameter.
|
|
arguments_length -= 1;
|
|
}
|
|
}
|
|
|
|
// It is ok to call a factory method of an abstract class, but it is
|
|
// a dynamic error to instantiate an abstract class.
|
|
ASSERT(!constructor.IsNull());
|
|
if (type_class.is_abstract() && !constructor.IsFactory()) {
|
|
// Evaluate arguments before throwing.
|
|
LetNode* result = new(Z) LetNode(call_pos);
|
|
for (intptr_t i = 0; i < arguments->length(); ++i) {
|
|
result->AddNode(arguments->NodeAt(i));
|
|
}
|
|
ArgumentListNode* error_arguments = new(Z) ArgumentListNode(type_pos);
|
|
error_arguments->Add(new(Z) LiteralNode(
|
|
TokenPos(), Integer::ZoneHandle(Z, Integer::New(type_pos))));
|
|
error_arguments->Add(new(Z) LiteralNode(
|
|
TokenPos(), String::ZoneHandle(Z, type_class_name.raw())));
|
|
result->AddNode(
|
|
MakeStaticCall(Symbols::AbstractClassInstantiationError(),
|
|
Library::PrivateCoreLibName(Symbols::ThrowNew()),
|
|
error_arguments));
|
|
return result;
|
|
}
|
|
String& error_message = String::Handle(Z);
|
|
if (!constructor.AreValidArguments(arguments_length,
|
|
arguments->names(),
|
|
&error_message)) {
|
|
const String& external_constructor_name =
|
|
(named_constructor ? constructor_name : type_class_name);
|
|
if (is_const) {
|
|
ReportError(call_pos,
|
|
"invalid arguments passed to constructor '%s' "
|
|
"for class '%s': %s",
|
|
external_constructor_name.ToCString(),
|
|
String::Handle(Z, type_class.Name()).ToCString(),
|
|
error_message.ToCString());
|
|
}
|
|
return ThrowNoSuchMethodError(call_pos,
|
|
type_class,
|
|
external_constructor_name,
|
|
arguments,
|
|
InvocationMirror::kConstructor,
|
|
InvocationMirror::kMethod,
|
|
&constructor);
|
|
}
|
|
|
|
// Return a throw in case of a malformed or malbounded type or report a
|
|
// compile-time error if the constructor is const.
|
|
if (type.IsMalformedOrMalbounded()) {
|
|
if (is_const) {
|
|
ReportError(Error::Handle(Z, type.error()));
|
|
}
|
|
return ThrowTypeError(type_pos, type);
|
|
}
|
|
type_arguments ^= type_arguments.Canonicalize();
|
|
// Make the constructor call.
|
|
AstNode* new_object = NULL;
|
|
if (is_const) {
|
|
if (!constructor.is_const()) {
|
|
const String& external_constructor_name =
|
|
(named_constructor ? constructor_name : type_class_name);
|
|
ReportError("non-const constructor '%s' cannot be used in "
|
|
"const object creation",
|
|
external_constructor_name.ToCString());
|
|
}
|
|
const Object& constructor_result = Object::Handle(Z,
|
|
EvaluateConstConstructorCall(type_class,
|
|
type_arguments,
|
|
constructor,
|
|
arguments));
|
|
if (constructor_result.IsUnhandledException()) {
|
|
// It's a compile-time error if invocation of a const constructor
|
|
// call fails.
|
|
ReportErrors(Error::Cast(constructor_result),
|
|
script_, new_pos,
|
|
"error while evaluating const constructor");
|
|
} else {
|
|
// Const constructors can return null in the case where a const native
|
|
// factory returns a null value. Thus we cannot use a Instance::Cast here.
|
|
Instance& const_instance = Instance::Handle(Z);
|
|
const_instance ^= constructor_result.raw();
|
|
new_object = new(Z) LiteralNode(
|
|
new_pos, Instance::ZoneHandle(Z, const_instance.raw()));
|
|
if (!type_bound.IsNull()) {
|
|
ASSERT(!type_bound.IsMalformed());
|
|
Error& malformed_error = Error::Handle(Z);
|
|
ASSERT(!is_top_level_); // We cannot check unresolved types.
|
|
if (!const_instance.IsInstanceOf(type_bound,
|
|
TypeArguments::Handle(Z),
|
|
&malformed_error)) {
|
|
type_bound = ClassFinalizer::NewFinalizedMalformedType(
|
|
malformed_error,
|
|
script_,
|
|
new_pos,
|
|
"const factory result is not an instance of '%s'",
|
|
String::Handle(Z, type_bound.UserVisibleName()).ToCString());
|
|
new_object = ThrowTypeError(new_pos, type_bound);
|
|
}
|
|
type_bound = AbstractType::null();
|
|
}
|
|
}
|
|
} else {
|
|
CheckConstructorCallTypeArguments(new_pos, constructor, type_arguments);
|
|
if (!type_arguments.IsNull() &&
|
|
!type_arguments.IsInstantiated() &&
|
|
(current_block_->scope->function_level() > 0)) {
|
|
// Make sure that the instantiator is captured.
|
|
CaptureInstantiator();
|
|
}
|
|
// If the type argument vector is not instantiated, we verify in checked
|
|
// mode at runtime that it is within its declared bounds.
|
|
new_object = CreateConstructorCallNode(
|
|
new_pos, type_arguments, constructor, arguments);
|
|
}
|
|
if (!type_bound.IsNull()) {
|
|
new_object = new(Z) AssignableNode(
|
|
new_pos, new_object, type_bound, Symbols::FactoryResult());
|
|
}
|
|
return new_object;
|
|
}
|
|
|
|
|
|
String& Parser::Interpolate(const GrowableArray<AstNode*>& values) {
|
|
const Class& cls = Class::Handle(
|
|
Z, Library::LookupCoreClass(Symbols::StringBase()));
|
|
ASSERT(!cls.IsNull());
|
|
const Function& func = Function::Handle(Z, cls.LookupStaticFunction(
|
|
Library::PrivateCoreLibName(Symbols::Interpolate())));
|
|
ASSERT(!func.IsNull());
|
|
|
|
// Build the array of literal values to interpolate.
|
|
const Array& value_arr = Array::Handle(Z, Array::New(values.length()));
|
|
for (int i = 0; i < values.length(); i++) {
|
|
ASSERT(values[i]->IsLiteralNode());
|
|
value_arr.SetAt(i, values[i]->AsLiteralNode()->literal());
|
|
}
|
|
|
|
// Build argument array to pass to the interpolation function.
|
|
const Array& interpolate_arg = Array::Handle(Z, Array::New(1));
|
|
interpolate_arg.SetAt(0, value_arr);
|
|
|
|
// Call interpolation function.
|
|
Object& result = Object::Handle(Z);
|
|
{
|
|
PAUSETIMERSCOPE(I, time_compilation);
|
|
result = DartEntry::InvokeFunction(func, interpolate_arg);
|
|
}
|
|
if (result.IsUnhandledException()) {
|
|
ReportError("%s", Error::Cast(result).ToErrorCString());
|
|
}
|
|
String& concatenated = String::ZoneHandle(Z);
|
|
concatenated ^= result.raw();
|
|
concatenated = Symbols::New(concatenated);
|
|
return concatenated;
|
|
}
|
|
|
|
|
|
// A string literal consists of the concatenation of the next n tokens
|
|
// that satisfy the EBNF grammar:
|
|
// literal = kSTRING {{ interpol } kSTRING }
|
|
// interpol = kINTERPOL_VAR | (kINTERPOL_START expression kINTERPOL_END)
|
|
// In other words, the scanner breaks down interpolated strings so that
|
|
// a string literal always begins and ends with a kSTRING token.
|
|
AstNode* Parser::ParseStringLiteral(bool allow_interpolation) {
|
|
TRACE_PARSER("ParseStringLiteral");
|
|
AstNode* primary = NULL;
|
|
const intptr_t literal_start = TokenPos();
|
|
ASSERT(CurrentToken() == Token::kSTRING);
|
|
Token::Kind l1_token = LookaheadToken(1);
|
|
if ((l1_token != Token::kSTRING) &&
|
|
(l1_token != Token::kINTERPOL_VAR) &&
|
|
(l1_token != Token::kINTERPOL_START)) {
|
|
// Common case: no interpolation.
|
|
primary = new(Z) LiteralNode(literal_start, *CurrentLiteral());
|
|
ConsumeToken();
|
|
return primary;
|
|
}
|
|
// String interpolation needed.
|
|
bool is_compiletime_const = true;
|
|
bool has_interpolation = false;
|
|
GrowableArray<AstNode*> values_list;
|
|
while (CurrentToken() == Token::kSTRING) {
|
|
if (CurrentLiteral()->Length() > 0) {
|
|
// Only add non-empty string sections to the values list
|
|
// that will be concatenated.
|
|
values_list.Add(new(Z) LiteralNode(TokenPos(), *CurrentLiteral()));
|
|
}
|
|
ConsumeToken();
|
|
while ((CurrentToken() == Token::kINTERPOL_VAR) ||
|
|
(CurrentToken() == Token::kINTERPOL_START)) {
|
|
if (!allow_interpolation) {
|
|
ReportError("string interpolation not allowed in this context");
|
|
}
|
|
has_interpolation = true;
|
|
AstNode* expr = NULL;
|
|
const intptr_t expr_pos = TokenPos();
|
|
if (CurrentToken() == Token::kINTERPOL_VAR) {
|
|
expr = ResolveIdent(TokenPos(), *CurrentLiteral(), true);
|
|
ConsumeToken();
|
|
} else {
|
|
ASSERT(CurrentToken() == Token::kINTERPOL_START);
|
|
ConsumeToken();
|
|
const bool saved_mode = SetAllowFunctionLiterals(true);
|
|
expr = ParseExpr(kAllowConst, kConsumeCascades);
|
|
SetAllowFunctionLiterals(saved_mode);
|
|
ExpectToken(Token::kINTERPOL_END);
|
|
}
|
|
// Check if this interpolated string is still considered a compile time
|
|
// constant. If it is we need to evaluate if the current string part is
|
|
// a constant or not. Only strings, numbers, booleans and null values
|
|
// are allowed in compile time const interpolations.
|
|
if (is_compiletime_const) {
|
|
const Object* const_expr = expr->EvalConstExpr();
|
|
if ((const_expr != NULL) &&
|
|
(const_expr->IsNumber() ||
|
|
const_expr->IsString() ||
|
|
const_expr->IsBool() ||
|
|
const_expr->IsNull())) {
|
|
// Change expr into a literal.
|
|
expr = new(Z) LiteralNode(expr_pos,
|
|
EvaluateConstExpr(expr_pos, expr));
|
|
} else {
|
|
is_compiletime_const = false;
|
|
}
|
|
}
|
|
values_list.Add(expr);
|
|
}
|
|
}
|
|
if (is_compiletime_const) {
|
|
if (has_interpolation) {
|
|
primary = new(Z) LiteralNode(literal_start, Interpolate(values_list));
|
|
} else {
|
|
const Array& strings = Array::Handle(Z, Array::New(values_list.length()));
|
|
for (int i = 0; i < values_list.length(); i++) {
|
|
const Instance& part = values_list[i]->AsLiteralNode()->literal();
|
|
ASSERT(part.IsString());
|
|
strings.SetAt(i, String::Cast(part));
|
|
}
|
|
String& lit = String::ZoneHandle(Z,
|
|
String::ConcatAll(strings, Heap::kOld));
|
|
lit = Symbols::New(lit);
|
|
primary = new(Z) LiteralNode(literal_start, lit);
|
|
}
|
|
} else {
|
|
ArrayNode* values = new(Z) ArrayNode(
|
|
TokenPos(),
|
|
Type::ZoneHandle(Z, Type::ArrayType()),
|
|
values_list);
|
|
primary = new(Z) StringInterpolateNode(TokenPos(), values);
|
|
}
|
|
return primary;
|
|
}
|
|
|
|
|
|
AstNode* Parser::ParsePrimary() {
|
|
TRACE_PARSER("ParsePrimary");
|
|
ASSERT(!is_top_level_);
|
|
AstNode* primary = NULL;
|
|
const Token::Kind token = CurrentToken();
|
|
if (IsFunctionLiteral()) {
|
|
// The name of a literal function is visible from inside the function, but
|
|
// must not collide with names in the scope declaring the literal.
|
|
OpenBlock();
|
|
primary = ParseFunctionStatement(true);
|
|
CloseBlock();
|
|
} else if (IsIdentifier()) {
|
|
intptr_t qual_ident_pos = TokenPos();
|
|
const LibraryPrefix& prefix = LibraryPrefix::ZoneHandle(Z, ParsePrefix());
|
|
String& ident = *CurrentLiteral();
|
|
ConsumeToken();
|
|
if (prefix.IsNull()) {
|
|
if (!ResolveIdentInLocalScope(qual_ident_pos, ident, &primary)) {
|
|
// Check whether the identifier is a type parameter.
|
|
if (!current_class().IsNull()) {
|
|
TypeParameter& type_param = TypeParameter::ZoneHandle(Z,
|
|
current_class().LookupTypeParameter(ident));
|
|
if (!type_param.IsNull()) {
|
|
return new(Z) PrimaryNode(qual_ident_pos, type_param);
|
|
}
|
|
}
|
|
// This is a non-local unqualified identifier so resolve the
|
|
// identifier locally in the main app library and all libraries
|
|
// imported by it.
|
|
primary = ResolveIdentInCurrentLibraryScope(qual_ident_pos, ident);
|
|
}
|
|
} else {
|
|
// This is a qualified identifier with a library prefix so resolve
|
|
// the identifier locally in that library (we do not include the
|
|
// libraries imported by that library).
|
|
primary = ResolveIdentInPrefixScope(qual_ident_pos, prefix, ident);
|
|
|
|
// If the identifier could not be resolved, throw a NoSuchMethodError.
|
|
// Note: unlike in the case of an unqualified identifier, do not
|
|
// interpret the unresolved identifier as an instance method or
|
|
// instance getter call when compiling an instance method.
|
|
if (primary == NULL) {
|
|
if (prefix.is_deferred_load() &&
|
|
ident.Equals(Symbols::LoadLibrary())) {
|
|
// Hack Alert: recognize special 'loadLibrary' call on the
|
|
// prefix object. The prefix is the primary. Rewind parser and
|
|
// let ParseSelectors() handle the loadLibrary call.
|
|
SetPosition(qual_ident_pos);
|
|
ConsumeToken(); // Prefix name.
|
|
primary = new(Z) LiteralNode(qual_ident_pos, prefix);
|
|
} else {
|
|
// TODO(hausner): Ideally we should generate the NoSuchMethodError
|
|
// later, when we know more about how the unresolved name is used.
|
|
// For example, we don't know yet whether the unresolved name
|
|
// refers to a getter or a setter. However, it is more awkward
|
|
// to distinuish four NoSuchMethodError cases all over the place
|
|
// in the parser. The four cases are: prefixed vs non-prefixed
|
|
// name, static vs dynamic context in which the unresolved name
|
|
// is used. We cheat a little here by looking at the next token
|
|
// to determine whether we have an unresolved method call or
|
|
// field access.
|
|
String& qualified_name = String::ZoneHandle(Z, prefix.name());
|
|
qualified_name = String::Concat(qualified_name, Symbols::Dot());
|
|
qualified_name = String::Concat(qualified_name, ident);
|
|
qualified_name = Symbols::New(qualified_name);
|
|
InvocationMirror::Type call_type =
|
|
CurrentToken() == Token::kLPAREN ?
|
|
InvocationMirror::kMethod : InvocationMirror::kGetter;
|
|
primary = ThrowNoSuchMethodError(qual_ident_pos,
|
|
current_class(),
|
|
qualified_name,
|
|
NULL, // No arguments.
|
|
InvocationMirror::kTopLevel,
|
|
call_type,
|
|
NULL); // No existing function.
|
|
}
|
|
}
|
|
}
|
|
ASSERT(primary != NULL);
|
|
} else if (token == Token::kTHIS) {
|
|
LocalVariable* local = LookupLocalScope(Symbols::This());
|
|
if (local == NULL) {
|
|
ReportError("receiver 'this' is not in scope");
|
|
}
|
|
primary = new(Z) LoadLocalNode(TokenPos(), local);
|
|
ConsumeToken();
|
|
} else if (token == Token::kINTEGER) {
|
|
const Integer& literal = Integer::ZoneHandle(Z, CurrentIntegerLiteral());
|
|
primary = new(Z) LiteralNode(TokenPos(), literal);
|
|
ConsumeToken();
|
|
} else if (token == Token::kTRUE) {
|
|
primary = new(Z) LiteralNode(TokenPos(), Bool::True());
|
|
ConsumeToken();
|
|
} else if (token == Token::kFALSE) {
|
|
primary = new(Z) LiteralNode(TokenPos(), Bool::False());
|
|
ConsumeToken();
|
|
} else if (token == Token::kNULL) {
|
|
primary = new(Z) LiteralNode(TokenPos(), Instance::ZoneHandle(Z));
|
|
ConsumeToken();
|
|
} else if (token == Token::kLPAREN) {
|
|
ConsumeToken();
|
|
const bool saved_mode = SetAllowFunctionLiterals(true);
|
|
primary = ParseExpr(kAllowConst, kConsumeCascades);
|
|
SetAllowFunctionLiterals(saved_mode);
|
|
ExpectToken(Token::kRPAREN);
|
|
} else if (token == Token::kDOUBLE) {
|
|
Double& double_value = Double::ZoneHandle(Z, CurrentDoubleLiteral());
|
|
if (double_value.IsNull()) {
|
|
ReportError("invalid double literal");
|
|
}
|
|
primary = new(Z) LiteralNode(TokenPos(), double_value);
|
|
ConsumeToken();
|
|
} else if (token == Token::kSTRING) {
|
|
primary = ParseStringLiteral(true);
|
|
} else if (token == Token::kNEW) {
|
|
ConsumeToken();
|
|
primary = ParseNewOperator(Token::kNEW);
|
|
} else if (token == Token::kCONST) {
|
|
if ((LookaheadToken(1) == Token::kLT) ||
|
|
(LookaheadToken(1) == Token::kLBRACK) ||
|
|
(LookaheadToken(1) == Token::kINDEX) ||
|
|
(LookaheadToken(1) == Token::kLBRACE)) {
|
|
primary = ParseCompoundLiteral();
|
|
} else {
|
|
ConsumeToken();
|
|
primary = ParseNewOperator(Token::kCONST);
|
|
}
|
|
} else if (token == Token::kLT ||
|
|
token == Token::kLBRACK ||
|
|
token == Token::kINDEX ||
|
|
token == Token::kLBRACE) {
|
|
primary = ParseCompoundLiteral();
|
|
} else if (token == Token::kHASH) {
|
|
primary = ParseSymbolLiteral();
|
|
} else if (token == Token::kSUPER) {
|
|
if (current_function().is_static()) {
|
|
ReportError("cannot access superclass from static method");
|
|
}
|
|
if (current_class().SuperClass() == Class::null()) {
|
|
ReportError("class '%s' does not have a superclass",
|
|
String::Handle(Z, current_class().Name()).ToCString());
|
|
}
|
|
if (current_class().IsMixinApplication()) {
|
|
const Type& mixin_type = Type::Handle(Z, current_class().mixin());
|
|
if (mixin_type.type_class() == current_function().origin()) {
|
|
ReportError("method of mixin class '%s' may not refer to 'super'",
|
|
String::Handle(Z, Class::Handle(Z,
|
|
current_function().origin()).Name()).ToCString());
|
|
}
|
|
}
|
|
const intptr_t super_pos = TokenPos();
|
|
ConsumeToken();
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
const intptr_t ident_pos = TokenPos();
|
|
const String& ident = *ExpectIdentifier("identifier expected");
|
|
if (CurrentToken() == Token::kLPAREN) {
|
|
primary = ParseSuperCall(ident);
|
|
} else {
|
|
primary = ParseSuperFieldAccess(ident, ident_pos);
|
|
}
|
|
} else if ((CurrentToken() == Token::kLBRACK) ||
|
|
Token::CanBeOverloaded(CurrentToken()) ||
|
|
(CurrentToken() == Token::kNE)) {
|
|
primary = ParseSuperOperator();
|
|
} else {
|
|
primary = new(Z) PrimaryNode(super_pos, Symbols::Super());
|
|
}
|
|
} else {
|
|
UnexpectedToken();
|
|
}
|
|
return primary;
|
|
}
|
|
|
|
|
|
// Evaluate expression in expr and return the value. The expression must
|
|
// be a compile time constant.
|
|
const Instance& Parser::EvaluateConstExpr(intptr_t expr_pos, AstNode* expr) {
|
|
if (expr->IsLiteralNode()) {
|
|
return expr->AsLiteralNode()->literal();
|
|
} else if (expr->IsLoadLocalNode() &&
|
|
expr->AsLoadLocalNode()->local().IsConst()) {
|
|
return *expr->AsLoadLocalNode()->local().ConstValue();
|
|
} else if (expr->IsLoadStaticFieldNode()) {
|
|
const Field& field = expr->AsLoadStaticFieldNode()->field();
|
|
// We already checked that this field is const and has been
|
|
// initialized.
|
|
ASSERT(field.is_const());
|
|
ASSERT(field.value() != Object::sentinel().raw());
|
|
ASSERT(field.value() != Object::transition_sentinel().raw());
|
|
return Instance::ZoneHandle(Z, field.value());
|
|
} else {
|
|
ASSERT(expr->EvalConstExpr() != NULL);
|
|
ReturnNode* ret = new(Z) ReturnNode(expr->token_pos(), expr);
|
|
// Compile time constant expressions cannot reference anything from a
|
|
// local scope.
|
|
LocalScope* empty_scope = new(Z) LocalScope(NULL, 0, 0);
|
|
SequenceNode* seq = new(Z) SequenceNode(expr->token_pos(), empty_scope);
|
|
seq->Add(ret);
|
|
|
|
Object& result = Object::Handle(Z, Compiler::ExecuteOnce(seq));
|
|
if (result.IsError()) {
|
|
ReportErrors(Error::Cast(result),
|
|
script_, expr_pos,
|
|
"error evaluating constant expression");
|
|
}
|
|
ASSERT(result.IsInstance());
|
|
Instance& value = Instance::ZoneHandle(Z);
|
|
value ^= result.raw();
|
|
value = TryCanonicalize(value, TokenPos());
|
|
return value;
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipFunctionLiteral() {
|
|
if (IsIdentifier()) {
|
|
if (LookaheadToken(1) != Token::kLPAREN) {
|
|
SkipType(true);
|
|
}
|
|
ExpectIdentifier("function name expected");
|
|
}
|
|
if (CurrentToken() == Token::kLPAREN) {
|
|
const bool allow_explicit_default_values = true;
|
|
ParamList params;
|
|
params.skipped = true;
|
|
ParseFormalParameterList(allow_explicit_default_values, false, ¶ms);
|
|
}
|
|
RawFunction::AsyncModifier async_modifier = ParseFunctionModifier();
|
|
BoolScope allow_await(&this->await_is_keyword_,
|
|
async_modifier != RawFunction::kNoModifier);
|
|
if (CurrentToken() == Token::kLBRACE) {
|
|
SkipBlock();
|
|
ExpectToken(Token::kRBRACE);
|
|
} else if (CurrentToken() == Token::kARROW) {
|
|
ConsumeToken();
|
|
SkipExpr();
|
|
}
|
|
}
|
|
|
|
|
|
// Skips function/method/constructor/getter/setter preambles until the formal
|
|
// parameter list. It is enough to skip the tokens, since we have already
|
|
// previously parsed the function.
|
|
void Parser::SkipFunctionPreamble() {
|
|
while (true) {
|
|
const Token::Kind token = CurrentToken();
|
|
if (token == Token::kLPAREN) {
|
|
return;
|
|
}
|
|
if (token == Token::kGET) {
|
|
if (LookaheadToken(1) == Token::kLPAREN) {
|
|
// Case: Function/method named get.
|
|
ConsumeToken(); // Parse away 'get' (the function's name).
|
|
return;
|
|
}
|
|
// Case: Getter.
|
|
ConsumeToken(); // Parse away 'get'.
|
|
ConsumeToken(); // Parse away the getter name.
|
|
return;
|
|
}
|
|
ConsumeToken();
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipListLiteral() {
|
|
if (CurrentToken() == Token::kINDEX) {
|
|
// Empty list literal.
|
|
ConsumeToken();
|
|
return;
|
|
}
|
|
ExpectToken(Token::kLBRACK);
|
|
while (CurrentToken() != Token::kRBRACK) {
|
|
SkipNestedExpr();
|
|
if (CurrentToken() == Token::kCOMMA) {
|
|
ConsumeToken();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
ExpectToken(Token::kRBRACK);
|
|
}
|
|
|
|
|
|
void Parser::SkipMapLiteral() {
|
|
ExpectToken(Token::kLBRACE);
|
|
while (CurrentToken() != Token::kRBRACE) {
|
|
SkipNestedExpr();
|
|
ExpectToken(Token::kCOLON);
|
|
SkipNestedExpr();
|
|
if (CurrentToken() == Token::kCOMMA) {
|
|
ConsumeToken();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
ExpectToken(Token::kRBRACE);
|
|
}
|
|
|
|
|
|
void Parser::SkipActualParameters() {
|
|
ExpectToken(Token::kLPAREN);
|
|
while (CurrentToken() != Token::kRPAREN) {
|
|
if (IsIdentifier() && (LookaheadToken(1) == Token::kCOLON)) {
|
|
// Named actual parameter.
|
|
ConsumeToken();
|
|
ConsumeToken();
|
|
}
|
|
SkipNestedExpr();
|
|
if (CurrentToken() == Token::kCOMMA) {
|
|
ConsumeToken();
|
|
}
|
|
}
|
|
ExpectToken(Token::kRPAREN);
|
|
}
|
|
|
|
|
|
void Parser::SkipCompoundLiteral() {
|
|
if (CurrentToken() == Token::kLT) {
|
|
SkipTypeArguments();
|
|
}
|
|
if ((CurrentToken() == Token::kLBRACK) ||
|
|
(CurrentToken() == Token::kINDEX)) {
|
|
SkipListLiteral();
|
|
} else if (CurrentToken() == Token::kLBRACE) {
|
|
SkipMapLiteral();
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipSymbolLiteral() {
|
|
ConsumeToken(); // Hash sign.
|
|
if (IsIdentifier()) {
|
|
ConsumeToken();
|
|
while (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
ExpectIdentifier("identifier expected");
|
|
}
|
|
} else if (Token::CanBeOverloaded(CurrentToken())) {
|
|
ConsumeToken();
|
|
} else {
|
|
UnexpectedToken();
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipNewOperator() {
|
|
ConsumeToken(); // Skip new or const keyword.
|
|
if (IsIdentifier()) {
|
|
SkipType(false);
|
|
if (CurrentToken() == Token::kLPAREN) {
|
|
SkipActualParameters();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipStringLiteral() {
|
|
ASSERT(CurrentToken() == Token::kSTRING);
|
|
while (CurrentToken() == Token::kSTRING) {
|
|
ConsumeToken();
|
|
while (true) {
|
|
if (CurrentToken() == Token::kINTERPOL_VAR) {
|
|
ConsumeToken();
|
|
} else if (CurrentToken() == Token::kINTERPOL_START) {
|
|
ConsumeToken();
|
|
const bool saved_mode = SetAllowFunctionLiterals(true);
|
|
SkipExpr();
|
|
SetAllowFunctionLiterals(saved_mode);
|
|
ExpectToken(Token::kINTERPOL_END);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipPrimary() {
|
|
if (IsFunctionLiteral()) {
|
|
SkipFunctionLiteral();
|
|
return;
|
|
}
|
|
switch (CurrentToken()) {
|
|
case Token::kTHIS:
|
|
case Token::kSUPER:
|
|
case Token::kNULL:
|
|
case Token::kTRUE:
|
|
case Token::kFALSE:
|
|
case Token::kINTEGER:
|
|
case Token::kDOUBLE:
|
|
ConsumeToken();
|
|
break;
|
|
case Token::kIDENT:
|
|
ConsumeToken();
|
|
break;
|
|
case Token::kSTRING:
|
|
SkipStringLiteral();
|
|
break;
|
|
case Token::kLPAREN:
|
|
ConsumeToken();
|
|
SkipNestedExpr();
|
|
ExpectToken(Token::kRPAREN);
|
|
break;
|
|
case Token::kNEW:
|
|
SkipNewOperator();
|
|
break;
|
|
case Token::kCONST:
|
|
if ((LookaheadToken(1) == Token::kLT) ||
|
|
(LookaheadToken(1) == Token::kLBRACE) ||
|
|
(LookaheadToken(1) == Token::kLBRACK) ||
|
|
(LookaheadToken(1) == Token::kINDEX)) {
|
|
ConsumeToken();
|
|
SkipCompoundLiteral();
|
|
} else {
|
|
SkipNewOperator();
|
|
}
|
|
break;
|
|
case Token::kLT:
|
|
case Token::kLBRACE:
|
|
case Token::kLBRACK:
|
|
case Token::kINDEX:
|
|
SkipCompoundLiteral();
|
|
break;
|
|
case Token::kHASH:
|
|
SkipSymbolLiteral();
|
|
break;
|
|
default:
|
|
if (IsIdentifier()) {
|
|
ConsumeToken(); // Handle pseudo-keyword identifiers.
|
|
} else {
|
|
UnexpectedToken();
|
|
UNREACHABLE();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipSelectors() {
|
|
while (true) {
|
|
const Token::Kind current_token = CurrentToken();
|
|
if (current_token == Token::kCASCADE) {
|
|
ConsumeToken();
|
|
if (CurrentToken() == Token::kLBRACK) {
|
|
continue; // Consume [ in next loop iteration.
|
|
} else {
|
|
ExpectIdentifier("identifier or [ expected after ..");
|
|
}
|
|
} else if (current_token == Token::kPERIOD) {
|
|
ConsumeToken();
|
|
ExpectIdentifier("identifier expected");
|
|
} else if (current_token == Token::kLBRACK) {
|
|
ConsumeToken();
|
|
SkipNestedExpr();
|
|
ExpectToken(Token::kRBRACK);
|
|
} else if (current_token == Token::kLPAREN) {
|
|
SkipActualParameters();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Parser::SkipPostfixExpr() {
|
|
SkipPrimary();
|
|
SkipSelectors();
|
|
if (IsIncrementOperator(CurrentToken())) {
|
|
ConsumeToken();
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipUnaryExpr() {
|
|
if (IsPrefixOperator(CurrentToken()) ||
|
|
IsIncrementOperator(CurrentToken()) ||
|
|
IsAwaitKeyword()) {
|
|
ConsumeToken();
|
|
SkipUnaryExpr();
|
|
} else {
|
|
SkipPostfixExpr();
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipBinaryExpr() {
|
|
SkipUnaryExpr();
|
|
const int min_prec = Token::Precedence(Token::kOR);
|
|
const int max_prec = Token::Precedence(Token::kMUL);
|
|
while (((min_prec <= Token::Precedence(CurrentToken())) &&
|
|
(Token::Precedence(CurrentToken()) <= max_prec))) {
|
|
if (CurrentToken() == Token::kIS) {
|
|
ConsumeToken();
|
|
if (CurrentToken() == Token::kNOT) {
|
|
ConsumeToken();
|
|
}
|
|
SkipType(false);
|
|
} else if (CurrentToken() == Token::kAS) {
|
|
ConsumeToken();
|
|
SkipType(false);
|
|
} else {
|
|
ConsumeToken();
|
|
SkipUnaryExpr();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipConditionalExpr() {
|
|
SkipBinaryExpr();
|
|
if (CurrentToken() == Token::kCONDITIONAL) {
|
|
ConsumeToken();
|
|
SkipExpr();
|
|
ExpectToken(Token::kCOLON);
|
|
SkipExpr();
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipExpr() {
|
|
while (CurrentToken() == Token::kTHROW) {
|
|
ConsumeToken();
|
|
}
|
|
SkipConditionalExpr();
|
|
if (CurrentToken() == Token::kCASCADE) {
|
|
SkipSelectors();
|
|
}
|
|
if (Token::IsAssignmentOperator(CurrentToken())) {
|
|
ConsumeToken();
|
|
SkipExpr();
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::SkipNestedExpr() {
|
|
const bool saved_mode = SetAllowFunctionLiterals(true);
|
|
SkipExpr();
|
|
SetAllowFunctionLiterals(saved_mode);
|
|
}
|
|
|
|
|
|
void Parser::SkipQualIdent() {
|
|
ASSERT(IsIdentifier());
|
|
ConsumeToken();
|
|
if (CurrentToken() == Token::kPERIOD) {
|
|
ConsumeToken(); // Consume the kPERIOD token.
|
|
ExpectIdentifier("identifier expected after '.'");
|
|
}
|
|
}
|
|
|
|
} // namespace dart
|