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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -0
- data/README.md +37 -20
- data/lib/loxxy.rb +23 -2
- data/lib/loxxy/ast/all_lox_nodes.rb +5 -0
- data/lib/loxxy/ast/ast_builder.rb +158 -17
- data/lib/loxxy/ast/ast_visitor.rb +36 -56
- data/lib/loxxy/ast/lox_compound_expr.rb +1 -1
- data/lib/loxxy/ast/lox_node.rb +5 -0
- data/lib/loxxy/ast/lox_noop_expr.rb +16 -0
- data/lib/loxxy/ast/lox_print_stmt.rb +22 -0
- data/lib/loxxy/back_end/engine.rb +48 -0
- data/lib/loxxy/datatype/builtin_datatype.rb +6 -0
- data/lib/loxxy/datatype/lx_string.rb +6 -0
- data/lib/loxxy/datatype/nil.rb +6 -0
- data/lib/loxxy/front_end/grammar.rb +21 -21
- data/lib/loxxy/interpreter.rb +38 -0
- data/lib/loxxy/version.rb +1 -1
- data/spec/back_end/engine_spec.rb +43 -0
- data/spec/datatype/boolean_spec.rb +31 -0
- data/spec/datatype/lx_string_spec.rb +4 -0
- data/spec/datatype/nil_spec.rb +26 -0
- data/spec/datatype/number_spec.rb +31 -0
- data/spec/front_end/parser_spec.rb +223 -27
- data/spec/front_end/scanner_spec.rb +12 -0
- data/spec/interpreter_spec.rb +37 -0
- metadata +17 -2
@@ -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.
|
data/lib/loxxy/ast/lox_node.rb
CHANGED
@@ -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)
|
data/lib/loxxy/datatype/nil.rb
CHANGED
@@ -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('
|
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('
|
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 '
|
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')
|
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
|
data/lib/loxxy/version.rb
CHANGED
@@ -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
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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
|
-
|
123
|
-
expect(
|
124
|
-
(prnt_stmt
|
125
|
-
expect(prnt_stmt).to be_kind_of(
|
126
|
-
expect(prnt_stmt.
|
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
|
130
|
+
context 'Parsing arithmetic operations' do
|
141
131
|
it 'should parse the addition of two number literals' do
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|