From 898a70f54156405b596dd80432e87a3c6ca68216 Mon Sep 17 00:00:00 2001 From: Akim Demaille Date: Sun, 22 May 2022 17:58:47 +0200 Subject: [PATCH] add support for let declarations --- lib/parser.y | 31 ++++++++++++++++++++--- lib/recma/nodes/node.rb | 13 ++++++---- lib/recma/nodes/var_decl_node.rb | 9 +++++-- lib/recma/tokenizer.rb | 2 +- lib/recma/visitors/dot_visitor.rb | 3 ++- lib/recma/visitors/ecma_visitor.rb | 12 ++++++--- lib/recma/visitors/evaluation_visitor.rb | 2 +- lib/recma/visitors/function_visitor.rb | 27 ++++++++++---------- lib/recma/visitors/sexp_visitor.rb | 6 ++++- lib/recma/visitors/visitor.rb | 4 +-- test/test_const_statement_node.rb | 13 ++++++++++ test/test_parser.rb | 32 ++++++++++++++++++++++++ test/test_tokenizer.rb | 6 ++--- 13 files changed, 124 insertions(+), 36 deletions(-) diff --git a/lib/parser.y b/lib/parser.y index 0e616e9..8c13b48 100644 --- a/lib/parser.y +++ b/lib/parser.y @@ -7,8 +7,8 @@ token NULL TRUE FALSE /* keywords */ token BREAK CASE CATCH CONST CONTINUE DEBUGGER DEFAULT DELETE DO ELSE ENUM -token FINALLY FOR FUNCTION IF IN INSTANCEOF NEW RETURN SWITCH THIS THROW TRY -token TYPEOF VAR VOID WHILE WITH +token FINALLY FOR FUNCTION IF IN INSTANCEOF LET NEW RETURN SWITCH +token THIS THROW TRY TYPEOF VAR VOID WHILE WITH /* punctuators */ token EQEQ NE /* == and != */ @@ -67,6 +67,7 @@ rule | WithStatement | SwitchStatement | LabelledStatement + | LetStatement | ThrowStatement | TryStatement | DebuggerStatement @@ -543,7 +544,7 @@ rule ; VariableDeclarationNoIn: - IDENT { result = VarDeclNode.new(val[0],nil) } + IDENT { result = VarDeclNode.new(val[0], nil) } | IDENT InitializerNoIn { result = VarDeclNode.new(val[0], val[1]) } ; @@ -571,6 +572,30 @@ rule | IDENT Initializer { result = VarDeclNode.new(val[0], val[1], true) } ; + LetStatement: + LET LetDeclarationList ';' { + result = LetStatementNode.new(val[1]) + debug(result) + } + | LET LetDeclarationList error { + result = LetStatementNode.new(val[1]) + debug(result) + yyerror unless allow_auto_semi?(val.last) + } + ; + + LetDeclarationList: + LetDeclaration { result = val } + | LetDeclarationList ',' LetDeclaration { + result = [val.first, val.last].flatten + } + ; + + LetDeclaration: + IDENT { result = VarDeclNode.new(val[0], nil, :let) } + | IDENT Initializer { result = VarDeclNode.new(val[0], val[1], :let) } + ; + Initializer: '=' AssignmentExpr { result = AssignExprNode.new(val[1]) } ; diff --git a/lib/recma/nodes/node.rb b/lib/recma/nodes/node.rb index c75cd6e..daa8277 100644 --- a/lib/recma/nodes/node.rb +++ b/lib/recma/nodes/node.rb @@ -77,11 +77,14 @@ def to_real_sexp end end - %w[EmptyStatement Parenthetical ExpressionStatement True Delete Return TypeOf - SourceElements Number LogicalNot AssignExpr FunctionBody - ObjectLiteral UnaryMinus Throw This BitwiseNot Element String - Array CaseBlock Null Break Parameter Block False Void Regexp - Arguments Attr Continue ConstStatement UnaryPlus VarStatement].each do |node| + %w[ + EmptyStatement Parenthetical ExpressionStatement True Delete + Return TypeOf SourceElements Number LogicalNot AssignExpr + FunctionBody ObjectLiteral UnaryMinus Throw This BitwiseNot + Element String Array CaseBlock Null Break Parameter Block False + Void Regexp Arguments Attr Continue ConstStatement LetStatement + UnaryPlus VarStatement + ].each do |node| eval "class #{node}Node < Node; end" end end diff --git a/lib/recma/nodes/var_decl_node.rb b/lib/recma/nodes/var_decl_node.rb index af1ee54..89b524e 100644 --- a/lib/recma/nodes/var_decl_node.rb +++ b/lib/recma/nodes/var_decl_node.rb @@ -2,14 +2,19 @@ module RECMA module Nodes class VarDeclNode < Node attr_accessor :name, :type + # constant can be true (for const), false (for var), or :let (for let). def initialize(name, value, constant = false) super(value) @name = name @constant = constant end - def constant?; @constant; end - def variable?; !@constant; end + def const?; @constant == true; end + def let?; @constant == :let; end + def var?; @constant == false; end + + alias constant? const? + alias variable? var? end end end diff --git a/lib/recma/tokenizer.rb b/lib/recma/tokenizer.rb index 764a22f..0b8a4ef 100644 --- a/lib/recma/tokenizer.rb +++ b/lib/recma/tokenizer.rb @@ -8,7 +8,7 @@ class Tokenizer if in instanceof new return switch this throw try typeof var void while with - const true false null debugger + const true false let null debugger } RESERVED = %w{ diff --git a/lib/recma/visitors/dot_visitor.rb b/lib/recma/visitors/dot_visitor.rb index 642dec1..8703845 100644 --- a/lib/recma/visitors/dot_visitor.rb +++ b/lib/recma/visitors/dot_visitor.rb @@ -86,7 +86,8 @@ def initialize # Array Value Nodes %w{ ArgumentsNode ArrayNode CaseBlockNode ConstStatementNode - ObjectLiteralNode SourceElementsNode VarStatementNode + LetStatementNode ObjectLiteralNode SourceElementsNode + VarStatementNode }.each do |type| define_method(:"visit_#{type}") do |o| node = Node.new(@node_index += 1, [type]) diff --git a/lib/recma/visitors/ecma_visitor.rb b/lib/recma/visitors/ecma_visitor.rb index 9c08769..178b25c 100644 --- a/lib/recma/visitors/ecma_visitor.rb +++ b/lib/recma/visitors/ecma_visitor.rb @@ -13,14 +13,18 @@ def visit_SourceElementsNode(o) o.value.map { |x| "#{indent}#{x.accept(self)}" }.join("\n") end - def visit_VarStatementNode(o) - "var #{o.value.map { |x| x.accept(self) }.join(', ')};" - end - def visit_ConstStatementNode(o) "const #{o.value.map { |x| x.accept(self) }.join(', ')};" end + def visit_LetStatementNode(o) + "let #{o.value.map { |x| x.accept(self) }.join(', ')};" + end + + def visit_VarStatementNode(o) + "var #{o.value.map { |x| x.accept(self) }.join(', ')};" + end + def visit_VarDeclNode(o) "#{o.name}#{o.value ? o.value.accept(self) : nil}" end diff --git a/lib/recma/visitors/evaluation_visitor.rb b/lib/recma/visitors/evaluation_visitor.rb index 83a0cd1..7da9386 100644 --- a/lib/recma/visitors/evaluation_visitor.rb +++ b/lib/recma/visitors/evaluation_visitor.rb @@ -283,7 +283,7 @@ def visit_UnaryMinusNode(o) ForInNode ForNode FunctionExprNode GetterPropertyNode GreaterNode GreaterOrEqualNode InNode InstanceOfNode LabelNode LeftShiftNode LessNode - LessOrEqualNode LogicalAndNode LogicalOrNode + LessOrEqualNode LetStatementNode LogicalAndNode LogicalOrNode NotEqualNode NotStrictEqualNode ObjectLiteralNode OpAndEqualNode OpDivideEqualNode OpLShiftEqualNode OpMinusEqualNode OpModEqualNode diff --git a/lib/recma/visitors/function_visitor.rb b/lib/recma/visitors/function_visitor.rb index 6c12851..e4d915c 100644 --- a/lib/recma/visitors/function_visitor.rb +++ b/lib/recma/visitors/function_visitor.rb @@ -21,22 +21,23 @@ def visit_FunctionDeclNode(o) AddNode ArgumentsNode ArrayNode AssignExprNode BitAndNode BitOrNode BitXOrNode BitwiseNotNode BlockNode BracketAccessorNode BreakNode CaseBlockNode CaseClauseNode CommaNode ConditionalNode - ConstStatementNode ContinueNode DeleteNode DivideNode - DoWhileNode DotAccessorNode ElementNode EmptyStatementNode EqualNode + ConstStatementNode ContinueNode DeleteNode DivideNode DoWhileNode + DotAccessorNode ElementNode EmptyStatementNode EqualNode ExpressionStatementNode FalseNode ForInNode ForNode FunctionBodyNode FunctionExprNode GetterPropertyNode GreaterNode GreaterOrEqualNode IfNode InNode InstanceOfNode LabelNode LeftShiftNode LessNode - LessOrEqualNode LogicalAndNode LogicalNotNode LogicalOrNode ModulusNode - MultiplyNode NewExprNode NotEqualNode NotStrictEqualNode NullNode - NumberNode ObjectLiteralNode OpAndEqualNode OpDivideEqualNode - OpEqualNode OpLShiftEqualNode OpMinusEqualNode OpModEqualNode - OpMultiplyEqualNode OpOrEqualNode OpPlusEqualNode OpRShiftEqualNode - OpURShiftEqualNode OpXOrEqualNode ParameterNode PostfixNode PrefixNode - PropertyNode RegexpNode ResolveNode ReturnNode RightShiftNode - SetterPropertyNode StrictEqualNode StringNode - SubtractNode SwitchNode ThisNode ThrowNode TrueNode TryNode TypeOfNode - UnaryMinusNode UnaryPlusNode UnsignedRightShiftNode VarDeclNode - VarStatementNode VoidNode WhileNode WithNode + LessOrEqualNode LetStatementNode LogicalAndNode LogicalNotNode + LogicalOrNode ModulusNode MultiplyNode NewExprNode NotEqualNode + NotStrictEqualNode NullNode NumberNode ObjectLiteralNode + OpAndEqualNode OpDivideEqualNode OpEqualNode OpLShiftEqualNode + OpMinusEqualNode OpModEqualNode OpMultiplyEqualNode OpOrEqualNode + OpPlusEqualNode OpRShiftEqualNode OpURShiftEqualNode OpXOrEqualNode + ParameterNode PostfixNode PrefixNode PropertyNode RegexpNode + ResolveNode ReturnNode RightShiftNode SetterPropertyNode + StrictEqualNode StringNode SubtractNode SwitchNode ThisNode ThrowNode + TrueNode TryNode TypeOfNode UnaryMinusNode UnaryPlusNode + UnsignedRightShiftNode VarDeclNode VarStatementNode VoidNode WhileNode + WithNode }.each do |type| define_method(:"visit_#{type}") do |o| end diff --git a/lib/recma/visitors/sexp_visitor.rb b/lib/recma/visitors/sexp_visitor.rb index c2fb94f..34b453a 100644 --- a/lib/recma/visitors/sexp_visitor.rb +++ b/lib/recma/visitors/sexp_visitor.rb @@ -14,7 +14,7 @@ def visit_AssignExprNode(o) end def visit_VarDeclNode(o) - [ o.constant? ? :const_decl : :var_decl ] + super(o) + [ o.const? ? :const_decl : o.let? ? :let_decl : :var_decl ] + super(o) end def visit_VarStatementNode(o) @@ -61,6 +61,10 @@ def visit_ConstStatementNode(o) [:const, super] end + def visit_LetStatementNode(o) + [:let, super] + end + def visit_MultiplyNode(o) [:multiply, *super] end diff --git a/lib/recma/visitors/visitor.rb b/lib/recma/visitors/visitor.rb index 15d9d68..adcf7f2 100644 --- a/lib/recma/visitors/visitor.rb +++ b/lib/recma/visitors/visitor.rb @@ -19,8 +19,8 @@ class Visitor While With } ARRAY_VALUE_NODES = %w{ - Arguments Array CaseBlock ConstStatement ObjectLiteral SourceElements - VarStatement + Arguments Array CaseBlock ConstStatement LetStatement ObjectLiteral + SourceElements VarStatement } NAME_VALUE_NODES = %w{ Label Property GetterProperty SetterProperty VarDecl diff --git a/test/test_const_statement_node.rb b/test/test_const_statement_node.rb index 5906ffb..99b6c32 100644 --- a/test/test_const_statement_node.rb +++ b/test/test_const_statement_node.rb @@ -12,3 +12,16 @@ def test_to_sexp ) end end + +class LetStatementNodeTest < NodeTestCase + def test_to_sexp + initializer = AssignExprNode.new(NumberNode.new(10)) + decl = VarDeclNode.new('foo', initializer, :let) + stmt = LetStatementNode.new([decl]) + + assert_sexp( + [:let, [[:let_decl, :foo, [:assign, [:lit, 10]]]]], + stmt + ) + end +end diff --git a/test/test_parser.rb b/test/test_parser.rb index 3993f9b..1806531 100644 --- a/test/test_parser.rb +++ b/test/test_parser.rb @@ -1271,6 +1271,38 @@ def test_const_statement_error ) end + def test_let_statement + assert_sexp( + [[:let, [[:let_decl, :foo, [:assign, [:lit, 10]]]]]], + @parser.parse('let foo = 10;') + ) + end + + def test_let_decl_list + assert_sexp( + [[:let, + [ + [:let_decl, :foo, [:assign, [:lit, 10]]], + [:let_decl, :bar, [:assign, [:lit, 1]]], + ]]], + @parser.parse('let foo = 10, bar = 1;') + ) + end + + def test_let_decl_no_init + assert_sexp( + [[:let, [[:let_decl, :foo, nil]]]], + @parser.parse('let foo;') + ) + end + + def test_let_statement_error + assert_sexp( + [[:let, [[:let_decl, :foo, [:assign, [:lit, 10]]]]]], + @parser.parse('let foo = 10') + ) + end + def test_variable_statement assert_sexp( [[:var, [[:var_decl, :foo, [:assign, [:lit, 10]]]]]], diff --git a/test/test_tokenizer.rb b/test/test_tokenizer.rb index a489867..eae866a 100644 --- a/test/test_tokenizer.rb +++ b/test/test_tokenizer.rb @@ -161,7 +161,7 @@ def assert_tokens(expected, actual) if in instanceof new return switch this throw try typeof var void while with - const true false null debugger + const let true false null debugger }.each do |kw| define_method(:"test_keyword_#{kw}") do tokens = @tokenizer.tokenize(kw) @@ -174,8 +174,8 @@ def assert_tokens(expected, actual) '!=' => :NE, '===' => :STREQ, '!==' => :STRNEQ, - '<=' => :LE, - '>=' => :GE, + '<=' => :LE, + '>=' => :GE, '||' => :OR, '&&' => :AND, '++' => :PLUSPLUS,