diff --git a/lib/parser/ruby-next/AST_FORMAT.md b/lib/parser/ruby-next/AST_FORMAT.md index b097a76ae..dbc5652bf 100644 --- a/lib/parser/ruby-next/AST_FORMAT.md +++ b/lib/parser/ruby-next/AST_FORMAT.md @@ -12,3 +12,30 @@ Format: ^^^ selector ^^^^^^^^^ expression ~~~ + +### "Endless" method + +Format: + +~~~ +(def_e :foo (args) (int 42)) +"def foo() = 42" + ~~~ keyword + ~~~ name + ^ assignment + ~~~~~~~~~~~~~~ expression +~~~ + + +### "Endless" singleton method + +Format: + +~~~ +(defs_e (self) :foo (args) (int 42)) +"def self.foo() = 42" + ~~~ keyword + ~~~ name + ^ assignment + ~~~~~~~~~~~~~~~~~~~ expression +~~~ diff --git a/lib/parser/ruby-next/ast/processor.rb b/lib/parser/ruby-next/ast/processor.rb new file mode 100644 index 000000000..79f99652a --- /dev/null +++ b/lib/parser/ruby-next/ast/processor.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "parser/ast/processor" + +# Processor extensions +module Parser + module AST + class Processor + alias on_def_e on_def + alias on_defs_e on_defs + end + end +end diff --git a/lib/parser/ruby-next/builder.rb b/lib/parser/ruby-next/builder.rb index 9119fda98..3e89eb085 100644 --- a/lib/parser/ruby-next/builder.rb +++ b/lib/parser/ruby-next/builder.rb @@ -10,5 +10,42 @@ def method_ref(receiver, dot_t, selector_t) n(:meth_ref, [ receiver, value(selector_t).to_sym ], send_map(receiver, dot_t, selector_t, nil, [], nil)) end + + def def_endless_method(def_t, name_t, args, + assignment_t, body) + n(:def_e, [ value(name_t).to_sym, args, body ], + endless_definition_map(def_t, nil, name_t, assignment_t, body)) + end + + def def_endless_singleton(def_t, definee, dot_t, + name_t, args, + assignment_t, body) + return unless validate_definee(definee) + + n(:defs_e, [ definee, value(name_t).to_sym, args, body ], + endless_definition_map(def_t, dot_t, name_t, assignment_t, body)) + end + + def endless_definition_map(keyword_t, operator_t, name_t, assignment_t, body_e) + body_l = body_e.loc.expression + + Source::Map::EndlessDefinition.new(loc(keyword_t), + loc(operator_t), loc(name_t), + loc(assignment_t), body_l) + end + + private + + def validate_definee(definee) + case definee.type + when :int, :str, :dstr, :sym, :dsym, + :regexp, :array, :hash + + diagnostic :error, :singleton_literal, nil, definee.loc.expression + false + else + true + end + end end end diff --git a/lib/parser/ruby-next/meta.rb b/lib/parser/ruby-next/meta.rb new file mode 100644 index 000000000..8de844689 --- /dev/null +++ b/lib/parser/ruby-next/meta.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "parser/meta" + +module Parser + # Parser metadata + module Meta + NEXT_NODE_TYPES = (NODE_TYPES + %i[meth_ref def_e defs_e]).freeze + + remove_const(:NODE_TYPES) + const_set(:NODE_TYPES, NEXT_NODE_TYPES) + end +end diff --git a/lib/parser/ruby-next/parser_ext.rb b/lib/parser/ruby-next/parser_ext.rb new file mode 100644 index 000000000..5bd818035 --- /dev/null +++ b/lib/parser/ruby-next/parser_ext.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require_relative "lexer" +require_relative "builder" +require_relative "source/map/endless_definition" +require_relative "ast/processor" + +module Parser + # Patch the base parser class to use custom builder and lexer + module NextExt + def initialize(*) + super + + # Extend builder + @builder.singleton_class.prepend(Builders::Next) + + # Use custom lexer + @lexer = Lexer::Next.new(version) + @lexer.diagnostics = @diagnostics + @lexer.static_env = @static_env + @lexer.context = @context + + # Reset the state again + reset + end + end +end diff --git a/lib/parser/ruby-next/source/map/endless_definition.rb b/lib/parser/ruby-next/source/map/endless_definition.rb new file mode 100644 index 000000000..cd40752e6 --- /dev/null +++ b/lib/parser/ruby-next/source/map/endless_definition.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Parser + module Source + + class Map::EndlessDefinition < Map + attr_reader :keyword + attr_reader :operator + attr_reader :name + attr_reader :assignment + + def initialize(keyword_l, operator_l, name_l, assignment_l, body_l) + @keyword = keyword_l + @operator = operator_l + @name = name_l + @assignment = assignment_l + + super(@keyword.join(body_l)) + end + end + + end +end diff --git a/lib/parser/rubynext.y b/lib/parser/rubynext.y index dff10806e..40504c6be 100644 --- a/lib/parser/rubynext.y +++ b/lib/parser/rubynext.y @@ -310,6 +310,35 @@ rule result = [ val[1], val[2] ] } + def_name: fname + { + @static_env.extend_static + @lexer.cmdarg.push(false) + @lexer.cond.push(false) + @current_arg_stack.push(nil) + + result = val[0] + } + + defn_head: kDEF def_name + { + @context.push(:def) + + result = [ val[0], val[1] ] + } + + defs_head: kDEF singleton dot_or_colon + { + @lexer.state = :expr_fname + } + def_name + { + @context.push(:defs) + + result = [ val[0], val[1], val[2], val[4] ] + } + + command_call: command | block_command @@ -817,6 +846,28 @@ rule result = @builder.ternary(val[0], val[1], val[2], val[4], val[5]) } + | defn_head f_paren_args tEQL arg + { + result = @builder.def_endless_method(*val[0], + val[1], val[2], val[3]) + + @lexer.cmdarg.pop + @lexer.cond.pop + @static_env.unextend + @context.pop + @current_arg_stack.pop + } + | defs_head f_paren_args tEQL arg + { + result = @builder.def_endless_singleton(*val[0], + val[1], val[2], val[3]) + + @lexer.cmdarg.pop + @lexer.cond.pop + @static_env.unextend + @context.pop + @current_arg_stack.pop + } | primary relop: tGT | tLT | tGEQ | tLEQ @@ -1230,18 +1281,10 @@ rule @static_env.unextend @context.pop } - | kDEF fname + | defn_head f_arglist bodystmt kEND { - @static_env.extend_static - @lexer.cmdarg.push(false) - @lexer.cond.push(false) - @context.push(:def) - @current_arg_stack.push(nil) - } - f_arglist bodystmt kEND - { - result = @builder.def_method(val[0], val[1], - val[3], val[4], val[5]) + result = @builder.def_method(*val[0], val[1], + val[2], val[3]) @lexer.cmdarg.pop @lexer.cond.pop @@ -1249,22 +1292,10 @@ rule @context.pop @current_arg_stack.pop } - | kDEF singleton dot_or_colon - { - @lexer.state = :expr_fname - } - fname - { - @static_env.extend_static - @lexer.cmdarg.push(false) - @lexer.cond.push(false) - @context.push(:defs) - @current_arg_stack.push(nil) - } - f_arglist bodystmt kEND + | defs_head f_arglist bodystmt kEND { - result = @builder.def_singleton(val[0], val[1], val[2], - val[4], val[6], val[7], val[8]) + result = @builder.def_singleton(*val[0], val[1], + val[2], val[3]) @lexer.cmdarg.pop @lexer.cond.pop @@ -2528,10 +2559,9 @@ keyword_variable: kNIL result = nil } - f_arglist: tLPAREN2 f_args rparen + f_paren_args: tLPAREN2 f_args rparen { result = @builder.args(val[0], val[1], val[2]) - @lexer.state = :expr_value } | tLPAREN2 args_forward rparen @@ -2541,6 +2571,8 @@ keyword_variable: kNIL @lexer.state = :expr_value } + + f_arglist: f_paren_args | { result = @lexer.in_kwarg @lexer.in_kwarg = true diff --git a/test/ruby-next/test_lexer.rb b/test/ruby-next/test_lexer.rb index 35c0af9b8..01d57cd14 100644 --- a/test/ruby-next/test_lexer.rb +++ b/test/ruby-next/test_lexer.rb @@ -135,4 +135,16 @@ def test_meth_ref_unsupported_newlines :tCOLON, ':', [4, 5], :tPLUS, '+', [6, 7]) end + + def test_endless_method + setup_lexer "next" + + assert_scanned('def foo() = 42', + :kDEF, "def", [0, 3], + :tIDENTIFIER, "foo", [4, 7], + :tLPAREN2, "(", [7, 8], + :tRPAREN, ")", [8, 9], + :tEQL, "=", [10, 11], + :tINTEGER, 42, [12, 14]) + end end diff --git a/test/ruby-next/test_parser.rb b/test/ruby-next/test_parser.rb index 3aea764b9..a24dd5b21 100644 --- a/test/ruby-next/test_parser.rb +++ b/test/ruby-next/test_parser.rb @@ -67,4 +67,93 @@ def test_meth_ref_unsupported_newlines %q{ ^ location}, SINCE_NEXT) end + + def test_endless_method + assert_parses( + s(:def_e, :foo, + s(:args), + s(:int, 42)), + %q{def foo() = 42}, + %q{~~~ keyword + | ~~~ name + | ^ assignment + |~~~~~~~~~~~~~~ expression}, + SINCE_NEXT) + + assert_parses( + s(:def_e, :inc, + s(:args, s(:arg, :x)), + s(:send, + s(:lvar, :x), :+, + s(:int, 1))), + %q{def inc(x) = x + 1}, + %q{~~~ keyword + | ~~~ name + | ^ assignment + |~~~~~~~~~~~~~~~~~~ expression}, + SINCE_NEXT) + + assert_parses( + s(:defs_e, s(:send, nil, :obj), :foo, + s(:args), + s(:int, 42)), + %q{def obj.foo() = 42}, + %q{~~~ keyword + | ^ operator + | ~~~ name + | ^ assignment + |~~~~~~~~~~~~~~~~~~ expression}, + SINCE_NEXT) + + assert_parses( + s(:defs_e, s(:send, nil, :obj), :inc, + s(:args, s(:arg, :x)), + s(:send, + s(:lvar, :x), :+, + s(:int, 1))), + %q{def obj.inc(x) = x + 1}, + %q{~~~ keyword + | ~~~ name + | ^ operator + | ^ assignment + |~~~~~~~~~~~~~~~~~~~~~~ expression}, + SINCE_NEXT) + + assert_parses( + s(:def_e, :foo, + s(:forward_args), + s(:send, nil, :bar, + s(:forwarded_args))), + %q{def foo(...) = bar(...)}, + %q{~~~ keyword + | ~~~ name + | ^ assignment + |~~~~~~~~~~~~~~~~~~~~~~~ expression}, + SINCE_NEXT) + end + + def test_endless_method_without_brackets + assert_diagnoses( + [:error, :unexpected_token, { :token => 'tEQL' }], + %Q{def foo = 42}, + %q{ ^ location}, + SINCE_NEXT) + + assert_diagnoses( + [:error, :unexpected_token, { :token => 'tEQL' }], + %Q{def obj.foo = 42}, + %q{ ^ location}, + SINCE_NEXT) + end + + def test_method_brackets_expression_bug + assert_parses( + s(:def, :foo, + s(:args), + s(:array, + s(:int, 42))), + %q{def foo() [42] end}, + %q{}, + SINCE_NEXT) + end end