Skip to content

Commit

Permalink
+ rubynext.y: add endless method
Browse files Browse the repository at this point in the history
  • Loading branch information
palkan committed Nov 17, 2020
1 parent 27d71c7 commit 2b4b1dc
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 28 deletions.
27 changes: 27 additions & 0 deletions lib/parser/ruby-next/AST_FORMAT.md
Expand Up @@ -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
~~~
13 changes: 13 additions & 0 deletions 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
37 changes: 37 additions & 0 deletions lib/parser/ruby-next/builder.rb
Expand Up @@ -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
13 changes: 13 additions & 0 deletions 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
27 changes: 27 additions & 0 deletions 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
23 changes: 23 additions & 0 deletions 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
88 changes: 60 additions & 28 deletions lib/parser/rubynext.y
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1230,41 +1281,21 @@ 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
@static_env.unextend
@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
Expand Down Expand Up @@ -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
Expand All @@ -2541,6 +2571,8 @@ keyword_variable: kNIL
@lexer.state = :expr_value
}
f_arglist: f_paren_args
| {
result = @lexer.in_kwarg
@lexer.in_kwarg = true
Expand Down
12 changes: 12 additions & 0 deletions test/ruby-next/test_lexer.rb
Expand Up @@ -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
89 changes: 89 additions & 0 deletions test/ruby-next/test_parser.rb
Expand Up @@ -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

0 comments on commit 2b4b1dc

Please sign in to comment.