From 814ea204930fb0c9dbcb1ef9cfdb276ec01b9b08 Mon Sep 17 00:00:00 2001 From: Sean Billig Date: Wed, 18 May 2022 17:47:37 -0700 Subject: [PATCH] wip --- crates/analyzer/src/traversal/functions.rs | 1 - crates/lowering/src/ast_utils.rs | 8 +- crates/lowering/src/mappers/functions.rs | 2 +- .../snapshots/lowering__module_const.snap | 9 +- crates/mir/src/lower/function.rs | 6 - crates/parser/src/ast.rs | 223 ++++++++++++------ crates/parser/src/grammar/Python.asdl | 130 ---------- crates/parser/src/grammar/SimPy.grammar | 164 ------------- crates/parser/src/grammar/contracts.rs | 35 +-- crates/parser/src/grammar/functions.rs | 31 +-- crates/parser/src/grammar/types.rs | 23 +- crates/parser/src/lexer/token.rs | 5 +- crates/parser/src/parser.rs | 34 +-- .../cases__errors__module_nonsense.snap | 2 +- .../cases__parse_ast__contract_def.snap | 4 +- .../cases__parse_ast__empty_contract_def.snap | 6 +- .../cases__parse_ast__guest_book.snap | 4 +- ...cases__parse_ast__module_level_events.snap | 4 +- .../cases__parse_ast__module_stmts.snap | 6 +- .../cases__parse_ast__pub_contract_def.snap | 6 +- .../snapshots/cases__print_ast__defs.snap | 20 ++ crates/test-files/fixtures/printing/defs.fe | 15 ++ crates/yulgen/src/mappers/functions.rs | 1 - 23 files changed, 242 insertions(+), 497 deletions(-) delete mode 100644 crates/parser/src/grammar/Python.asdl delete mode 100644 crates/parser/src/grammar/SimPy.grammar diff --git a/crates/analyzer/src/traversal/functions.rs b/crates/analyzer/src/traversal/functions.rs index e3769f8580..1a46594b72 100644 --- a/crates/analyzer/src/traversal/functions.rs +++ b/crates/analyzer/src/traversal/functions.rs @@ -33,7 +33,6 @@ fn func_stmt(scope: &mut BlockScope, stmt: &Node) -> Result<(), Fa Unsafe { .. } => unsafe_block(scope, stmt), Assert { .. } => assert(scope, stmt), Expr { value } => expressions::expr(scope, value, None).map(|_| ()), - Pass => Ok(()), Revert { .. } => revert(scope, stmt), Break | Continue => { loop_flow_statement(scope, stmt); diff --git a/crates/lowering/src/ast_utils.rs b/crates/lowering/src/ast_utils.rs index 1d7ff36304..c1316dc3b4 100644 --- a/crates/lowering/src/ast_utils.rs +++ b/crates/lowering/src/ast_utils.rs @@ -105,7 +105,7 @@ where body: map_body(body, map_fn), }, // See comment below for why no catch all should be used here - FuncStmt::Pass | FuncStmt::Break | FuncStmt::Continue => stmt.kind, + FuncStmt::Break | FuncStmt::Continue => stmt.kind, } .into_traceable_node(stmt.original_id); @@ -426,9 +426,7 @@ pub fn inject_before_expression( transformed_body.push(stmt.clone()) } } - FuncStmt::Break | FuncStmt::Continue | FuncStmt::Pass => { - transformed_body.push(stmt.clone()) - } + FuncStmt::Break | FuncStmt::Continue => transformed_body.push(stmt.clone()), } } transformed_body @@ -822,7 +820,6 @@ mod tests { assert_eq!( to_code(&original_body), "if true { - } else { 1 if true else 1 if true else 2 }" @@ -833,7 +830,6 @@ mod tests { assert_eq!( to_code(&body), "if true { - } else { return 1 if true else 1 if true else 2 diff --git a/crates/lowering/src/mappers/functions.rs b/crates/lowering/src/mappers/functions.rs index b3303be097..0747102d1d 100644 --- a/crates/lowering/src/mappers/functions.rs +++ b/crates/lowering/src/mappers/functions.rs @@ -234,7 +234,7 @@ fn func_stmt(context: &mut FnContext, stmt: Node) -> Vec vec![fe::FuncStmt::Expr { value: expressions::expr(context, value), }], - fe::FuncStmt::Pass | fe::FuncStmt::Break | fe::FuncStmt::Continue => vec![stmt.kind], + fe::FuncStmt::Break | fe::FuncStmt::Continue => vec![stmt.kind], fe::FuncStmt::Revert { error } => vec![fe::FuncStmt::Revert { error: error.map(|expr| expressions::expr(context, expr)), }], diff --git a/crates/lowering/tests/snapshots/lowering__module_const.snap b/crates/lowering/tests/snapshots/lowering__module_const.snap index fe4d118120..28df98fef3 100644 --- a/crates/lowering/tests/snapshots/lowering__module_const.snap +++ b/crates/lowering/tests/snapshots/lowering__module_const.snap @@ -45,15 +45,10 @@ contract Foo { let my_array: Array = list_expr_array_u256_2(3, 10) let my_tuple: $tuple_u256_u256_ = $tuple_u256_u256_(item0: 3, item1: 10) let my_bar: Bar = Bar(val: 3) - while 3 > 4 { - - } - for x in list_expr_array_u256_2(3, 10) { - - } + while 3 > 4 {} + for x in list_expr_array_u256_2(3, 10) {} self.table[10] = 3 if 3 == 10 { - } else if 10 == 10 { return 3 * 10 } else if true { diff --git a/crates/mir/src/lower/function.rs b/crates/mir/src/lower/function.rs index 5831b35881..2a7511e6d4 100644 --- a/crates/mir/src/lower/function.rs +++ b/crates/mir/src/lower/function.rs @@ -251,12 +251,6 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { self.lower_expr(value); } - ast::FuncStmt::Pass => { - // TODO: Generate appropriate error message. - let arg = self.make_unit(); - self.builder.revert(arg, stmt.into()); - } - ast::FuncStmt::Break => { let exit = self.scope().loop_exit(&self.scopes); self.builder.jump(exit, stmt.into()) diff --git a/crates/parser/src/ast.rs b/crates/parser/src/ast.rs index 3289aad82c..6f9c2e7092 100644 --- a/crates/parser/src/ast.rs +++ b/crates/parser/src/ast.rs @@ -247,7 +247,6 @@ pub enum FuncStmt { Expr { value: Node, }, - Pass, Break, Continue, Revert { @@ -492,7 +491,7 @@ impl fmt::Display for Pragma { impl fmt::Display for Use { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // XXX pub + // TODO pub use write!(f, "use {}", self.tree.kind) } } @@ -531,37 +530,57 @@ impl fmt::Display for Path { impl fmt::Display for ConstantDecl { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // XXX pub - write!( - f, - "const {}: {} = {}", - self.name.kind, self.typ.kind, self.value.kind - ) + let ConstantDecl { + name, + typ, + value, + pub_qual, + } = self; + if pub_qual.is_some() { + write!(f, "pub ")?; + } + write!(f, "const {}: {} = {}", name.kind, typ.kind, value.kind) } } impl fmt::Display for TypeAlias { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // XXX pub - write!(f, "type {} = {}", self.name.kind, self.typ.kind) + let TypeAlias { + name, + typ, + pub_qual, + } = self; + if pub_qual.is_some() { + write!(f, "pub ")?; + } + write!(f, "type {} = {}", name.kind, typ.kind) } } impl fmt::Display for Contract { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // XXX pub - write!(f, "contract {} {{", self.name.kind)?; + let Contract { + name, + fields, + body, + pub_qual, + } = self; + + if pub_qual.is_some() { + write!(f, "pub ")?; + } + write!(f, "contract {} {{", name.kind)?; - if !self.fields.is_empty() { - for field in &self.fields { + if !fields.is_empty() { + for field in fields { writeln!(f)?; write!(indented(f), "{}", field.kind)?; } writeln!(f)?; } - if !self.body.is_empty() { + if !body.is_empty() { writeln!(f)?; - write!(indented(f), "{}", double_line_joined(&self.body))?; + write!(indented(f), "{}", double_line_joined(&body))?; writeln!(f)?; } write!(f, "}}") @@ -570,19 +589,27 @@ impl fmt::Display for Contract { impl fmt::Display for Struct { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // XXX pub - write!(f, "struct {} ", self.name.kind)?; - if self.fields.is_empty() && self.functions.is_empty() { - write!(f, "{{}}") - } else { - writeln!(f, "{{")?; - write!(indented(f), "{}", node_line_joined(&self.fields))?; - if !self.fields.is_empty() && !self.functions.is_empty() { - write!(f, "\n\n")?; - } - writeln!(indented(f), "{}", double_line_joined(&self.functions))?; - write!(f, "}}") + let Struct { + name, + fields, + functions, + pub_qual, + } = self; + + if pub_qual.is_some() { + write!(f, "pub ")?; + } + write!(f, "struct {} ", name.kind)?; + write!(f, "{{")?; + write_nodes_line_wrapped(&mut indented(f), &fields)?; + + if !self.fields.is_empty() && !functions.is_empty() { + writeln!(f)?; + } + if !functions.is_empty() { + writeln!(indented(f), "{}", double_line_joined(&functions))?; } + write!(f, "}}") } } @@ -592,7 +619,13 @@ impl fmt::Display for TypeDesc { TypeDesc::Unit => write!(f, "()"), TypeDesc::Base { base } => write!(f, "{}", base), TypeDesc::Path(path) => write!(f, "{}", path), - TypeDesc::Tuple { items } => write!(f, "({})", node_comma_joined(items)), // XXX if 1 item, insert trailing comma + TypeDesc::Tuple { items } => { + if items.len() == 1 { + write!(f, "({},)", items[0].kind) + } else { + write!(f, "({})", node_comma_joined(items)) + } + } TypeDesc::Generic { base, args } => { write!(f, "{}<{}>", base.kind, comma_joined(&args.kind)) } @@ -600,12 +633,21 @@ impl fmt::Display for TypeDesc { } } +impl fmt::Display for GenericParameter { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + GenericParameter::Unbounded(name) => write!(f, "{}", name.kind), + GenericParameter::Bounded { name, bound } => write!(f, "{}: {}", name.kind, bound.kind), + } + } +} + impl fmt::Display for GenericArg { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { GenericArg::TypeDesc(node) => write!(f, "{}", node.kind), GenericArg::Int(node) => write!(f, "{}", node.kind), - GenericArg::ConstExpr(node) => write!(f, "{}", node.kind), // XXX wrap in {} + GenericArg::ConstExpr(node) => write!(f, "{{ {} }}", node.kind), } } } @@ -652,37 +694,34 @@ impl fmt::Display for Node { impl fmt::Display for Function { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if self.is_pub() { + let Function { + pub_, + unsafe_, + name, + generic_params, + args, + return_type, + body, + } = self; + + if pub_.is_some() { write!(f, "pub ")?; } - if self.is_unsafe() { + if unsafe_.is_some() { write!(f, "unsafe ")?; } - // XXX print generic params - write!( - f, - "fn {}({})", - self.name.kind, - node_comma_joined(&self.args) - )?; - if let Some(return_type) = self.return_type.as_ref() { - write!(f, " -> {}", return_type.kind)?; + write!(f, "fn {}", name.kind)?; + if !generic_params.kind.is_empty() { + write!(f, "<{}>", comma_joined(&generic_params.kind))?; } - if self.body.is_empty() - || matches!( - &self.body[..], - &[Node { - kind: FuncStmt::Pass, - .. - }] - ) - { - write!(f, " {{}}") - } else { - writeln!(f, " {{")?; - writeln!(indented(f), "{}", node_line_joined(&self.body))?; - write!(f, "}}") + write!(f, "({})", node_comma_joined(args))?; + + if let Some(return_type) = return_type.as_ref() { + write!(f, " -> {}", return_type.kind)?; } + write!(f, " {{")?; + write_nodes_line_wrapped(&mut indented(f), body)?; + write!(f, "}}") } } @@ -738,13 +777,13 @@ impl fmt::Display for FuncStmt { write!(f, "{} {}= {}", target.kind, op.kind, value.kind) } FuncStmt::For { target, iter, body } => { - writeln!(f, "for {} in {} {{", target.kind, iter.kind)?; - writeln!(indented(f), "{}", node_line_joined(body))?; + write!(f, "for {} in {} {{", target.kind, iter.kind)?; + write_nodes_line_wrapped(&mut indented(f), body)?; write!(f, "}}") } FuncStmt::While { test, body } => { - writeln!(f, "while {} {{", test.kind)?; - writeln!(indented(f), "{}", node_line_joined(body))?; + write!(f, "while {} {{", test.kind)?; + write_nodes_line_wrapped(&mut indented(f), body)?; write!(f, "}}") } FuncStmt::If { @@ -752,23 +791,30 @@ impl fmt::Display for FuncStmt { body, or_else, } => { - writeln!(f, "if {} {{", test.kind)?; - writeln!(indented(f), "{}", node_line_joined(body))?; + write!(f, "if {} {{", test.kind)?; + write_nodes_line_wrapped(&mut indented(f), body)?; + if or_else.is_empty() { write!(f, "}}") - } else if matches!( - &or_else[..], - &[Node { - kind: FuncStmt::If { .. }, - .. - }] - ) { - write!(f, "}} else ")?; - write!(f, "{}", or_else[0].kind) } else { - writeln!(f, "}} else {{")?; - writeln!(indented(f), "{}", node_line_joined(or_else))?; - write!(f, "}}") + if body.is_empty() { + writeln!(f)?; + } + + if matches!( + &or_else[..], + &[Node { + kind: FuncStmt::If { .. }, + .. + }] + ) { + write!(f, "}} else ")?; + write!(f, "{}", or_else[0].kind) + } else { + write!(f, "}} else {{")?; + write_nodes_line_wrapped(&mut indented(f), or_else)?; + write!(f, "}}") + } } } FuncStmt::Assert { test, msg } => { @@ -782,7 +828,6 @@ impl fmt::Display for FuncStmt { write!(f, "emit {}({})", name.kind, node_comma_joined(&args.kind)) } FuncStmt::Expr { value } => write!(f, "{}", value.kind), - FuncStmt::Pass => Ok(()), FuncStmt::Break => write!(f, "break"), FuncStmt::Continue => write!(f, "continue"), FuncStmt::Revert { error } => { @@ -793,8 +838,8 @@ impl fmt::Display for FuncStmt { } } FuncStmt::Unsafe(body) => { - writeln!(f, "unsafe {{")?; - writeln!(indented(f), "{}", node_line_joined(body))?; + write!(f, "unsafe {{")?; + write_nodes_line_wrapped(&mut indented(f), body)?; write!(f, "}}") } } @@ -805,7 +850,13 @@ impl fmt::Display for VarDeclTarget { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { VarDeclTarget::Name(name) => write!(f, "{}", name), - VarDeclTarget::Tuple(elts) => write!(f, "{}", node_comma_joined(elts)), // XXX wrap in parens; trailing comma for one elem + VarDeclTarget::Tuple(elts) => { + if elts.len() == 1 { + write!(f, "({},)", elts[0].kind) + } else { + write!(f, "({})", node_comma_joined(elts)) + } + } } } } @@ -859,7 +910,13 @@ impl fmt::Display for Expr { write!(f, "({})", node_comma_joined(&args.kind)) } Expr::List { elts } => write!(f, "[{}]", node_comma_joined(elts)), - Expr::Tuple { elts } => write!(f, "({})", node_comma_joined(elts)), // XXX trailing comma for 1 elem + Expr::Tuple { elts } => { + if elts.len() == 1 { + write!(f, "({},)", elts[0].kind) + } else { + write!(f, "({})", node_comma_joined(elts)) + } + } Expr::Bool(bool) => write!(f, "{}", bool), Expr::Name(name) => write!(f, "{}", name), Expr::Path(path) => write!(f, "{}", path), @@ -951,6 +1008,16 @@ fn comma_joined(items: &[impl fmt::Display]) -> String { .join(", ") } +fn write_nodes_line_wrapped(f: &mut impl Write, nodes: &[Node]) -> fmt::Result { + if !nodes.is_empty() { + writeln!(f)?; + } + for n in nodes { + writeln!(f, "{}", n.kind)?; + } + Ok(()) +} + fn node_line_joined(nodes: &[Node]) -> String { line_joined(&nodes.iter().map(|node| &node.kind).collect::>()) } diff --git a/crates/parser/src/grammar/Python.asdl b/crates/parser/src/grammar/Python.asdl deleted file mode 100644 index e54b882341..0000000000 --- a/crates/parser/src/grammar/Python.asdl +++ /dev/null @@ -1,130 +0,0 @@ --- ASDL's 7 builtin types are: --- identifier, int, string, bytes, object, singleton, constant --- --- singleton: None, True or False --- constant can be None, whereas None means "no value" for object. - -module Python -{ - mod = Module(stmt* body) - | Interactive(stmt* body) - | Expression(expr body) - - -- not really an actual node but useful in Jython's typesystem. - | Suite(stmt* body) - - stmt = FunctionDef(identifier name, arguments args, - stmt* body, expr* decorator_list, expr? returns) - | AsyncFunctionDef(identifier name, arguments args, - stmt* body, expr* decorator_list, expr? returns) - - | ClassDef(identifier name, - expr* bases, - keyword* keywords, - stmt* body, - expr* decorator_list) - | Return(expr? value) - - | Delete(expr* targets) - | Assign(expr* targets, expr value) - | AugAssign(expr target, operator op, expr value) - -- 'simple' indicates that we annotate simple name without parens - | AnnAssign(expr target, expr annotation, expr? value, int simple) - - -- use 'orelse' because else is a keyword in target languages - | For(expr target, expr iter, stmt* body, stmt* orelse) - | AsyncFor(expr target, expr iter, stmt* body, stmt* orelse) - | While(expr test, stmt* body, stmt* orelse) - | If(expr test, stmt* body, stmt* orelse) - | With(withitem* items, stmt* body) - | AsyncWith(withitem* items, stmt* body) - - | Raise(expr? exc, expr? cause) - | Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody) - | Assert(expr test, expr? msg) - - | Import(alias* names) - | ImportFrom(identifier? module, alias* names, int? level) - - | Global(identifier* names) - | Nonlocal(identifier* names) - | Expr(expr value) - | Pass | Break | Continue - - -- XXX Jython will be different - -- col_offset is the byte offset in the utf8 string the parser uses - attributes (int lineno, int col_offset) - - -- BoolOp() can use left & right? - expr = BoolOp(boolop op, expr* values) - | BinOp(expr left, operator op, expr right) - | UnaryOp(unaryop op, expr operand) - | Lambda(arguments args, expr body) - | IfExp(expr test, expr body, expr orelse) - | Dict(expr* keys, expr* values) - | Set(expr* elts) - | SetComp(expr elt, comprehension* generators) - | DictComp(expr key, expr value, comprehension* generators) - | GeneratorExp(expr elt, comprehension* generators) - -- the grammar constrains where yield expressions can occur - | Await(expr value) - | Yield(expr? value) - | YieldFrom(expr value) - -- need sequences for compare to distinguish between - -- x < 4 < 3 and (x < 4) < 3 - | Compare(expr left, cmpop* ops, expr* comparators) - | Call(expr func, expr* args, keyword* keywords) - | Num(object n) -- a number as a PyObject. - | Str(string s) -- need to specify raw, unicode, etc? - | FormattedValue(expr value, int? conversion, expr? format_spec) - | JoinedStr(expr* values) - | Bytes(bytes s) - | NameConstant(singleton value) - | Ellipsis - | Constant(constant value) - - -- the following expression can appear in assignment context - | Attribute(expr value, identifier attr, expr_context ctx) - | Subscript(expr value, slice slice, expr_context ctx) - | Starred(expr value, expr_context ctx) - | Name(identifier id, expr_context ctx) - | List(expr* elts, expr_context ctx) - | Tuple(expr* elts, expr_context ctx) - - -- col_offset is the byte offset in the utf8 string the parser uses - attributes (int lineno, int col_offset) - - expr_context = Load | Store | Del | AugLoad | AugStore | Param - - slice = Slice(expr? lower, expr? upper, expr? step) - | ExtSlice(slice* dims) - | Index(expr value) - - boolop = And | Or - - operator = Add | Sub | Mult | MatMult | Div | Mod | Pow | LShift - | RShift | BitOr | BitXor | BitAnd | FloorDiv - - unaryop = Invert | Not | UAdd | USub - - cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn - - comprehension = (expr target, expr iter, expr* ifs, int is_async) - - excepthandler = ExceptHandler(expr? type, identifier? name, stmt* body) - attributes (int lineno, int col_offset) - - arguments = (arg* args, arg? vararg, arg* kwonlyargs, expr* kw_defaults, - arg? kwarg, expr* defaults) - - arg = (identifier arg, expr? annotation) - attributes (int lineno, int col_offset) - - -- keyword arguments supplied to call (NULL identifier for **kwargs) - keyword = (identifier? arg, expr value) - - -- import name with optional 'as' alias. - alias = (identifier name, identifier? asname) - - withitem = (expr context_expr, expr? optional_vars) -} diff --git a/crates/parser/src/grammar/SimPy.grammar b/crates/parser/src/grammar/SimPy.grammar deleted file mode 100644 index c0428f8095..0000000000 --- a/crates/parser/src/grammar/SimPy.grammar +++ /dev/null @@ -1,164 +0,0 @@ -# Simplified grammar for Python - -start: [statements] ENDMARKER -statements: statement+ - -statement: compound_stmt | simple_stmt -simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE -# NOTE: assignment MUST precede expression, else the parser will get stuck; -# but it must follow all others, else reserved words will match a simple NAME. -small_stmt: ( return_stmt | import_stmt | 'pass' | raise_stmt | yield_stmt | assert_stmt | del_stmt | global_stmt | nonlocal_stmt - | assignment | expressions - ) -compound_stmt: if_stmt | while_stmt | for_stmt | with_stmt | try_stmt | function_def | class_def - -# NOTE: yield_expression may start with 'yield'; yield_expr must start with 'yield' -assignment: target ':' expression ['=' yield_expression] | (star_targets '=')+ (yield_expr | expressions) | target augassign (yield_expr | expressions) -augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' | '**=' | '//=') - -global_stmt: 'global' NAME (',' NAME)* -nonlocal_stmt: 'nonlocal' NAME (',' NAME)* - -yield_stmt: yield_expr - -assert_stmt: 'assert' expression [',' expression] - -del_stmt: 'del' targets # TODO: exclude *target - -import_stmt: import_name | import_from -import_name: 'import' dotted_as_names -# note below: the ('.' | '...') is necessary because '...' is tokenized as ELLIPSIS -import_from: ('from' (('.' | '...')* !'import' dotted_name | ('.' | '...')+) - 'import' ('*' | '(' import_as_names ')' | import_as_names)) -import_as_name: NAME ['as' NAME] -dotted_as_name: dotted_name ['as' NAME] -import_as_names: import_as_name (',' import_as_name)* [','] -dotted_as_names: dotted_as_name (',' dotted_as_name)* -dotted_name: NAME ('.' NAME)* - -if_stmt: 'if' full_expression ':' block elif_block* [else_block] -elif_block: 'elif' full_expression ':' block -else_block: 'else' ':' block - -while_stmt: 'while' full_expression ':' block [else_block] - -for_stmt: [ASYNC] 'for' star_targets 'in' expressions ':' block [else_block] - -with_stmt: [ASYNC] 'with' expression ['as' target] (',' [expression ['as' target]])* ':' block - -try_stmt: try_block finally_block | try_block except_block+ [else_block] [finally_block] -try_block: 'try' ':' block -except_block: 'except' [expression ['as' target]] ':' block -finally_block: 'finally' ':' block - -return_stmt: 'return' [expressions] - -raise_stmt: 'raise' [expression ['from' expression]] - -function_def: [decorators] [ASYNC] 'def' NAME '(' [parameters] ')' ['->' annotation] ':' block - -parameters: ( slash_without_default [',' plain_names] [',' names_with_default] [',' [star_etc]] - | slash_with_default [',' names_with_default] [',' [star_etc]] - | plain_names [',' names_with_default] [',' [star_etc]] - | names_with_default [',' [star_etc]] - | star_etc - ) -slash_without_default: plain_names ',' '/' -slash_with_default: [plain_names ','] names_with_default ',' '/' -names_with_default: name_with_default (',' name_with_default)* -plain_names: plain_name !'=' (',' plain_name !'=')* -star_etc: ( '*' NAME [':' annotation] (',' plain_name ['=' expression])* [',' kwds] [','] - | '*' (',' plain_name ['=' expression])+ [',' kwds] [','] - | kwds [','] - ) -name_with_default: plain_name '=' expression -plain_name: NAME [':' annotation] -kwds: '**' NAME [':' annotation] -annotation: expression - -decorators: ('@' factor NEWLINE)+ - -class_def: [decorators] 'class' NAME ['(' [arguments] ')'] ':' block - -block: simple_stmt | NEWLINE INDENT statements DEDENT - -star_full_expressions: (star_full_expression) (',' (star_full_expression))* [','] -expressions: ('*' bitwise_or | expression) (',' ('*' bitwise_or | expression))* [','] - -star_full_expression: '*' bitwise_or | full_expression -full_expression: NAME ':=' expression | expression -yield_expression: yield_expr | expression -expression: lambdef | disjunction ['if' disjunction 'else' expression] -lambdef: 'lambda' [lambda_parameters] ':' expression - -lambda_parameters: ( lambda_slash_without_default [',' lambda_plain_names] [',' lambda_names_with_default] [',' [lambda_star_etc]] - | lambda_slash_with_default [',' lambda_names_with_default] [',' [lambda_star_etc]] - | lambda_plain_names [',' lambda_names_with_default] [',' [lambda_star_etc]] - | lambda_names_with_default [',' [lambda_star_etc]] - | lambda_star_etc - ) -lambda_slash_without_default: lambda_plain_names ',' '/' -lambda_slash_with_default: [lambda_plain_names ','] lambda_names_with_default ',' '/' -lambda_names_with_default: lambda_name_with_default (',' lambda_name_with_default)* -lambda_plain_names: NAME !'=' (',' NAME !'=')* -lambda_star_etc: ( '*' NAME (',' NAME ['=' expression])* [',' '**' NAME] [','] - | '*' (',' NAME ['=' expression])+ [',' '**' NAME] [','] - | '**' NAME [','] - ) -lambda_name_with_default: NAME '=' expression - -disjunction: conjunction ('or' conjunction)* -conjunction: comparison ('and' comparison)* -comparison: 'not'* bitwise_or (compare_op bitwise_or)* -compare_op: '<' | '<=' | '==' | '>=' | '>' | '!=' | ['not'] 'in' | 'is' ['not'] - -bitwise_or: bitwise_xor ('|' bitwise_xor)* -bitwise_xor: bitwise_and ('^' bitwise_and)* -bitwise_and: shift_expr ('&' shift_expr)* -shift_expr: sum (('<<'|'>>') sum)* - -sum: term (('+' term | '-' term))* -term: factor (('*' factor | '/' factor | '//' factor | '%' factor | '@' factor))* -factor: ('+' | '-' | '~') factor | power -power: primary '**' factor | primary -primary: [AWAIT] atom ('.' NAME | '[' slices ']' | '(' [arguments] ')')* - -slices: slice (',' slice)* [','] -slice: [expression] ':' [expression] [':' [expression]] | expression -atom: list | listcomp | tuple | group | genexp | set | setcomp | dict | dictcomp | NAME | STRING+ | NUMBER | '...' -list: '[' [star_full_expressions] ']' -listcomp: '[' full_expression for_if_clauses ']' -tuple: '(' [star_full_expression ',' [star_full_expressions]] ')' -group: '(' (yield_expr | full_expression) ')' -genexp: '(' expression for_if_clauses ')' -set: '{' expressions '}' -setcomp: '{' expression for_if_clauses '}' -dict: '{' [kvpairs] '}' -dictcomp: '{' kvpair for_if_clauses '}' -kvpairs: kvpair (',' kvpair)* [','] -kvpair: '**' bitwise_or | expression ':' expression -for_if_clauses: ([ASYNC] 'for' star_targets 'in' expression ('if' expression)*)+ - -yield_expr: 'yield' 'from' expression | 'yield' [expressions] - -arguments: expression for_if_clauses | args [','] -args: '*' expression [',' args] | kwargs | posarg [',' args] # Weird to make it work -kwargs: kwarg (',' kwarg)* -posarg: full_expression | '*' expression -kwarg: NAME '=' expression | '*' expression | '**' expression - -# NOTE: star_targets may contain *NAME, targets may not. -# NOTE: the !'in' is to handle "for x, in ...". -# TODO: things like {k: v}[k] = v should also be acceptable [star] targets. -star_targets: star_target (',' !'in' star_target)* [','] -star_target: '*' bitwise_or | atom t_tail+ | star_atom t_tail* -star_atom: NAME | '(' [star_targets] ')' | '[' [star_targets] ']' - -targets: target (',' target)* [','] -target: atom t_tail+ | t_atom t_tail* -t_atom: NAME | '(' [targets] ')' | '[' [targets] ']' - -t_tail: call_tail* (attr_tail | index_tail) -call_tail: '(' [arguments] ')' -attr_tail: '.' NAME -index_tail: '[' slices ']' diff --git a/crates/parser/src/grammar/contracts.rs b/crates/parser/src/grammar/contracts.rs index c7d8041afc..5841c6c949 100644 --- a/crates/parser/src/grammar/contracts.rs +++ b/crates/parser/src/grammar/contracts.rs @@ -19,28 +19,14 @@ pub fn parse_contract_def( contract_pub_qual: Option, ) -> ParseResult> { let contract_tok = par.assert(TokenKind::Contract); - - // contract Foo: - // x: Map - // pub y: u8 - // const z: u256 = 10 - // - // event Sent: - // idx sender: address - // val: u256 - // - // pub fn foo() -> address: - // return abc - // - let contract_name = par.expect_with_notes( TokenKind::Name, "failed to parse contract definition", |_| vec!["Note: `contract` must be followed by a name, which must start with a letter and contain only letters, numbers, or underscores".into()], )?; - let header_span = contract_tok.span + contract_name.span; - par.enter_block(header_span, "contract definition")?; + let mut span = contract_tok.span + contract_name.span; + par.enter_block(span, "contract definition")?; let mut fields = vec![]; let mut defs = vec![]; @@ -57,15 +43,15 @@ pub fn parse_contract_def( ); } - match par.peek() { - Some(TokenKind::Name) => { + match par.peek_or_err()? { + TokenKind::Name => { let field = parse_field(par, pub_qual, const_qual)?; if !defs.is_empty() { par.error(field.span, "contract field definitions must come before any function or event definitions"); } fields.push(field); } - Some(TokenKind::Fn | TokenKind::Unsafe) => { + TokenKind::Fn | TokenKind::Unsafe => { if let Some(span) = const_qual { par.error( span, @@ -74,7 +60,7 @@ pub fn parse_contract_def( } defs.push(ContractStmt::Function(parse_fn_def(par, pub_qual)?)); } - Some(TokenKind::Event) => { + TokenKind::Event => { if let Some(span) = pub_qual { par.error( span, @@ -89,13 +75,11 @@ pub fn parse_contract_def( } defs.push(ContractStmt::Event(parse_event_def(par, None)?)); } - Some(TokenKind::BraceClose) => { - // XXX span - par.next()?; + TokenKind::BraceClose => { + span += par.next()?.span; break; } - None => break, // XXX error - Some(_) => { + _ => { let tok = par.next()?; par.unexpected_token_error( &tok, @@ -107,7 +91,6 @@ pub fn parse_contract_def( }; } - let span = header_span + contract_pub_qual + fields.last() + defs.last(); Ok(Node::new( Contract { name: Node::new(contract_name.text.into(), contract_name.span), diff --git a/crates/parser/src/grammar/functions.rs b/crates/parser/src/grammar/functions.rs index ec78d14054..ea4da21cb3 100644 --- a/crates/parser/src/grammar/functions.rs +++ b/crates/parser/src/grammar/functions.rs @@ -29,6 +29,8 @@ pub fn parse_fn_def(par: &mut Parser, mut pub_qual: Option) -> ParseResult let fn_tok = par.expect(TokenKind::Fn, "failed to parse function definition")?; let name = par.expect(TokenKind::Name, "failed to parse function definition")?; + let mut span = fn_tok.span + name.span + unsafe_qual + pub_qual; + let generic_params = if par.peek() == Some(TokenKind::Lt) { parse_generic_params(par)? } else { @@ -38,6 +40,7 @@ pub fn parse_fn_def(par: &mut Parser, mut pub_qual: Option) -> ParseResult let args = match par.peek_or_err()? { TokenKind::ParenOpen => { let node = parse_fn_param_list(par)?; + span += node.span; node.kind } TokenKind::BraceOpen | TokenKind::Arrow => { @@ -74,18 +77,17 @@ pub fn parse_fn_def(par: &mut Parser, mut pub_qual: Option) -> ParseResult }; let return_type = if par.peek() == Some(TokenKind::Arrow) { par.next()?; - Some(parse_type_desc(par)?) + let typ = parse_type_desc(par)?; + span += typ.span; + Some(typ) } else { None }; // TODO: allow multi-line return type? `fn f()\n ->\n u8` - // XXX: test single line fn def - par.enter_block(fn_tok.span + name.span, "function definition")?; // XXX bigger span + par.enter_block(span, "function definition")?; let body = parse_block_stmts(par)?; let rbrace = par.expect(TokenKind::BraceClose, "missing `}` in fn definition")?; - // `pub` and `unsafe` are optoinal; we need to add them like this - let span = fn_tok.span + unsafe_qual + pub_qual + rbrace.span; Ok(Node::new( Function { @@ -97,7 +99,7 @@ pub fn parse_fn_def(par: &mut Parser, mut pub_qual: Option) -> ParseResult return_type, body, }, - span, + span + rbrace.span, )) } @@ -253,14 +255,9 @@ fn parse_block_stmts(par: &mut Parser) -> ParseResult>> { let mut body = vec![]; loop { par.eat_newlines(); - match par.peek() { - None => todo!(), // XXX eof error - Some(TokenKind::BraceClose) => { - break; - } - Some(_) => { - body.push(parse_stmt(par)?); - } + match par.peek_or_err()? { + TokenKind::BraceClose => break, + _ => body.push(parse_stmt(par)?), } } Ok(body) @@ -306,7 +303,7 @@ pub fn parse_single_word_stmt(par: &mut Parser) -> ParseResult> { pub fn parse_stmt(par: &mut Parser) -> ParseResult> { use TokenKind::*; - // rule: stmt parsing fns eat the trailing separator (newline, semi, eof) // XXX not true anymore? + // rule: stmt parsing fns eat the trailing separator (newline, semi) match par.peek_or_err()? { For => parse_for_stmt(par), If => parse_if_stmt(par), @@ -486,9 +483,7 @@ fn expr_to_vardecl_target(par: &mut Parser, expr: Node) -> ParseResult ParseResult> { - let if_tok = par.next()?; - assert!(matches!(if_tok.kind, TokenKind::If | TokenKind::Elif)); - + let if_tok = par.assert(TokenKind::If); let test = parse_expr(par)?; par.enter_block(if_tok.span + test.span, "`if` statement")?; let body = parse_block_stmts(par)?; diff --git a/crates/parser/src/grammar/types.rs b/crates/parser/src/grammar/types.rs index 1d0cb85e89..3353df8245 100644 --- a/crates/parser/src/grammar/types.rs +++ b/crates/parser/src/grammar/types.rs @@ -28,8 +28,8 @@ pub fn parse_struct_def( loop { par.eat_newlines(); let pub_qual = par.optional(TokenKind::Pub).map(|tok| tok.span); - match par.peek() { - Some(TokenKind::Name) => { + match par.peek_or_err()? { + TokenKind::Name => { let field = parse_field(par, pub_qual, None)?; if !functions.is_empty() { par.error( @@ -39,17 +39,14 @@ pub fn parse_struct_def( } fields.push(field); } - Some(TokenKind::Fn | TokenKind::Unsafe) => { + TokenKind::Fn | TokenKind::Unsafe => { functions.push(parse_fn_def(par, pub_qual)?); } - Some(TokenKind::BraceClose) => { + TokenKind::BraceClose => { span += par.next()?.span; break; } - None => { - todo!(); // XXX: missing `}` - } - Some(_) => { + _ => { let tok = par.next()?; par.unexpected_token_error(&tok, "failed to parse struct definition", vec![]); return Err(ParseFailed); @@ -102,20 +99,18 @@ pub fn parse_event_def(par: &mut Parser, pub_qual: Option) -> ParseResult< let name = par.expect(Name, "failed to parse event definition")?; par.enter_block(event_tok.span + name.span, "event definition")?; - // XXX require comma separation let mut span = event_tok.span; let mut fields = vec![]; loop { - match par.peek() { - Some(Name | Idx) => { + match par.peek_or_err()? { + Name | Idx => { fields.push(parse_event_field(par)?); } - Some(BraceClose) => { + BraceClose => { span += par.next()?.span; break; } - None => break, // XXX missing `}` error - Some(_) => { + _ => { let tok = par.next()?; par.unexpected_token_error(&tok, "failed to parse event definition", vec![]); return Err(ParseFailed); diff --git a/crates/parser/src/lexer/token.rs b/crates/parser/src/lexer/token.rs index b39aa76886..c5d8d65f3c 100644 --- a/crates/parser/src/lexer/token.rs +++ b/crates/parser/src/lexer/token.rs @@ -69,8 +69,6 @@ pub enum TokenKind { Fn, #[token("const")] Const, - #[token("elif")] // XXX remove - Elif, #[token("else")] Else, #[token("emit")] @@ -227,7 +225,6 @@ impl TokenKind { Fn => "keyword `fn`", Const => "keyword `const`", Let => "keyword `let`", - Elif => "keyword `elif`", Else => "keyword `else`", Emit => "keyword `emit`", Event => "keyword `event`", @@ -292,7 +289,7 @@ impl TokenKind { GtGtEq => "symbol `>>=`", Arrow => "symbol `->`", - Error => unreachable!(), // XXX this is reachable + Error => unreachable!(), // TODO this is reachable } } } diff --git a/crates/parser/src/parser.rs b/crates/parser/src/parser.rs index 0727468a6b..74930859da 100644 --- a/crates/parser/src/parser.rs +++ b/crates/parser/src/parser.rs @@ -1,13 +1,9 @@ -#![allow(unused_variables, dead_code, unused_imports)] // XXX - pub use fe_common::diagnostics::Label; use fe_common::diagnostics::{Diagnostic, Severity}; use fe_common::files::SourceFileId; -use crate::ast::Module; use crate::lexer::{Lexer, Token, TokenKind}; use crate::node::Span; -use smol_str::SmolStr; use std::{error, fmt}; #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] @@ -78,7 +74,7 @@ impl<'a> Parser<'a> { todo!() } } else { - self.error(tok.span, "Unmatched right parenthesis"); // XXX + self.error(tok.span, format!("Unmatched `{}`", tok.text)); } } Ok(tok) @@ -266,16 +262,11 @@ impl<'a> Parser<'a> { ) { self.fancy_error( message, - vec![Label::primary( - tok.span, - "unexpected token", - // XXX format!("unexpected token: {}", tok.kind.describe()), - )], + vec![Label::primary(tok.span, "unexpected token")], notes, ); } - // XXX update comment /// Enter a "block", which is a brace-enclosed list of statements, /// separated by newlines and/or semicolons. /// This checks for and consumes the `{` that precedes the block. @@ -294,18 +285,12 @@ impl<'a> Parser<'a> { )], vec![], ); - Err(ParseFailed) // XXX shouldn't be fatal? + Err(ParseFailed) } } - /// XXX update comment - /// Expect and consume one or more newlines, without returning them. - /// Returns an error if the next token isn't a newline or the end of the - /// file, or if the indent of the next non-empty line doesn't match the - /// current indentation. - /// - /// # Panics - /// Panics if called while the parser is inside a set of parentheses. + /// Consumes newlines and semicolons. Returns Ok if one or more newlines or semicolons + /// are consumed, or if the next token is a `}`. pub fn expect_stmt_end(&mut self, context_name: &str) -> ParseResult<()> { let mut newline = false; while matches!(self.peek_raw(), Some(TokenKind::Newline | TokenKind::Semi)) { @@ -316,7 +301,6 @@ impl<'a> Parser<'a> { return Ok(()); } match self.peek_raw() { - None => Ok(()), // XXX unexpected eof Some(TokenKind::BraceClose) => Ok(()), Some(_) => { let tok = self.next()?; @@ -330,6 +314,7 @@ impl<'a> Parser<'a> { ); Err(ParseFailed) } + None => Ok(()), // unexpect eof error will be generated be parent block } } @@ -417,22 +402,21 @@ impl<'a, 'b> std::ops::DerefMut for BTParser<'a, 'b> { #[derive(Clone, Debug)] struct Enclosure { token_kind: TokenKind, - token_span: Span, + _token_span: Span, // TODO: mark mismatched tokens is_block: bool, - // context_name: SmolStr, } impl Enclosure { pub fn block(token_span: Span) -> Self { Self { token_kind: TokenKind::BraceOpen, - token_span, + _token_span: token_span, is_block: true, } } pub fn non_block(token_kind: TokenKind, token_span: Span) -> Self { Self { token_kind, - token_span, + _token_span: token_span, is_block: false, } } diff --git a/crates/parser/tests/cases/snapshots/cases__errors__module_nonsense.snap b/crates/parser/tests/cases/snapshots/cases__errors__module_nonsense.snap index ea60ad7d71..1ac7b8ae48 100644 --- a/crates/parser/tests/cases/snapshots/cases__errors__module_nonsense.snap +++ b/crates/parser/tests/cases/snapshots/cases__errors__module_nonsense.snap @@ -3,7 +3,7 @@ source: crates/parser/tests/cases/errors.rs expression: "err_string(stringify!(module_nonsense), module::parse_module, \"))\")" --- -error: Unmatched right parenthesis +error: Unmatched `)` ┌─ module_nonsense:1:1 │ 1 │ )) diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__contract_def.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__contract_def.snap index 55d76f49b3..9155669950 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__contract_def.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__contract_def.snap @@ -233,13 +233,13 @@ Node( ), span: Span( start: 0, - end: 153, + end: 155, ), )), ], ), span: Span( start: 0, - end: 153, + end: 155, ), ) diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__empty_contract_def.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__empty_contract_def.snap index a481bda481..7c064f97da 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__empty_contract_def.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__empty_contract_def.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(empty_contract_def), module::parse_module,\n r#\"contract Foo:\n pass\n\"#)" +expression: "ast_string(stringify!(empty_contract_def), try_parse_module,\n \"contract Foo {}\")" --- Node( @@ -21,13 +21,13 @@ Node( ), span: Span( start: 0, - end: 12, + end: 15, ), )), ], ), span: Span( start: 0, - end: 12, + end: 15, ), ) diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__guest_book.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__guest_book.snap index 454f563135..268c529205 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__guest_book.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__guest_book.snap @@ -473,13 +473,13 @@ Node( ), span: Span( start: 35, - end: 387, + end: 389, ), )), ], ), span: Span( start: 0, - end: 387, + end: 389, ), ) diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__module_level_events.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__module_level_events.snap index 6e0e4b2f42..90e00d2467 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__module_level_events.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__module_level_events.snap @@ -375,13 +375,13 @@ Node( ), span: Span( start: 113, - end: 259, + end: 261, ), )), ], ), span: Span( start: 0, - end: 259, + end: 261, ), ) diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__module_stmts.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__module_stmts.snap index e61dc0abc8..95c9accc16 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__module_stmts.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__module_stmts.snap @@ -361,7 +361,7 @@ Node( ), span: Span( start: 173, - end: 207, + end: 214, ), )), Contract(Node( @@ -407,13 +407,13 @@ Node( ), span: Span( start: 216, - end: 241, + end: 243, ), )), ], ), span: Span( start: 0, - end: 241, + end: 243, ), ) diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__pub_contract_def.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__pub_contract_def.snap index 058c24cd1b..21cc0bead1 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__pub_contract_def.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__pub_contract_def.snap @@ -78,14 +78,14 @@ Node( )), ), span: Span( - start: 1, - end: 66, + start: 5, + end: 68, ), )), ], ), span: Span( start: 0, - end: 66, + end: 68, ), ) diff --git a/crates/parser/tests/cases/snapshots/cases__print_ast__defs.snap b/crates/parser/tests/cases/snapshots/cases__print_ast__defs.snap index f68eeaaed5..ebe56f1eee 100644 --- a/crates/parser/tests/cases/snapshots/cases__print_ast__defs.snap +++ b/crates/parser/tests/cases/snapshots/cases__print_ast__defs.snap @@ -35,9 +35,29 @@ contract Foo { pub fn my_func() { std::solidity::bytes2::from_array([1, 2]) + if x { + } else if y { + 2 + 1 + 3 + 4 + } else if z { + } else { + 1 + } + unsafe {} } fn my_other_func() {} + + fn foo() { + while x { + y + } + while y {} + for x in y {} + for x in y { + 5 + } + } } contract Bar { diff --git a/crates/test-files/fixtures/printing/defs.fe b/crates/test-files/fixtures/printing/defs.fe index 3c6f843755..43a4076567 100644 --- a/crates/test-files/fixtures/printing/defs.fe +++ b/crates/test-files/fixtures/printing/defs.fe @@ -30,9 +30,24 @@ contract Foo { pub fn my_func() { std::solidity::bytes2::from_array([1, 2]) + if x { + } else if y { + 2 + 1 + 3 + 4 + } else { + if z {} else { 1 } + } + unsafe {} } fn my_other_func() {} + + fn foo() { + while x { y } + while y {} + for x in y {} + for x in y { 5 } + } } contract Bar { diff --git a/crates/yulgen/src/mappers/functions.rs b/crates/yulgen/src/mappers/functions.rs index dbd41e0af2..ba6f821941 100644 --- a/crates/yulgen/src/mappers/functions.rs +++ b/crates/yulgen/src/mappers/functions.rs @@ -41,7 +41,6 @@ fn func_stmt(context: &mut FnContext, stmt: &Node) -> yul::Stateme } fe::FuncStmt::Assert { .. } => assert(context, stmt), fe::FuncStmt::Expr { .. } => expr(context, stmt), - fe::FuncStmt::Pass => statement! { pop(0) }, fe::FuncStmt::Break => break_statement(context, stmt), fe::FuncStmt::Continue => continue_statement(context, stmt), fe::FuncStmt::Revert { .. } => revert(context, stmt),