Skip to content

Commit

Permalink
Add &+ &- &* &** operators parsing (#6329)
Browse files Browse the repository at this point in the history
* Add &+ &- &* &** operators parsing
* Add lookahead to parse &->expr as &(->expr)
* Allow &+, &- as prefix operator
  • Loading branch information
bcardiff authored Jul 9, 2018
1 parent 4771dcb commit 8d28cbb
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 21 deletions.
2 changes: 1 addition & 1 deletion spec/compiler/codegen/proc_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ describe "Code gen: proc" do
)).to_i.should eq(1)
end

it "passes proc as &-> to method that yields" do
it "passes proc as &->expr to method that yields" do
run(%(
def foo
yield
Expand Down
12 changes: 11 additions & 1 deletion spec/compiler/formatter/formatter_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ describe Crystal::Formatter do
assert_format "with foo yield bar"

assert_format "1 + 2", "1 + 2"
assert_format "1 &+ 2", "1 &+ 2"
assert_format "1 > 2", "1 > 2"
assert_format "1 * 2", "1 * 2"
assert_format "1*2", "1*2"
Expand All @@ -312,14 +313,22 @@ describe Crystal::Formatter do
assert_format "- 1", "-1"
assert_format "~ 1", "~1"
assert_format "+ 1", "+1"
assert_format "&- 1", "&-1"
assert_format "&+ 1", "&+1"
assert_format "a-1", "a - 1"
assert_format "a+1", "a + 1"
assert_format "a&-1", "a &- 1"
assert_format "a&+1", "a &+ 1"
assert_format "1 + \n2", "1 +\n 2"
assert_format "1 + # foo\n2", "1 + # foo\n 2"
assert_format "a = 1 + # foo\n2", "a = 1 + # foo\n 2"
assert_format "1+2*3", "1 + 2*3"
assert_format "1&+2&*3", "1 &+ 2 &* 3"

assert_format "foo(1 + \n2)", "foo(1 +\n 2)"
assert_format "foo(1 &+ \n2)", "foo(1 &+\n 2)"

assert_format "foo(1 &- 2)"

assert_format "foo[]", "foo[]"
assert_format "foo[ 1 , 2 ]", "foo[1, 2]"
Expand Down Expand Up @@ -650,7 +659,8 @@ describe Crystal::Formatter do
assert_format "->( x , y ) { x }", "->(x, y) { x }"
assert_format "->( x : Int32 , y ) { x }", "->(x : Int32, y) { x }"

{:+, :-, :*, :/, :^, :>>, :<<, :|, :&}.each do |sym|
# TODO remove quotes after 0.26.0
{:+, :-, :*, :/, :^, :>>, :<<, :|, :&, :"&+", :"&-", :"&*", :"&**"}.each do |sym|
assert_format ":#{sym}"
end
assert_format ":\"foo bar\""
Expand Down
8 changes: 6 additions & 2 deletions spec/compiler/lexer/lexer_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -249,18 +249,22 @@ describe "Lexer" do
:"|", :"{", :"}", :"?", :":", :"+=", :"-=", :"*=", :"%=", :"&=",
:"|=", :"^=", :"**=", :"<<", :">>", :"%", :"&", :"|", :"^", :"**", :"<<=",
:">>=", :"~", :"[]", :"[]=", :"[", :"]", :"::", :"<=>", :"=>", :"||=",
:"&&=", :"===", :";", :"->", :"[]?", :"{%", :"{{", :"%}", :"@[", :"!~"]
:"&&=", :"===", :";", :"->", :"[]?", :"{%", :"{{", :"%}", :"@[", :"!~",
:"&+", :"&-", :"&*", :"&**", :"&+=", :"&-=", :"&*="]
it_lexes "!@foo", :"!"
it_lexes "+@foo", :"+"
it_lexes "-@foo", :"-"
it_lexes "&+@foo", :"&+"
it_lexes "&-@foo", :"&-"
it_lexes_const "Foo"
it_lexes_instance_var "@foo"
it_lexes_class_var "@@foo"
it_lexes_globals ["$foo", "$FOO", "$_foo", "$foo123"]
it_lexes_symbols [":foo", ":foo!", ":foo?", ":foo=", ":\"foo\"", ":かたな", ":+", ":-", ":*", ":/",
":==", ":<", ":<=", ":>", ":>=", ":!", ":!=", ":=~", ":!~", ":&", ":|",
":^", ":~", ":**", ":>>", ":<<", ":%", ":[]", ":[]?", ":[]=", ":<=>", ":===",
]
":&+", ":&-", ":&*", ":&**"]

it_lexes_global_match_data_index ["$1", "$10", "$1?", "$23?"]

it_lexes "$~", :"$~"
Expand Down
6 changes: 4 additions & 2 deletions spec/compiler/normalize/op_assign_spec.cr
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
require "../../spec_helper"

describe "Normalize: op assign" do
it "normalizes var +=" do
assert_normalize "a = 1; a += 2", "a = 1\na = a + 2"
["+", "-", "*", "&+", "&-", "&*"].each do |op|
it "normalizes var #{op}=" do
assert_normalize "a = 1; a #{op}= 2", "a = 1\na = a #{op} 2"
end
end

it "normalizes var ||=" do
Expand Down
14 changes: 10 additions & 4 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ module Crystal
it_parses "~ 1", Call.new(1.int32, "~")
it_parses "1 && 2", And.new(1.int32, 2.int32)
it_parses "1 || 2", Or.new(1.int32, 2.int32)
it_parses "&- 1", Call.new(1.int32, "&-")
it_parses "&+ 1", Call.new(1.int32, "&+")

it_parses "1 <=> 2", Call.new(1.int32, "<=>", 2.int32)
it_parses "1 !~ 2", Call.new(1.int32, "!~", 2.int32)
Expand Down Expand Up @@ -419,7 +421,7 @@ module Crystal
it_parses "f.x = Foo.new", Call.new("f".call, "x=", [Call.new("Foo".path, "new")] of ASTNode)
it_parses "f.x = - 1", Call.new("f".call, "x=", [Call.new(1.int32, "-")] of ASTNode)

["+", "-", "*", "/", "%", "|", "&", "^", "**", "<<", ">>"].each do |op|
["+", "-", "*", "/", "%", "|", "&", "^", "**", "<<", ">>", "&+", "&-", "&*"].each do |op|
it_parses "f.x #{op}= 2", OpAssign.new(Call.new("f".call, "x"), op, 2.int32)
end

Expand All @@ -430,9 +432,10 @@ module Crystal
it_parses "def %(); end;", Def.new("%")
it_parses "def /(); end;", Def.new("/")

["<<", "<", "<=", "==", ">>", ">", ">=", "+", "-", "*", "/", "%", "|", "&", "^", "**", "===", "=~", "!~"].each do |op|
["<<", "<", "<=", "==", ">>", ">", ">=", "+", "-", "*", "/", "%", "|", "&", "^", "**", "===", "=~", "!~", "&+", "&-", "&*", "&**"].each do |op|
it_parses "1 #{op} 2", Call.new(1.int32, op, 2.int32)
it_parses "n #{op} 2", Call.new("n".call, op, 2.int32)
it_parses "def #{op}(); end", Def.new(op)
end

["bar", "+", "-", "*", "/", "<", "<=", "==", ">", ">=", "%", "|", "&", "^", "**", "===", "=~", "!~"].each do |name|
Expand All @@ -441,7 +444,7 @@ module Crystal
it_parses "foo.#{name}(1, 2)", Call.new("foo".call, name, 1.int32, 2.int32)
end

["+", "-", "*", "/", "%", "|", "&", "^", "**", "<<", ">>"].each do |op|
["+", "-", "*", "/", "%", "|", "&", "^", "**", "<<", ">>", "&+", "&-", "&*"].each do |op|
it_parses "a = 1; a #{op}= 1", [Assign.new("a".var, 1.int32), OpAssign.new("a".var, op, 1.int32)]
it_parses "a = 1; a #{op}=\n1", [Assign.new("a".var, 1.int32), OpAssign.new("a".var, op, 1.int32)]
it_parses "a.b #{op}=\n1", OpAssign.new(Call.new("a".call, "b"), op, 1.int32)
Expand Down Expand Up @@ -642,7 +645,8 @@ module Crystal
assert_syntax_error "#{keyword} ? 1 : 2", "void value expression"
assert_syntax_error "+#{keyword}", "void value expression"

["<<", "<", "<=", "==", ">>", ">", ">=", "+", "-", "*", "/", "%", "|", "&", "^", "**", "==="].each do |op|
["<<", "<", "<=", "==", ">>", ">", ">=", "+", "-", "*", "/", "%", "|",
"&", "^", "**", "===", "&+", "&-", "&*", "&**"].each do |op|
assert_syntax_error "#{keyword} #{op} 1", "void value expression"
end

Expand Down Expand Up @@ -1082,6 +1086,8 @@ module Crystal
it_parses "->foo=", ProcPointer.new(nil, "foo=")
it_parses "foo = 1; ->foo.foo=", [Assign.new("foo".var, 1.int32), ProcPointer.new("foo".var, "foo=")]

it_parses "foo &->bar", Call.new(nil, "foo", block_arg: ProcPointer.new(nil, "bar"))

it_parses "foo.bar = {} of Int32 => Int32", Call.new("foo".call, "bar=", HashLiteral.new(of: HashLiteral::Entry.new("Int32".path, "Int32".path)))

it_parses "alias Foo = Bar", Alias.new("Foo", "Bar".path)
Expand Down
46 changes: 45 additions & 1 deletion src/compiler/crystal/syntax/lexer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,21 @@ module Crystal
symbol ">"
end
when '&'
next_char_and_symbol "&"
case next_char
when '+'
next_char_and_symbol "&+"
when '-'
next_char_and_symbol "&-"
when '*'
case next_char
when '*'
next_char_and_symbol "&**"
else
symbol "&*"
end
else
symbol "&"
end
when '|'
next_char_and_symbol "|"
when '^'
Expand Down Expand Up @@ -574,6 +588,36 @@ module Crystal
end
when '='
next_char :"&="
when '+'
case next_char
when '='
next_char :"&+="
else
@token.type = :"&+"
end
when '-'
# Check if '>' comes after '&-', making it '&->'.
# We want to parse that like '&(->...)',
# so we only return '&' for now.
if peek_next_char == '>'
@token.type = :"&"
else
case next_char
when '='
next_char :"&-="
else
@token.type = :"&-"
end
end
when '*'
case next_char
when '*'
next_char :"&**"
when '='
next_char :"&*="
else
@token.type = :"&*"
end
else
@token.type = :"&"
end
Expand Down
18 changes: 9 additions & 9 deletions src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ module Crystal
atomic.doc = doc
atomic
end
when :"+=", :"-=", :"*=", :"/=", :"%=", :"|=", :"&=", :"^=", :"**=", :"<<=", :">>=", :"||=", :"&&="
when :"+=", :"-=", :"*=", :"/=", :"%=", :"|=", :"&=", :"^=", :"**=", :"<<=", :">>=", :"||=", :"&&=", :"&+=", :"&-=", :"&*="
unexpected_token unless allow_ops

break unless can_be_assigned?(atomic)
Expand Down Expand Up @@ -502,7 +502,7 @@ module Crystal
case @token.type
when :SPACE
next_token
when :"+", :"-"
when :"+", :"-", :"&+", :"&-"
check_void_value left, location

method = @token.type.to_s
Expand Down Expand Up @@ -531,13 +531,13 @@ module Crystal
end
end

parse_operator :mul_or_div, :pow, "Call.new left, method, [right] of ASTNode, name_column_number: method_column_number", ":\"*\", :\"/\", :\"%\""
parse_operator :pow, :prefix, "Call.new left, method, [right] of ASTNode, name_column_number: method_column_number", ":\"**\""
parse_operator :mul_or_div, :pow, "Call.new left, method, [right] of ASTNode, name_column_number: method_column_number", %(:"*", :"/", :"%", :"&*")
parse_operator :pow, :prefix, "Call.new left, method, [right] of ASTNode, name_column_number: method_column_number", %(:"**", :"&**")

def parse_prefix
column_number = @token.column_number
case token_type = @token.type
when :"!", :"+", :"-", :"~"
when :"!", :"+", :"-", :"~", :"&+", :"&-"
location = @token.location
next_token_skip_space_or_newline
check_void_expression_keyword
Expand All @@ -552,7 +552,7 @@ module Crystal
end
end

AtomicWithMethodCheck = [:IDENT, :CONST, :"+", :"-", :"*", :"/", :"%", :"|", :"&", :"^", :"**", :"<<", :"<", :"<=", :"==", :"!=", :"=~", :"!~", :">>", :">", :">=", :"<=>", :"===", :"[]", :"[]=", :"[]?", :"["]
AtomicWithMethodCheck = [:IDENT, :CONST, :"+", :"-", :"*", :"/", :"%", :"|", :"&", :"^", :"**", :"<<", :"<", :"<=", :"==", :"!=", :"=~", :"!~", :">>", :">", :">=", :"<=>", :"===", :"[]", :"[]=", :"[]?", :"[", :"&+", :"&-", :"&*", :"&**"]

def parse_atomic_with_method
location = @token.location
Expand Down Expand Up @@ -666,7 +666,7 @@ module Crystal

atomic = Call.new(atomic, "#{name}=", [arg] of ASTNode, name_column_number: name_column_number).at(location)
next
when :"+=", :"-=", :"*=", :"/=", :"%=", :"|=", :"&=", :"^=", :"**=", :"<<=", :">>=", :"||=", :"&&="
when :"+=", :"-=", :"*=", :"/=", :"%=", :"|=", :"&=", :"^=", :"**=", :"<<=", :">>=", :"||=", :"&&=", :"&+=", :"&-=", :"&*="
method = @token.type.to_s.byte_slice(0, @token.type.to_s.size - 1)
next_token_skip_space_or_newline
value = parse_op_assign
Expand Down Expand Up @@ -3197,8 +3197,8 @@ module Crystal
exp
end

DefOrMacroCheck1 = [:IDENT, :CONST, :"<<", :"<", :"<=", :"==", :"===", :"!=", :"=~", :"!~", :">>", :">", :">=", :"+", :"-", :"*", :"/", :"!", :"~", :"%", :"&", :"|", :"^", :"**", :"[]", :"[]=", :"<=>", :"[]?"]
DefOrMacroCheck2 = [:"<<", :"<", :"<=", :"==", :"===", :"!=", :"=~", :"!~", :">>", :">", :">=", :"+", :"-", :"*", :"/", :"!", :"~", :"%", :"&", :"|", :"^", :"**", :"[]", :"[]?", :"[]=", :"<=>"]
DefOrMacroCheck1 = [:IDENT, :CONST, :"<<", :"<", :"<=", :"==", :"===", :"!=", :"=~", :"!~", :">>", :">", :">=", :"+", :"-", :"*", :"/", :"!", :"~", :"%", :"&", :"|", :"^", :"**", :"[]", :"[]=", :"<=>", :"[]?", :"&+", :"&-", :"&*", :"&**"]
DefOrMacroCheck2 = [:"<<", :"<", :"<=", :"==", :"===", :"!=", :"=~", :"!~", :">>", :">", :">=", :"+", :"-", :"*", :"/", :"!", :"~", :"%", :"&", :"|", :"^", :"**", :"[]", :"[]?", :"[]=", :"<=>", :"&+", :"&-", :"&*", :"&**"]

def parse_def_helper(is_abstract = false)
push_def
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/tools/formatter.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2145,7 +2145,7 @@ module Crystal
write_token :"::" if node.global?

if obj
{:"!", :"+", :"-", :"~"}.each do |op|
{:"!", :"+", :"-", :"~", :"&+", :"&-"}.each do |op|
if node.name == op.to_s && @token.type == op && node.args.empty?
write op
next_token_skip_space_or_newline
Expand Down

0 comments on commit 8d28cbb

Please sign in to comment.