Skip to content
This repository has been archived by the owner on Dec 24, 2023. It is now read-only.

Commit

Permalink
Proper operation parsing
Browse files Browse the repository at this point in the history
- Added proper parsing of binary and unary expressions along
  with operation precedence and correct left/right association
  • Loading branch information
deathbeam committed Apr 28, 2016
1 parent 816ef32 commit 21aad85
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 163 deletions.
4 changes: 2 additions & 2 deletions examples/greet.spoon
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

function log message
if message
print message
print "[INFO] #{@message}"
else
print "It is empty, sorry"
print "[INFO] Empty message"

greet = name -> log "Hello #{name}"

Expand Down
89 changes: 20 additions & 69 deletions lib/spoon/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,7 @@ def initialize
# Matches expression
rule(:expression) {
space.maybe >>
parens(
statement |
operation |
value
) >>
(statement | operation) >>
endline.maybe
}

Expand All @@ -67,66 +63,37 @@ def initialize

# Matches operation
rule(:operation) {
binary_operation |
unary_operation
}

# Matches binary operation
# example: foo + bar
rule(:binary_operation) {
(
value.as(:left) >>
trim((
str("<=") |
str(">=") |
str("!=") |
str("==") |
str("+=") |
str("-=") |
str("*=") |
str("/=") |
str("%=") |
str("and=") |
str("or=") |
key("or") |
key("and") |
key("is") |
key("isnt") |
match['\+\-\*\/%\^><\|&=']
).as(:op)) >>
expression.as(:right)
).as(:binary)
unary_operation | infix_expression(
parens(value) | parens(expression, true),
[trim(str(".")), 2, :left],
[trim(match['\*/%']), 5, :left],
[trim(match['\+\-']), 6, :left],
[trim(str("<<") | str(">>")), 7, :left],
[trim(match['<>'] |str("<=") | str(">=")), 8, :left],
[trim(str("==") | str("!=")), 9, :left],
[trim(str("&")), 10, :left],
[trim(str("^")), 11, :left],
[trim(str("|")), 12, :left],
[trim(str("&&") | key("and")), 13, :left],
[trim(str("||") | key("or")), 14, :left],
[trim(str("+=") | str("-=") | str("*=") | str("/=") |
str("%=") | str("<<=") | str(">>=") | str("&=")|
str("^=") | str("|=") | str("=")), 15, :right]
)
}

# Matches unary operation
# example: !foo
rule(:unary_operation) {
(
(
trim((
str("++") |
str("--") |
key("not") |
match['\+\-!']
).as(:op)) >>
value.as(:right)
) |
(
value.as(:left) >>
trim((
str("++") |
str("--")
).as(:op))
)
).as(:unary)
(trim(str("++") | str("--") | key("not") | match['\+\-!']).as(:o) >> value.as(:r)) |
(value.as(:l) >> trim(str("++") | str("--")).as(:o))
}

# Matches value
rule(:value) {
condition |
closure |
block |
chain |
call |
ret |
name |
Expand Down Expand Up @@ -167,10 +134,6 @@ def initialize
}

rule(:integer) {
(
str('+') |
str('-')
).maybe >>
match("[0-9]").repeat(1)
}

Expand Down Expand Up @@ -204,18 +167,6 @@ def initialize
expression_list.maybe.as(:return)
}

# Matches chain of expressions
# example: abc(a).def(b).efg
rule(:chain) {
repeat(chain_value, trim(str(".")), 1).as(:chain)
}

# Matches chain value
rule(:chain_value) {
call |
name
}

# Matches function call
# example: a(b, c, d, e, f)
rule(:call) {
Expand Down
23 changes: 16 additions & 7 deletions lib/spoon/util/indent_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,25 @@ class IndentParser < Parslet::Parser
def initialize
super
@stack = [0]
@prev_stack = [0]
@current = 0
@last = 0
@matcher = /[ \t]/
end

# Check indentation level at current position and adjust stack
def check_indentation(source)
indent_level = 0
matcher = /[ \t]/
@current = 0

while source.matches?(matcher)
while source.matches?(@matcher)
source.consume(1)
indent_level += 1
@current += 1
end

@last = @stack[@stack.length - 1]
@current = indent_level

if @current > @last
@prev_stack.push @last
@stack.push @current
elsif @current < @last
@stack.pop
Expand All @@ -36,8 +37,16 @@ def check_indentation(source)
# We need to do this, so next samedent won't be skipped
def fix_position(source)
source.bytepos = source.bytepos - @current
@current = @last
AlwaysMatch.new

if @current <= @prev_stack[@prev_stack.length - 1]
while @last != @current && !@prev_stack.empty?
@last = @prev_stack.pop
end

return AlwaysMatch.new if @last == @current
end

NeverMatch.new "Mismatched indentation level"
end

rule (:checkdent) {
Expand Down
64 changes: 6 additions & 58 deletions lib/spoon/util/parser_extensions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ def repeat(value, delimiter, min = 0)
end

# Matches value in parens or not in parens
def parens(value)
(str("(") >> trim(value.maybe) >> str(")") >> endline.maybe) | value
def parens(value, force = false)
if force
str("(") >> trim(value) >> str(")") >> endline.maybe
else
(str("(") >> trim(value.maybe) >> str(")") >> endline.maybe) | value
end
end

# Matches single or multiple end of lines
Expand All @@ -63,62 +67,6 @@ def parens(value)
rule(:endline) {
(space.maybe >> newline).repeat(1) >> checkdent
}

rule(:op_compound_assign) {
str("-=") |
str("+=") |
str("/=") |
str("*=") |
str("%=") |
str("||=") |
str("&&=") |
str(">>>=") |
str("<<=") |
str(">>=") |
str("&=") |
str("^=") |
str("|=")
}

rule(:op_shift) {
str(">>>") |
str(">>") |
str("<<")
}

rule(:op_math) {
str("%") |
str("/") |
str("*") |
str("\\")
}

rule(:op_compare) {
str(">=") |
str("<=") |
str(">") |
str("<") |
str("!=") |
str("==")
}

rule(:op_relation) {
key("of") |
key("in")
}

rule(:op_logic) {
str("||") |
str("&&") |
str("|") |
str("&") |
str("^")
}

rule(:op_unary_math) {
str("!") |
str("~")
}
end
end
end
32 changes: 5 additions & 27 deletions spec/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,6 @@
describe Spoon::Parser do
let(:parser) { Spoon::Parser.new }

context "binary operation" do
subject { parser.binary_operation }

it { should parse "foo + bar" }
it { should parse "foo * bar" }
it { should_not parse "foo + bar + baz" }
end

context "block" do
subject { parser.block }

Expand All @@ -34,13 +26,6 @@
it { should parse "foo(bar, baz)" }
end

context "chain" do
subject { parser.chain }

it { should parse "foo.bar" }
it { should parse "foo().bar().baz(foo, bar).baz" }
end

context "closure" do
subject { parser.closure }

Expand Down Expand Up @@ -71,8 +56,12 @@
context "expression" do
subject { parser.expression }

it { should parse "foo.bar.(baz())" }
it { should parse "foo and bar" }
it { should parse "foo + bar" }
it { should parse "foo * bar" }
it { should parse "++foo" }
it { should parse "foo++" }
it { should_not parse "foo ** bar" }
end

Expand Down Expand Up @@ -101,11 +90,9 @@
subject { parser.number }

it { should parse "10" }
it { should parse "+0" }
it { should parse "-10" }
it { should parse "0" }
it { should parse "1.0" }
it { should parse "0.0" }
it { should parse "-10.0" }
it { should parse "1e10" }
it { should_not parse "a2" }
end
Expand All @@ -115,13 +102,4 @@

it { should parse "# foo\n print bar\n # baz " }
end

context "unary operation" do
subject { parser.unary_operation }

it { should parse "++foo" }
it { should parse "foo++" }
it { should parse "not foo" }
it { should_not parse "foo + bar" }
end
end

0 comments on commit 21aad85

Please sign in to comment.