loxxy 0.0.4 → 0.0.9

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.4'
4
+ VERSION = '0.0.9'
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'
@@ -1,35 +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
+ # 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
@@ -0,0 +1,275 @@
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/parser'
7
+
8
+ module Loxxy
9
+ module FrontEnd
10
+ describe Parser do
11
+ subject { Parser.new }
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
+
25
+ context 'Initialization:' do
26
+ it 'should be initialized without argument' do
27
+ expect { Parser.new }.not_to raise_error
28
+ end
29
+
30
+ it 'should have its parse engine initialized' do
31
+ expect(subject.engine).to be_kind_of(Rley::Engine)
32
+ end
33
+ end # context
34
+
35
+ context 'Parsing blank files:' do
36
+ def check_empty_input_result(aParseTree)
37
+ # Parse results MUST to comply to grammar rule:
38
+ # program => declaration_star EOF
39
+ # where the declaration_star MUST be empty
40
+ expect(aParseTree.root.symbol.name).to eq('EOF')
41
+ end
42
+
43
+ it 'should cope with an empty input' do
44
+ ptree = subject.parse('')
45
+ check_empty_input_result(ptree)
46
+ end
47
+
48
+ it 'should cope with whitespaces only input' do
49
+ ptree = subject.parse(' ' * 80 + "\n" * 20)
50
+ check_empty_input_result(ptree)
51
+ end
52
+
53
+ it 'should cope with comments only input' do
54
+ input = +''
55
+ %w[First Second Third].each do |ordinal|
56
+ input << "// #{ordinal} comment line\r\n"
57
+ end
58
+ ptree = subject.parse(input)
59
+ check_empty_input_result(ptree)
60
+ end
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 multiplicative 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
+
225
+ it 'should parse combination of terms and factors' do
226
+ input = '5 + 2 / 3;'
227
+ ptree = subject.parse(input)
228
+ parent = ptree.root.subnodes[0]
229
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
230
+ expect(parent.symbol.name).to eq('exprStmt')
231
+ expr = parent.subnodes[0]
232
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
233
+ expect(expr.operator).to eq(:+)
234
+ expect(expr.operands[0].literal.value).to eq(5)
235
+ expect(expr.operands[1]).to be_kind_of(Ast::LoxBinaryExpr)
236
+ expect(expr.operands[1].operator).to eq(:/)
237
+ expect(expr.operands[1].operands[0].literal.value).to eq(2)
238
+ expect(expr.operands[1].operands[1].literal.value).to eq(3)
239
+ end
240
+ end # context
241
+
242
+ context 'Parsing string concatenation' do
243
+ it 'should parse the concatenation of two string literals' do
244
+ input = '"Lo" + "ve";'
245
+ ptree = subject.parse(input)
246
+ parent = ptree.root.subnodes[0]
247
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
248
+ expect(parent.symbol.name).to eq('exprStmt')
249
+ expr = parent.subnodes[0]
250
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
251
+ expect(expr.operator).to eq(:+)
252
+ expect(expr.operands[0].literal.value).to eq('Lo')
253
+ expect(expr.operands[1].literal.value).to eq('ve')
254
+ end
255
+ end # context
256
+
257
+ context 'Parsing comparison expressions' do
258
+ it 'should parse the comparison of two number literals' do
259
+ %w[> >= < <=].each do |predicate|
260
+ input = "3 #{predicate} 2;"
261
+ ptree = subject.parse(input)
262
+ parent = ptree.root.subnodes[0]
263
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
264
+ expect(parent.symbol.name).to eq('exprStmt')
265
+ expr = parent.subnodes[0]
266
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
267
+ expect(expr.operator).to eq(predicate.to_sym)
268
+ expect(expr.operands[0].literal.value).to eq(3)
269
+ expect(expr.operands[1].literal.value).to eq(2)
270
+ end
271
+ end
272
+ end # context
273
+ end # describe
274
+ end # module
275
+ end # module
@@ -26,10 +26,7 @@ module Loxxy
26
26
  # program => declaration_star EOF
27
27
  # where the declaration_star MUST be empty
28
28
  expect(aParseTree.root.symbol.name).to eq('program')
29
- (decls, eof) = aParseTree.root.subnodes
30
- expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
31
- expect(decls.symbol.name).to eq('declaration_star')
32
- expect(decls.subnodes).to be_empty
29
+ eof = aParseTree.root.subnodes.first
33
30
  expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
34
31
  expect(eof.symbol.name).to eq('EOF')
35
32
  end
@@ -76,8 +73,8 @@ LOX_END
76
73
  expect(root.symbol.name).to eq('program')
77
74
  (decls, eof) = root.subnodes
78
75
  expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
79
- expect(decls.symbol.name).to eq('declaration_star')
80
- stmt = decls.subnodes[1].subnodes[0]
76
+ expect(decls.symbol.name).to eq('declaration_plus')
77
+ stmt = decls.subnodes[0].subnodes[0]
81
78
  expect(stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
82
79
  expect(stmt.symbol.name).to eq('statement')
83
80
  prnt_stmt = stmt.subnodes[0]
@@ -92,8 +89,10 @@ LOX_END
92
89
  expect(leaf_node.token.value).to eq('Hello, world!')
93
90
  expect(prnt_stmt.subnodes[2]).to be_kind_of(Rley::PTree::TerminalNode)
94
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')
95
94
  end
96
95
  end
97
96
  end # describe
98
97
  end # module
99
- 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