loxxy 0.0.7 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ require_relative 'lox_node'
5
5
  module Loxxy
6
6
  module Ast
7
7
  class LoxCompoundExpr < LoxNode
8
- # @return [Array<Ast::LoxNode>]
8
+ # @return [Array<Ast::LoxNode>] the children nodes (sub-expressions)
9
9
  attr_reader :subnodes
10
10
 
11
11
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
@@ -11,6 +11,11 @@ module Loxxy
11
11
  @position = aPosition
12
12
  end
13
13
 
14
+ # Notification that the parsing was successfully completed
15
+ def done!
16
+ # Default: do nothing ...
17
+ end
18
+
14
19
  # Abstract method.
15
20
  # Part of the 'visitee' role in Visitor design pattern.
16
21
  # @param _visitor [LoxxyTreeVisitor] the visitor
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_node'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxNoopExpr < LoxNode
8
+ # Abstract method.
9
+ # Part of the 'visitee' role in Visitor design pattern.
10
+ # @param _visitor [LoxxyTreeVisitor] the visitor
11
+ def accept(_visitor)
12
+ # Do nothing...
13
+ end
14
+ end # class
15
+ end # module
16
+ end # module
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_compound_expr'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxPrintStmt < LoxCompoundExpr
8
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
9
+ # @param anExpression [Ast::LoxNode] expression to output
10
+ def initialize(aPosition, anExpression)
11
+ super(aPosition, [anExpression])
12
+ end
13
+
14
+ # Abstract method.
15
+ # Part of the 'visitee' role in Visitor design pattern.
16
+ # @param visitor [Ast::ASTVisitor] the visitor
17
+ def accept(visitor)
18
+ visitor.visit_print_stmt(self)
19
+ end
20
+ end # class
21
+ end # module
22
+ end # module
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Load all the classes implementing AST nodes
4
+ require_relative '../ast/all_lox_nodes'
5
+
6
+ module Loxxy
7
+ module BackEnd
8
+ # An instance of this class executes the statements as when they
9
+ # occur during the abstract syntax tree walking.
10
+ # @note WIP: very crude implementation.
11
+ class Engine
12
+ # @return [Hash] A set of configuration options
13
+ attr_reader :config
14
+
15
+ # @return [Array] Data stack used for passing data between statements
16
+ attr_reader :stack
17
+
18
+ # @param theOptions [Hash]
19
+ def initialize(theOptions)
20
+ @config = theOptions
21
+ @ostream = config.include?(:ostream) ? config[:ostream] : $stdout
22
+ @stack = []
23
+ end
24
+
25
+ # Given an abstract syntax parse tree visitor, luanch the visit
26
+ # and execute the visit events in the output stream.
27
+ # @param aVisitor [AST::ASTVisitor]
28
+ def execute(aVisitor)
29
+ aVisitor.subscribe(self)
30
+ aVisitor.start
31
+ aVisitor.unsubscribe(self)
32
+ stack.empty? ? Datatype::Nil.instance : stack.pop
33
+ end
34
+
35
+ # Visit event handling
36
+
37
+ def after_print_stmt(_printStmt)
38
+ tos = stack.pop
39
+ @ostream.print tos.to_str
40
+ end
41
+
42
+ # @param literalExpr [Ast::LoxLiteralExpr]
43
+ def before_literal_expr(literalExpr)
44
+ stack.push(literalExpr.literal)
45
+ end
46
+ end # class
47
+ end # module
48
+ end # module
@@ -14,6 +14,12 @@ module Loxxy
14
14
  @value = validated_value(aValue)
15
15
  end
16
16
 
17
+ # Method called from Lox to obtain the text representation of the boolean.
18
+ # @return [String]
19
+ def to_str
20
+ value.to_s # Default implementation...
21
+ end
22
+
17
23
  protected
18
24
 
19
25
  def validated_value(aValue)
@@ -21,6 +21,12 @@ module Loxxy
21
21
  end
22
22
  end
23
23
 
24
+ # Method called from Lox to obtain the text representation of the object.
25
+ # @return [String]
26
+ def to_str
27
+ value
28
+ end
29
+
24
30
  protected
25
31
 
26
32
  def validated_value(aValue)
@@ -13,6 +13,12 @@ module Loxxy
13
13
  def initialize
14
14
  super(nil)
15
15
  end
16
+
17
+ # Method called from Lox to obtain the text representation of nil.
18
+ # @return [String]
19
+ def to_str
20
+ 'nil'
21
+ end
16
22
  end # class
17
23
 
18
24
  Nil.instance.freeze # Make the sole instance immutable
@@ -26,8 +26,8 @@ module Loxxy
26
26
  add_terminals('EOF')
27
27
 
28
28
  # Top-level rule that matches an entire Lox program
29
- rule('program' => 'EOF')
30
- rule('program' => 'declaration_plus EOF')
29
+ rule('program' => 'EOF').as 'null_program'
30
+ rule('program' => 'declaration_plus EOF').as 'lox_program'
31
31
 
32
32
  # Declarations: bind an identifier to something
33
33
  rule('declaration_plus' => 'declaration_plus declaration')
@@ -73,7 +73,7 @@ module Loxxy
73
73
  rule('elsePart_opt' => 'ELSE statement')
74
74
  rule('elsePart_opt' => [])
75
75
 
76
- rule('printStmt' => 'PRINT expression SEMICOLON')
76
+ rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
77
77
  rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
78
78
  rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement')
79
79
  rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE')
@@ -88,37 +88,37 @@ module Loxxy
88
88
  rule('owner_opt' => 'call DOT')
89
89
  rule('owner_opt' => [])
90
90
  rule('logic_or' => 'logic_and')
91
- rule('logic_or' => 'logic_and disjunct_plus')
92
- rule('disjunct_plus' => 'disjunct_plus OR logic_and')
93
- rule('disjunct_plus' => 'OR logic_and')
91
+ rule('logic_or' => 'logic_and disjunct_plus').as 'logic_or_plus'
92
+ rule('disjunct_plus' => 'disjunct_plus OR logic_and').as 'logic_or_plus_more'
93
+ rule('disjunct_plus' => 'OR logic_and').as 'logic_or_plus_end'
94
94
  rule('logic_and' => 'equality')
95
- rule('logic_and' => 'equality conjunct_plus')
96
- rule('conjunct_plus' => 'conjunct_plus AND equality')
97
- rule('conjunct_' => 'AND equality')
95
+ rule('logic_and' => 'equality conjunct_plus').as 'logic_and_plus'
96
+ rule('conjunct_plus' => 'conjunct_plus AND equality').as 'logic_and_plus_more'
97
+ rule('conjunct_plus' => 'AND equality').as 'logic_and_plus_end'
98
98
  rule('equality' => 'comparison')
99
- rule('equality' => 'comparison equalityTest_plus')
100
- rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison')
101
- rule('equalityTest_star' => 'equalityTest comparison')
99
+ rule('equality' => 'comparison equalityTest_plus').as 'equality_plus'
100
+ rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison').as 'equality_t_plus_more'
101
+ rule('equalityTest_plus' => 'equalityTest comparison').as 'equality_t_plus_end'
102
102
  rule('equalityTest' => 'BANG_EQUAL')
103
103
  rule('equalityTest' => 'EQUAL_EQUAL')
104
104
  rule('comparison' => 'term')
105
- rule('comparison' => 'term comparisonTest_plus')
106
- rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term')
107
- rule('comparisonTest_plus' => 'comparisonTest term')
105
+ rule('comparison' => 'term comparisonTest_plus').as 'comparison_plus'
106
+ rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
107
+ rule('comparisonTest_plus' => 'comparisonTest term').as 'comparison_t_plus_end'
108
108
  rule('comparisonTest' => 'GREATER')
109
109
  rule('comparisonTest' => 'GREATER_EQUAL')
110
110
  rule('comparisonTest' => 'LESS')
111
111
  rule('comparisonTest' => 'LESS_EQUAL')
112
112
  rule('term' => 'factor')
113
- rule('term' => 'factor additive_plus')
114
- rule('additive_plus' => 'additive_plus additionOp factor').as 'additionOp_expr'
115
- rule('additive_plus' => 'additionOp factor')
113
+ rule('term' => 'factor additive_plus').as 'term_additive'
114
+ rule('additive_plus' => 'additive_plus additionOp factor').as 'additive_plus_more'
115
+ rule('additive_plus' => 'additionOp factor').as 'additive_plus_end'
116
116
  rule('additionOp' => 'MINUS')
117
117
  rule('additionOp' => 'PLUS')
118
118
  rule('factor' => 'unary')
119
- rule('factor' => 'multiplicative_plus')
120
- rule('multiplicative_plus' => 'multiplicative_plus multOp unary') # .as 'multOp_expr'
121
- rule('multiplicative_plus' => 'multOp unary')
119
+ rule('factor' => 'unary multiplicative_plus').as 'factor_multiplicative'
120
+ rule('multiplicative_plus' => 'multiplicative_plus multOp unary').as 'multiplicative_plus_more'
121
+ rule('multiplicative_plus' => 'multOp unary').as 'multiplicative_plus_end'
122
122
  rule('multOp' => 'SLASH')
123
123
  rule('multOp' => 'STAR')
124
124
  rule('unary' => 'unaryOp unary')
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './front_end/parser'
4
+ require_relative './ast/ast_visitor'
5
+ require_relative './back_end/engine'
6
+
7
+ module Loxxy
8
+ # A Lox tree-walking interpreter.
9
+ # It acts as a facade object that:
10
+ # - hides the internal plumbings of the front-end and back-end parts.
11
+ # - delegates all the core work to its subordinate objects.
12
+ # @note WIP: very crude implementation.
13
+ class Interpreter
14
+ # return [Hash]
15
+ attr_reader :config
16
+
17
+ # @param theOptions [Hash]
18
+ def initialize(theOptions = {})
19
+ @config = theOptions
20
+ end
21
+
22
+ # Evaluate the given Lox program
23
+ # @param lox_input [String] Lox program to evaluate
24
+ def evaluate(lox_input)
25
+ # Front-end scans, parses the input and blurps an AST...
26
+ parser = FrontEnd::Parser.new
27
+
28
+ # The AST is the data object passed to the back-end
29
+ ast_tree = parser.parse(lox_input)
30
+ visitor = Ast::ASTVisitor.new(ast_tree)
31
+
32
+ # Back-end launches the tree walking & reponds to visit events
33
+ # by executing the code determined by the visited AST node.
34
+ engine = BackEnd::Engine.new(config)
35
+ engine.execute(visitor)
36
+ end
37
+ end # class
38
+ end # module
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.7'
4
+ VERSION = '0.0.12'
5
5
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require 'stringio'
5
+
6
+ # Load the class under test
7
+ require_relative '../../lib/loxxy/back_end/engine'
8
+
9
+ module Loxxy
10
+ module BackEnd
11
+ describe Engine do
12
+ let(:sample_options) do
13
+ { ostream: StringIO.new }
14
+ end
15
+ subject { Engine.new(sample_options) }
16
+
17
+ context 'Initialization:' do
18
+ it 'should accept a option Hash at initialization' do
19
+ expect { Engine.new(sample_options) }.not_to raise_error
20
+ end
21
+
22
+ it 'should know its config options' do
23
+ expect(subject.config).to eq(sample_options)
24
+ end
25
+
26
+ it 'should have an empty stack' do
27
+ expect(subject.stack).to be_empty
28
+ end
29
+ end
30
+
31
+ context 'Listening to visitor events:' do
32
+ let(:greeting) { Datatype::LXString.new('Hello, world') }
33
+ let(:sample_pos) { double('fake-position') }
34
+ let(:lit_expr) { Ast::LoxLiteralExpr.new(sample_pos, greeting) }
35
+
36
+ it "should react to 'before_literal_expr' event" do
37
+ expect { subject.before_literal_expr(lit_expr) }.not_to raise_error
38
+ expect(subject.stack.pop).to eq(greeting)
39
+ end
40
+ end
41
+ end # describe
42
+ end # module
43
+ end # module
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+
5
+ # Load the class under test
6
+ require_relative '../../lib/loxxy/datatype/boolean'
7
+
8
+ module Loxxy
9
+ module Datatype
10
+ describe Boolean do
11
+ let(:thruth_value) { false }
12
+ subject { Boolean.new(thruth_value) }
13
+
14
+ context 'Initialization:' do
15
+ it 'should accept a boolean value at initialization' do
16
+ expect { Boolean.new(thruth_value) }.not_to raise_error
17
+ end
18
+
19
+ it 'should know its value' do
20
+ expect(subject.value).to eq(thruth_value)
21
+ end
22
+ end
23
+
24
+ context 'Provided services:' do
25
+ it 'should give its display representation' do
26
+ expect(subject.to_str).to eq('false')
27
+ end
28
+ end
29
+ end # describe
30
+ end # module
31
+ end # module
@@ -22,6 +22,10 @@ module Loxxy
22
22
  end
23
23
 
24
24
  context 'Provided services:' do
25
+ it 'should give its display representation' do
26
+ expect(subject.to_str).to eq(sample_text)
27
+ end
28
+
25
29
  it 'compares with another string' do
26
30
  expect(subject).to eq(sample_text)
27
31
  expect(subject).to eq(LXString.new(sample_text.dup))
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+
5
+ # Load the class under test
6
+ require_relative '../../lib/loxxy/datatype/boolean'
7
+
8
+ module Loxxy
9
+ module Datatype
10
+ describe Nil do
11
+ subject { Nil.instance }
12
+
13
+ context 'Initialization:' do
14
+ it 'should know its value' do
15
+ expect(subject.value).to be_nil
16
+ end
17
+ end
18
+
19
+ context 'Provided services:' do
20
+ it 'should give its display representation' do
21
+ expect(subject.to_str).to eq('nil')
22
+ end
23
+ end
24
+ end # describe
25
+ end # module
26
+ end # module
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+
5
+ # Load the class under test
6
+ require_relative '../../lib/loxxy/datatype/number'
7
+
8
+ module Loxxy
9
+ module Datatype
10
+ describe Number do
11
+ let(:sample_value) { -12.34 }
12
+ subject { Number.new(sample_value) }
13
+
14
+ context 'Initialization:' do
15
+ it 'should accept a Numeric value at initialization' do
16
+ expect { Number.new(sample_value) }.not_to raise_error
17
+ end
18
+
19
+ it 'should know its value' do
20
+ expect(subject.value).to eq(sample_value)
21
+ end
22
+ end
23
+
24
+ context 'Provided services:' do
25
+ it 'should give its display representation' do
26
+ expect(subject.to_str).to eq(sample_value.to_s)
27
+ end
28
+ end
29
+ end # describe
30
+ end # module
31
+ end # module
@@ -37,7 +37,7 @@ module Loxxy
37
37
  # Parse results MUST to comply to grammar rule:
38
38
  # program => declaration_star EOF
39
39
  # where the declaration_star MUST be empty
40
- expect(aParseTree.root.symbol.name).to eq('EOF')
40
+ expect(aParseTree.root).to be_kind_of(Ast::LoxNoopExpr)
41
41
  end
42
42
 
43
43
  it 'should cope with an empty input' do
@@ -64,7 +64,7 @@ module Loxxy
64
64
  it 'should parse a false literal' do
65
65
  input = 'false;'
66
66
  ptree = subject.parse(input)
67
- leaf = walk_subnodes(ptree.root, [0, 0])
67
+ leaf = ptree.root.subnodes[0]
68
68
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
69
69
  expect(leaf.literal).to be_equal(Datatype::False.instance)
70
70
  end
@@ -72,7 +72,7 @@ module Loxxy
72
72
  it 'should parse a true literal' do
73
73
  input = 'true;'
74
74
  ptree = subject.parse(input)
75
- leaf = walk_subnodes(ptree.root, [0, 0])
75
+ leaf = ptree.root.subnodes[0]
76
76
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
77
77
  expect(leaf.literal).to be_equal(Datatype::True.instance)
78
78
  end
@@ -81,7 +81,7 @@ module Loxxy
81
81
  inputs = %w[1234; 12.34;]
82
82
  inputs.each do |source|
83
83
  ptree = subject.parse(source)
84
- leaf = walk_subnodes(ptree.root, [0, 0])
84
+ leaf = ptree.root.subnodes[0]
85
85
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
86
86
  expect(leaf.literal).to be_kind_of(Datatype::Number)
87
87
  expect(leaf.literal.value).to eq(source.to_f)
@@ -96,7 +96,7 @@ module Loxxy
96
96
  ]
97
97
  inputs.each do |source|
98
98
  ptree = subject.parse(source)
99
- leaf = walk_subnodes(ptree.root, [0, 0])
99
+ leaf = ptree.root.subnodes[0]
100
100
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
101
101
  expect(leaf.literal).to be_kind_of(Datatype::LXString)
102
102
  expect(leaf.literal.value).to eq(source.gsub(/(^")|(";$)/, ''))
@@ -106,7 +106,7 @@ module Loxxy
106
106
  it 'should parse a nil literal' do
107
107
  input = 'nil;'
108
108
  ptree = subject.parse(input)
109
- leaf = walk_subnodes(ptree.root, [0, 0])
109
+ leaf = ptree.root.subnodes[0]
110
110
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
111
111
  expect(leaf.literal).to be_equal(Datatype::Nil.instance)
112
112
  end
@@ -119,31 +119,227 @@ module Loxxy
119
119
  print "Hello, world!";
120
120
  LOX_END
121
121
  ptree = subject.parse(program)
122
- root = ptree.root
123
- expect(root.symbol.name).to eq('program')
124
- (prnt_stmt, eof) = root.subnodes
125
- expect(prnt_stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
126
- expect(prnt_stmt.symbol.name).to eq('printStmt')
127
- expect(prnt_stmt.subnodes.size).to eq(3)
128
- expect(prnt_stmt.subnodes[0]).to be_kind_of(Rley::PTree::TerminalNode)
129
- expect(prnt_stmt.subnodes[0].symbol.name).to eq('PRINT')
130
- expect(prnt_stmt.subnodes[1]).to be_kind_of(Loxxy::Ast::LoxLiteralExpr)
131
- expect(prnt_stmt.subnodes[1].literal).to be_kind_of(Loxxy::Datatype::LXString)
132
- expect(prnt_stmt.subnodes[1].literal.value).to eq('Hello, world!')
133
- expect(prnt_stmt.subnodes[2]).to be_kind_of(Rley::PTree::TerminalNode)
134
- expect(prnt_stmt.subnodes[2].symbol.name).to eq('SEMICOLON')
135
- expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
136
- expect(eof.symbol.name).to eq('EOF')
122
+ prnt_stmt = ptree.root
123
+ expect(prnt_stmt).to be_kind_of(Ast::LoxPrintStmt)
124
+ expect(prnt_stmt.subnodes[0]).to be_kind_of(Ast::LoxLiteralExpr)
125
+ expect(prnt_stmt.subnodes[0].literal).to be_kind_of(Loxxy::Datatype::LXString)
126
+ expect(prnt_stmt.subnodes[0].literal.value).to eq('Hello, world!')
137
127
  end
138
128
  end # context
139
129
 
140
- context 'Parsing literals:' do
130
+ context 'Parsing arithmetic operations' do
141
131
  it 'should parse the addition of two number literals' do
142
- # input = '123 + 456;'
143
- # ptree = subject.parse(input)
144
- # term = walk_subnodes(ptree.root, [0, 0, 0, 0, 0, 0, 0])
145
- # expect(leaf).to be_kind_of(Ast::LoxBinaryExpr)
146
- # expect(leaf.literal).to be_equal(Datatype::False.instance)
132
+ input = '123 + 456;'
133
+ ptree = subject.parse(input)
134
+ parent = ptree.root
135
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
136
+ expect(parent.symbol.name).to eq('exprStmt')
137
+ expr = parent.subnodes[0]
138
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
139
+ expect(expr.operator).to eq(:+)
140
+ expect(expr.operands[0].literal.value).to eq(123)
141
+ expect(expr.operands[1].literal.value).to eq(456)
142
+ end
143
+
144
+ it 'should parse the subtraction of two number literals' do
145
+ input = '4 - 3;'
146
+ ptree = subject.parse(input)
147
+ parent = ptree.root
148
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
149
+ expect(parent.symbol.name).to eq('exprStmt')
150
+ expr = parent.subnodes[0]
151
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
152
+ expect(expr.operator).to eq(:-)
153
+ expect(expr.operands[0].literal.value).to eq(4)
154
+ expect(expr.operands[1].literal.value).to eq(3)
155
+ end
156
+
157
+ it 'should parse multiple additive operations' do
158
+ input = '5 + 2 - 3;'
159
+ ptree = subject.parse(input)
160
+ parent = ptree.root
161
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
162
+ expect(parent.symbol.name).to eq('exprStmt')
163
+ expr = parent.subnodes[0]
164
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
165
+ expect(expr.operator).to eq(:-)
166
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
167
+ expect(expr.operands[0].operator).to eq(:+)
168
+ expect(expr.operands[0].operands[0].literal.value).to eq(5)
169
+ expect(expr.operands[0].operands[1].literal.value).to eq(2)
170
+ expect(expr.operands[1].literal.value).to eq(3)
171
+ end
172
+
173
+ it 'should parse the division of two number literals' do
174
+ input = '8 / 2;'
175
+ ptree = subject.parse(input)
176
+ parent = ptree.root
177
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
178
+ expect(parent.symbol.name).to eq('exprStmt')
179
+ expr = parent.subnodes[0]
180
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
181
+ expect(expr.operator).to eq(:/)
182
+ expect(expr.operands[0].literal.value).to eq(8)
183
+ expect(expr.operands[1].literal.value).to eq(2)
184
+ end
185
+
186
+ it 'should parse the product of two number literals' do
187
+ input = '12.34 * 0.3;'
188
+ ptree = subject.parse(input)
189
+ parent = ptree.root
190
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
191
+ expect(parent.symbol.name).to eq('exprStmt')
192
+ expr = parent.subnodes[0]
193
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
194
+ expect(expr.operator).to eq(:*)
195
+ expect(expr.operands[0].literal.value).to eq(12.34)
196
+ expect(expr.operands[1].literal.value).to eq(0.3)
197
+ end
198
+
199
+ it 'should parse multiple multiplicative operations' do
200
+ input = '5 * 2 / 3;'
201
+ ptree = subject.parse(input)
202
+ parent = ptree.root
203
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
204
+ expect(parent.symbol.name).to eq('exprStmt')
205
+ expr = parent.subnodes[0]
206
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
207
+ expect(expr.operator).to eq(:/)
208
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
209
+ expect(expr.operands[0].operator).to eq(:*)
210
+ expect(expr.operands[0].operands[0].literal.value).to eq(5)
211
+ expect(expr.operands[0].operands[1].literal.value).to eq(2)
212
+ expect(expr.operands[1].literal.value).to eq(3)
213
+ end
214
+
215
+ it 'should parse combination of terms and factors' do
216
+ input = '5 + 2 / 3;'
217
+ ptree = subject.parse(input)
218
+ parent = ptree.root
219
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
220
+ expect(parent.symbol.name).to eq('exprStmt')
221
+ expr = parent.subnodes[0]
222
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
223
+ expect(expr.operator).to eq(:+)
224
+ expect(expr.operands[0].literal.value).to eq(5)
225
+ expect(expr.operands[1]).to be_kind_of(Ast::LoxBinaryExpr)
226
+ expect(expr.operands[1].operator).to eq(:/)
227
+ expect(expr.operands[1].operands[0].literal.value).to eq(2)
228
+ expect(expr.operands[1].operands[1].literal.value).to eq(3)
229
+ end
230
+ end # context
231
+
232
+ context 'Parsing string concatenation' do
233
+ it 'should parse the concatenation of two string literals' do
234
+ input = '"Lo" + "ve";'
235
+ ptree = subject.parse(input)
236
+ parent = ptree.root
237
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
238
+ expect(parent.symbol.name).to eq('exprStmt')
239
+ expr = parent.subnodes[0]
240
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
241
+ expect(expr.operator).to eq(:+)
242
+ expect(expr.operands[0].literal.value).to eq('Lo')
243
+ expect(expr.operands[1].literal.value).to eq('ve')
244
+ end
245
+ end # context
246
+
247
+ context 'Parsing comparison expressions' do
248
+ it 'should parse the comparison of two number literals' do
249
+ %w[> >= < <=].each do |predicate|
250
+ input = "3 #{predicate} 2;"
251
+ ptree = subject.parse(input)
252
+ parent = ptree.root
253
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
254
+ expect(parent.symbol.name).to eq('exprStmt')
255
+ expr = parent.subnodes[0]
256
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
257
+ expect(expr.operator).to eq(predicate.to_sym)
258
+ expect(expr.operands[0].literal.value).to eq(3)
259
+ expect(expr.operands[1].literal.value).to eq(2)
260
+ end
261
+ end
262
+ end # context
263
+
264
+ context 'Parsing equality expressions' do
265
+ it 'should parse the equality of two number literals' do
266
+ %w[!= ==].each do |predicate|
267
+ input = "3 #{predicate} 2;"
268
+ ptree = subject.parse(input)
269
+ parent = ptree.root
270
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
271
+ expect(parent.symbol.name).to eq('exprStmt')
272
+ expr = parent.subnodes[0]
273
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
274
+ expect(expr.operator).to eq(predicate.to_sym)
275
+ expect(expr.operands[0].literal.value).to eq(3)
276
+ expect(expr.operands[1].literal.value).to eq(2)
277
+ end
278
+ end
279
+
280
+ it 'should parse combination of equality expressions' do
281
+ input = '5 != 2 == false; // A bit contrived example'
282
+ ptree = subject.parse(input)
283
+ parent = ptree.root
284
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
285
+ expect(parent.symbol.name).to eq('exprStmt')
286
+ expr = parent.subnodes[0]
287
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
288
+ expect(expr.operator).to eq(:==)
289
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
290
+ expect(expr.operands[0].operator).to eq(:!=)
291
+ expect(expr.operands[0].operands[0].literal.value).to eq(5)
292
+ expect(expr.operands[0].operands[1].literal.value).to eq(2)
293
+ expect(expr.operands[1].literal.value).to be_falsey
294
+ end
295
+ end # context
296
+
297
+ context 'Parsing logical expressions' do
298
+ it 'should parse the logical operations betweentwo sub-expression' do
299
+ %w[or and].each do |connector|
300
+ input = "5 > 2 #{connector} 3 <= 4;"
301
+ ptree = subject.parse(input)
302
+ parent = ptree.root
303
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
304
+ expect(parent.symbol.name).to eq('exprStmt')
305
+ expr = parent.subnodes[0]
306
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
307
+ expect(expr.operator).to eq(connector.to_sym)
308
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
309
+ expect(expr.operands[0].operator).to eq(:>)
310
+ expect(expr.operands[0].operands[0].literal.value).to eq(5)
311
+ expect(expr.operands[0].operands[1].literal.value).to eq(2)
312
+ expect(expr.operands[1]).to be_kind_of(Ast::LoxBinaryExpr)
313
+ expect(expr.operands[1].operator).to eq(:<=)
314
+ expect(expr.operands[1].operands[0].literal.value).to eq(3)
315
+ expect(expr.operands[1].operands[1].literal.value).to eq(4)
316
+ end
317
+ end
318
+
319
+ it 'should parse a combinations of logical expressions' do
320
+ input = '4 > 3 and 1 < 2 or 4 >= 5;'
321
+ ptree = subject.parse(input)
322
+ parent = ptree.root
323
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
324
+ expect(parent.symbol.name).to eq('exprStmt')
325
+ expr = parent.subnodes[0]
326
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
327
+ expect(expr.operator).to eq(:or) # or has lower precedence than and
328
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
329
+ expect(expr.operands[0].operator).to eq(:and)
330
+ conjuncts = expr.operands[0].operands
331
+ expect(conjuncts[0]).to be_kind_of(Ast::LoxBinaryExpr)
332
+ expect(conjuncts[0].operator).to eq(:>)
333
+ expect(conjuncts[0].operands[0].literal.value).to eq(4)
334
+ expect(conjuncts[0].operands[1].literal.value).to eq(3)
335
+ expect(conjuncts[1]).to be_kind_of(Ast::LoxBinaryExpr)
336
+ expect(conjuncts[1].operator).to eq(:<)
337
+ expect(conjuncts[1].operands[0].literal.value).to eq(1)
338
+ expect(conjuncts[1].operands[1].literal.value).to eq(2)
339
+ expect(expr.operands[1]).to be_kind_of(Ast::LoxBinaryExpr)
340
+ expect(expr.operands[1].operator).to eq(:>=)
341
+ expect(expr.operands[1].operands[0].literal.value).to eq(4)
342
+ expect(expr.operands[1].operands[1].literal.value).to eq(5)
147
343
  end
148
344
  end # context
149
345
  end # describe