loxxy 0.0.3 → 0.0.8

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.
@@ -50,7 +50,7 @@ module Loxxy
50
50
  '>' => 'GREATER',
51
51
  '>=' => 'GREATER_EQUAL',
52
52
  '<' => 'LESS',
53
- '<=' => 'LESS_EQUAL',
53
+ '<=' => 'LESS_EQUAL'
54
54
  }.freeze
55
55
 
56
56
  # Here are all the implemented Lox keywords (in uppercase)
@@ -99,7 +99,7 @@ module Loxxy
99
99
 
100
100
  token = nil
101
101
 
102
- if "(){},.;/*".include? curr_ch
102
+ if '(){},.;/*'.include? curr_ch
103
103
  # Single delimiter or separator character
104
104
  token = build_token(@@lexeme2name[curr_ch], scanner.getch)
105
105
  elsif (lexeme = scanner.scan(/[+\-](?!\d)/))
@@ -150,7 +150,7 @@ module Loxxy
150
150
  when 'FALSE'
151
151
  value = Datatype::False.instance
152
152
  when 'NIL'
153
- value = Datatype::Nil.instance
153
+ value = Datatype::Nil.instance
154
154
  when 'NUMBER'
155
155
  value = Datatype::Number.new(aLexeme)
156
156
  when 'STRING'
@@ -167,8 +167,6 @@ module Loxxy
167
167
  # Skip non-significant whitespaces and comments.
168
168
  # Advance the scanner until something significant is found.
169
169
  def skip_intertoken_spaces
170
- pre_pos = scanner.pos
171
-
172
170
  loop do
173
171
  ws_found = scanner.skip(/[ \t\f]+/) ? true : false
174
172
  nl_found = scanner.skip(/(?:\r\n)|\r|\n/)
@@ -191,7 +189,7 @@ module Loxxy
191
189
  break unless ws_found || cmt_found
192
190
  end
193
191
 
194
- curr_pos = scanner.pos
192
+ scanner.pos
195
193
  end
196
194
 
197
195
  def skip_block_comment
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.3'
4
+ VERSION = '0.0.8'
5
5
  end
@@ -40,8 +40,8 @@ Gem::Specification.new do |spec|
40
40
  spec.version = Loxxy::VERSION
41
41
  spec.authors = ['Dimitri Geshef']
42
42
  spec.email = ['famished.tiger@yahoo.com']
43
- spec.summary = %q{An implementation of the Lox programming language. WIP}
44
- spec.description = %q{An implementation of the Lox programming language. WIP}
43
+ spec.summary = 'An implementation of the Lox programming language. WIP'
44
+ spec.description = 'An implementation of the Lox programming language. WIP'
45
45
  spec.homepage = 'https://github.com/famished-tiger/loxxy'
46
46
  spec.license = 'MIT'
47
47
  spec.required_ruby_version = '~> 2.4'
@@ -0,0 +1,35 @@
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/lx_string'
7
+
8
+ module Loxxy
9
+ module Datatype
10
+ describe LXString do
11
+ let(:sample_text) { 'some_text' }
12
+ subject { LXString.new(sample_text) }
13
+
14
+ context 'Initialization:' do
15
+ it 'should accept a String value at initialization' do
16
+ expect { LXString.new(sample_text) }.not_to raise_error
17
+ end
18
+
19
+ it 'should know its value' do
20
+ expect(subject.value).to eq(sample_text)
21
+ end
22
+ end
23
+
24
+ context 'Provided services:' do
25
+ it 'compares with another string' do
26
+ expect(subject).to eq(sample_text)
27
+ expect(subject).to eq(LXString.new(sample_text.dup))
28
+
29
+ expect(subject).not_to eq('')
30
+ expect(subject).not_to eq('other-text')
31
+ end
32
+ end
33
+ end # describe
34
+ end # module
35
+ end # module
@@ -1,13 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../spec_helper' # Use the RSpec framework
4
- require_relative '../../lib/loxxy/front_end/parser' # Load the class under test
4
+
5
+ # Load the class under test
6
+ require_relative '../../lib/loxxy/front_end/parser'
5
7
 
6
8
  module Loxxy
7
9
  module FrontEnd
8
10
  describe Parser do
9
11
  subject { Parser.new }
10
12
 
13
+ # Utility method to walk towards deeply nested node
14
+ # @param aNTNode [Rley::PTree::NonTerminalNode]
15
+ # @param subnodePath[Array<Integer>] An Array of subnode indices
16
+ def walk_subnodes(aNTNode, subnodePath)
17
+ curr_node = aNTNode
18
+ subnodePath.each do |index|
19
+ curr_node = curr_node.subnodes[index]
20
+ end
21
+
22
+ curr_node
23
+ end
24
+
11
25
  context 'Initialization:' do
12
26
  it 'should be initialized without argument' do
13
27
  expect { Parser.new }.not_to raise_error
@@ -17,19 +31,13 @@ module Loxxy
17
31
  expect(subject.engine).to be_kind_of(Rley::Engine)
18
32
  end
19
33
  end # context
20
-
34
+
21
35
  context 'Parsing blank files:' do
22
36
  def check_empty_input_result(aParseTree)
23
37
  # Parse results MUST to comply to grammar rule:
24
38
  # program => declaration_star EOF
25
39
  # where the declaration_star MUST be empty
26
- expect(aParseTree.root.symbol.name).to eq('program')
27
- (decls, eof) = aParseTree.root.subnodes
28
- expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
29
- expect(decls.symbol.name).to eq('declaration_star')
30
- expect(decls.subnodes).to be_empty
31
- expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
32
- expect(eof.symbol.name).to eq('EOF')
40
+ expect(aParseTree.root.symbol.name).to eq('EOF')
33
41
  end
34
42
 
35
43
  it 'should cope with an empty input' do
@@ -51,6 +59,169 @@ module Loxxy
51
59
  check_empty_input_result(ptree)
52
60
  end
53
61
  end # context
62
+
63
+ context 'Parsing literals:' do
64
+ it 'should parse a false literal' do
65
+ input = 'false;'
66
+ ptree = subject.parse(input)
67
+ leaf = walk_subnodes(ptree.root, [0, 0])
68
+ expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
69
+ expect(leaf.literal).to be_equal(Datatype::False.instance)
70
+ end
71
+
72
+ it 'should parse a true literal' do
73
+ input = 'true;'
74
+ ptree = subject.parse(input)
75
+ leaf = walk_subnodes(ptree.root, [0, 0])
76
+ expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
77
+ expect(leaf.literal).to be_equal(Datatype::True.instance)
78
+ end
79
+
80
+ it 'should parse number literals' do
81
+ inputs = %w[1234; 12.34;]
82
+ inputs.each do |source|
83
+ ptree = subject.parse(source)
84
+ leaf = walk_subnodes(ptree.root, [0, 0])
85
+ expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
86
+ expect(leaf.literal).to be_kind_of(Datatype::Number)
87
+ expect(leaf.literal.value).to eq(source.to_f)
88
+ end
89
+ end
90
+
91
+ it 'should parse string literals' do
92
+ inputs = [
93
+ '"I am a string";',
94
+ '"";',
95
+ '"123";'
96
+ ]
97
+ inputs.each do |source|
98
+ ptree = subject.parse(source)
99
+ leaf = walk_subnodes(ptree.root, [0, 0])
100
+ expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
101
+ expect(leaf.literal).to be_kind_of(Datatype::LXString)
102
+ expect(leaf.literal.value).to eq(source.gsub(/(^")|(";$)/, ''))
103
+ end
104
+ end
105
+
106
+ it 'should parse a nil literal' do
107
+ input = 'nil;'
108
+ ptree = subject.parse(input)
109
+ leaf = walk_subnodes(ptree.root, [0, 0])
110
+ expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
111
+ expect(leaf.literal).to be_equal(Datatype::Nil.instance)
112
+ end
113
+ end
114
+
115
+ context 'Parsing expressions:' do
116
+ it 'should parse a hello world program' do
117
+ program = <<-LOX_END
118
+ // Your first Lox program!
119
+ print "Hello, world!";
120
+ LOX_END
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')
137
+ end
138
+ end # context
139
+
140
+ context 'Parsing arithmetic operations' do
141
+ it 'should parse the addition of two number literals' do
142
+ input = '123 + 456;'
143
+ ptree = subject.parse(input)
144
+ parent = ptree.root.subnodes[0]
145
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
146
+ expect(parent.symbol.name).to eq('exprStmt')
147
+ expr = parent.subnodes[0]
148
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
149
+ expect(expr.operator).to eq(:+)
150
+ expect(expr.operands[0].literal.value).to eq(123)
151
+ expect(expr.operands[1].literal.value).to eq(456)
152
+ end
153
+
154
+ it 'should parse the subtraction of two number literals' do
155
+ input = '4 - 3;'
156
+ ptree = subject.parse(input)
157
+ parent = ptree.root.subnodes[0]
158
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
159
+ expect(parent.symbol.name).to eq('exprStmt')
160
+ expr = parent.subnodes[0]
161
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
162
+ expect(expr.operator).to eq(:-)
163
+ expect(expr.operands[0].literal.value).to eq(4)
164
+ expect(expr.operands[1].literal.value).to eq(3)
165
+ end
166
+
167
+ it 'should parse multiple additive operations' do
168
+ input = '5 + 2 - 3;'
169
+ ptree = subject.parse(input)
170
+ parent = ptree.root.subnodes[0]
171
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
172
+ expect(parent.symbol.name).to eq('exprStmt')
173
+ expr = parent.subnodes[0]
174
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
175
+ expect(expr.operator).to eq(:-)
176
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
177
+ expect(expr.operands[0].operator).to eq(:+)
178
+ expect(expr.operands[0].operands[0].literal.value).to eq(5)
179
+ expect(expr.operands[0].operands[1].literal.value).to eq(2)
180
+ expect(expr.operands[1].literal.value).to eq(3)
181
+ end
182
+
183
+ it 'should parse the division of two number literals' do
184
+ input = '8 / 2;'
185
+ ptree = subject.parse(input)
186
+ parent = ptree.root.subnodes[0]
187
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
188
+ expect(parent.symbol.name).to eq('exprStmt')
189
+ expr = parent.subnodes[0]
190
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
191
+ expect(expr.operator).to eq(:/)
192
+ expect(expr.operands[0].literal.value).to eq(8)
193
+ expect(expr.operands[1].literal.value).to eq(2)
194
+ end
195
+
196
+ it 'should parse the product of two number literals' do
197
+ input = '12.34 * 0.3;'
198
+ ptree = subject.parse(input)
199
+ parent = ptree.root.subnodes[0]
200
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
201
+ expect(parent.symbol.name).to eq('exprStmt')
202
+ expr = parent.subnodes[0]
203
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
204
+ expect(expr.operator).to eq(:*)
205
+ expect(expr.operands[0].literal.value).to eq(12.34)
206
+ expect(expr.operands[1].literal.value).to eq(0.3)
207
+ end
208
+
209
+ it 'should parse multiple additive operations' do
210
+ input = '5 * 2 / 3;'
211
+ ptree = subject.parse(input)
212
+ parent = ptree.root.subnodes[0]
213
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
214
+ expect(parent.symbol.name).to eq('exprStmt')
215
+ expr = parent.subnodes[0]
216
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
217
+ expect(expr.operator).to eq(:/)
218
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
219
+ expect(expr.operands[0].operator).to eq(:*)
220
+ expect(expr.operands[0].operands[0].literal.value).to eq(5)
221
+ expect(expr.operands[0].operands[1].literal.value).to eq(2)
222
+ expect(expr.operands[1].literal.value).to eq(3)
223
+ end
224
+ end # context
54
225
  end # describe
55
226
  end # module
56
- end # module
227
+ end # module
@@ -0,0 +1,98 @@
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/front_end/raw_parser'
7
+
8
+ module Loxxy
9
+ module FrontEnd
10
+ describe RawParser do
11
+ subject { RawParser.new }
12
+
13
+ context 'Initialization:' do
14
+ it 'should be initialized without argument' do
15
+ expect { RawParser.new }.not_to raise_error
16
+ end
17
+
18
+ it 'should have its parse engine initialized' do
19
+ expect(subject.engine).to be_kind_of(Rley::Engine)
20
+ end
21
+ end # context
22
+
23
+ context 'Parsing blank files:' do
24
+ def check_empty_input_result(aParseTree)
25
+ # Parse results MUST to comply to grammar rule:
26
+ # program => declaration_star EOF
27
+ # where the declaration_star MUST be empty
28
+ expect(aParseTree.root.symbol.name).to eq('program')
29
+ eof = aParseTree.root.subnodes.first
30
+ expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
31
+ expect(eof.symbol.name).to eq('EOF')
32
+ end
33
+
34
+ it 'should cope with an empty input' do
35
+ ptree = subject.parse('')
36
+ check_empty_input_result(ptree)
37
+ end
38
+
39
+ it 'should cope with whitespaces only input' do
40
+ ptree = subject.parse(' ' * 80 + "\n" * 20)
41
+ check_empty_input_result(ptree)
42
+ end
43
+
44
+ it 'should cope with comments only input' do
45
+ input = +''
46
+ %w[First Second Third].each do |ordinal|
47
+ input << "// #{ordinal} comment line\r\n"
48
+ end
49
+ ptree = subject.parse(input)
50
+ check_empty_input_result(ptree)
51
+ end
52
+ end # context
53
+
54
+ context 'Parsing expressions:' do
55
+ # Utility method to walk towards deeply nested node
56
+ # @param aNTNode [Rley::PTree::NonTerminalNode]
57
+ # @param subnodePath[Array<Integer>] An Array of subnode indices
58
+ def walk_subnodes(aNTNode, subnodePath)
59
+ curr_node = aNTNode
60
+ subnodePath.each do |index|
61
+ curr_node = curr_node.subnodes[index]
62
+ end
63
+
64
+ curr_node
65
+ end
66
+ it 'should parse a hello world program' do
67
+ program = <<-LOX_END
68
+ // Your first Lox program!
69
+ print "Hello, world!";
70
+ LOX_END
71
+ ptree = subject.parse(program)
72
+ root = ptree.root
73
+ expect(root.symbol.name).to eq('program')
74
+ (decls, eof) = root.subnodes
75
+ expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
76
+ expect(decls.symbol.name).to eq('declaration_plus')
77
+ stmt = decls.subnodes[0].subnodes[0]
78
+ expect(stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
79
+ expect(stmt.symbol.name).to eq('statement')
80
+ prnt_stmt = stmt.subnodes[0]
81
+ expect(prnt_stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
82
+ expect(prnt_stmt.subnodes.size).to eq(3)
83
+ expect(prnt_stmt.subnodes[0]).to be_kind_of(Rley::PTree::TerminalNode)
84
+ expect(prnt_stmt.subnodes[0].symbol.name).to eq('PRINT')
85
+ expect(prnt_stmt.subnodes[1]).to be_kind_of(Rley::PTree::NonTerminalNode)
86
+ leaf_node = walk_subnodes(prnt_stmt, [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
87
+ expect(leaf_node).to be_kind_of(Rley::PTree::TerminalNode)
88
+ expect(leaf_node.symbol.name).to eq('STRING')
89
+ expect(leaf_node.token.value).to eq('Hello, world!')
90
+ expect(prnt_stmt.subnodes[2]).to be_kind_of(Rley::PTree::TerminalNode)
91
+ expect(prnt_stmt.subnodes[2].symbol.name).to eq('SEMICOLON')
92
+ expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
93
+ expect(eof.symbol.name).to eq('EOF')
94
+ end
95
+ end
96
+ end # describe
97
+ end # module
98
+ end # module
@@ -78,7 +78,7 @@ module Loxxy
78
78
  end
79
79
 
80
80
  it 'should recognize non-datatype keywords' do
81
- keywords =<<-LOX_END
81
+ keywords = <<-LOX_END
82
82
  and class else fun for if or
83
83
  print return super this var while
84
84
  LOX_END
@@ -167,7 +167,7 @@ LOX_END
167
167
  end
168
168
 
169
169
  it 'should recognize string values' do
170
- input =<<-LOX_END
170
+ input = <<-LOX_END
171
171
  ""
172
172
  "string"
173
173
  "123"
@@ -223,6 +223,18 @@ LOX_END
223
223
  ]
224
224
  match_expectations(subject, expectations)
225
225
  end
226
+
227
+ it 'should cope with single slash (divide) expression' do
228
+ subject.start_with('8 / 2')
229
+
230
+ expectations = [
231
+ # [token lexeme]
232
+ %w[NUMBER 8],
233
+ %w[SLASH /],
234
+ %w[NUMBER 2]
235
+ ]
236
+ match_expectations(subject, expectations)
237
+ end
226
238
  end # context
227
239
  end # describe
228
240
  end # module