loxxy 0.2.02 → 0.3.00

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -0
  3. data/README.md +19 -2
  4. data/lib/loxxy/ast/all_lox_nodes.rb +0 -1
  5. data/lib/loxxy/ast/ast_builder.rb +27 -4
  6. data/lib/loxxy/ast/ast_visitee.rb +53 -0
  7. data/lib/loxxy/ast/ast_visitor.rb +2 -10
  8. data/lib/loxxy/ast/lox_assign_expr.rb +1 -5
  9. data/lib/loxxy/ast/lox_binary_expr.rb +1 -6
  10. data/lib/loxxy/ast/lox_block_stmt.rb +1 -5
  11. data/lib/loxxy/ast/lox_call_expr.rb +1 -5
  12. data/lib/loxxy/ast/lox_class_stmt.rb +1 -5
  13. data/lib/loxxy/ast/lox_fun_stmt.rb +1 -5
  14. data/lib/loxxy/ast/lox_get_expr.rb +1 -6
  15. data/lib/loxxy/ast/lox_grouping_expr.rb +1 -5
  16. data/lib/loxxy/ast/lox_if_stmt.rb +2 -6
  17. data/lib/loxxy/ast/lox_literal_expr.rb +1 -5
  18. data/lib/loxxy/ast/lox_logical_expr.rb +1 -6
  19. data/lib/loxxy/ast/lox_node.rb +9 -1
  20. data/lib/loxxy/ast/lox_print_stmt.rb +1 -5
  21. data/lib/loxxy/ast/lox_return_stmt.rb +1 -5
  22. data/lib/loxxy/ast/lox_seq_decl.rb +1 -5
  23. data/lib/loxxy/ast/lox_set_expr.rb +1 -5
  24. data/lib/loxxy/ast/lox_super_expr.rb +2 -6
  25. data/lib/loxxy/ast/lox_this_expr.rb +1 -5
  26. data/lib/loxxy/ast/lox_unary_expr.rb +1 -6
  27. data/lib/loxxy/ast/lox_var_stmt.rb +1 -5
  28. data/lib/loxxy/ast/lox_variable_expr.rb +1 -5
  29. data/lib/loxxy/ast/lox_while_stmt.rb +2 -6
  30. data/lib/loxxy/back_end/engine.rb +28 -26
  31. data/lib/loxxy/back_end/lox_instance.rb +1 -1
  32. data/lib/loxxy/back_end/resolver.rb +17 -21
  33. data/lib/loxxy/datatype/number.rb +19 -4
  34. data/lib/loxxy/front_end/parser.rb +1 -1
  35. data/lib/loxxy/front_end/scanner.rb +11 -10
  36. data/lib/loxxy/version.rb +1 -1
  37. data/spec/back_end/environment_spec.rb +0 -14
  38. data/spec/back_end/symbol_table_spec.rb +0 -19
  39. data/spec/back_end/variable_spec.rb +0 -35
  40. data/spec/front_end/scanner_spec.rb +35 -7
  41. data/spec/interpreter_spec.rb +36 -0
  42. metadata +3 -3
  43. data/lib/loxxy/ast/lox_for_stmt.rb +0 -41
@@ -46,7 +46,7 @@ module Loxxy
46
46
  # Stop if the parse failed...
47
47
  line1 = "Parsing failed\n"
48
48
  line2 = "Reason: #{result.failure_reason.message}"
49
- raise StandardError, line1 + line2
49
+ raise SyntaxError, line1 + line2
50
50
  end
51
51
 
52
52
  return engine.convert(result) # engine.to_ptree(result)
@@ -54,11 +54,11 @@ module Loxxy
54
54
  '<=' => 'LESS_EQUAL'
55
55
  }.freeze
56
56
 
57
- # Here are all the implemented Lox keywords (in uppercase)
57
+ # Here are all the implemented Lox keywords
58
58
  # These are enumerated in section 4.2.1 Token type
59
59
  @@keywords = %w[
60
- AND CLASS ELSE FALSE FUN FOR IF NIL OR
61
- PRINT RETURN SUPER THIS TRUE VAR WHILE
60
+ and class else false fun for if nil or
61
+ print return super this true var while
62
62
  ].map { |x| [x, x] }.to_h
63
63
 
64
64
  # Constructor. Initialize a tokenizer for Lox input.
@@ -84,7 +84,7 @@ module Loxxy
84
84
  token = _next_token
85
85
  tok_sequence << token unless token.nil?
86
86
  end
87
- tok_sequence << build_token('EOF', '')
87
+ tok_sequence << build_token('EOF', nil)
88
88
 
89
89
  return tok_sequence
90
90
  end
@@ -99,22 +99,22 @@ 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
- elsif (lexeme = scanner.scan(/[+\-](?!\d)/))
105
+ elsif (lexeme = scanner.scan(/\+(?!\d)/))
106
106
  # Minus or plus character not preceding a digit
107
107
  token = build_token(@@lexeme2name[lexeme], lexeme)
108
108
  elsif (lexeme = scanner.scan(/[!=><]=?/))
109
109
  # One or two special character tokens
110
110
  token = build_token(@@lexeme2name[lexeme], lexeme)
111
- elsif (lexeme = scanner.scan(/-?\d+(?:\.\d+)?/))
111
+ elsif (lexeme = scanner.scan(/\d+(?:\.\d+)?/))
112
112
  token = build_token('NUMBER', lexeme)
113
113
  elsif (lexeme = scanner.scan(/"(?:\\"|[^"])*"/))
114
114
  token = build_token('STRING', lexeme)
115
115
  elsif (lexeme = scanner.scan(/[a-zA-Z_][a-zA-Z_0-9]*/))
116
- keyw = @@keywords[lexeme.upcase]
117
- tok_type = keyw || 'IDENTIFIER'
116
+ keyw = @@keywords[lexeme]
117
+ tok_type = keyw ? keyw.upcase : 'IDENTIFIER'
118
118
  token = build_token(tok_type, lexeme)
119
119
  elsif scanner.scan(/"(?:\\"|[^"])*\z/)
120
120
  # Error: unterminated string...
@@ -133,7 +133,8 @@ module Loxxy
133
133
  def build_token(aSymbolName, aLexeme)
134
134
  begin
135
135
  (value, symb) = convert_to(aLexeme, aSymbolName)
136
- col = scanner.pos - aLexeme.size - @line_start + 1
136
+ lex_length = aLexeme ? aLexeme.size : 0
137
+ col = scanner.pos - lex_length - @line_start + 1
137
138
  pos = Rley::Lexical::Position.new(@lineno, col)
138
139
  if value
139
140
  token = Literal.new(value, aLexeme.dup, symb, pos)
data/lib/loxxy/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.2.02'
4
+ VERSION = '0.3.00'
5
5
  end
@@ -54,20 +54,6 @@ module Loxxy
54
54
  expect(var_b).to be_kind_of(Variable)
55
55
  expect(var_b.name).to eq('b')
56
56
  end
57
-
58
- # it 'should set the suffix of just created variable' do
59
- # subject.insert(var('a'))
60
- # var_a = subject.defns['a']
61
- # expect(var_a.suffix).to eq("_#{subject.object_id.to_s(16)}")
62
- # end
63
-
64
- # it 'should complain when variable names collide' do
65
- # subject.insert(var('c'))
66
- # expect(subject.defns['c']).to be_kind_of(Datatype::Variable)
67
- # err = StandardError
68
- # err_msg = "Variable with name 'c' already exists."
69
- # expect { subject.insert(var('c')) }.to raise_error(err, err_msg)
70
- # end
71
57
  end # context
72
58
  end # describe
73
59
  end # module
@@ -106,25 +106,6 @@ module Loxxy
106
106
  expect(subject.lookup('q')).to eq(subject.current_env.defns['q'])
107
107
  end
108
108
 
109
- # it 'should allow the search of an entry based on its i_name' do
110
- # subject.insert(var('q'))
111
- # i_name_x = subject.insert(var('x'))
112
- # subject.enter_environment(BackEnd::Environment.new)
113
- # i_name_q2 = subject.insert(var('q'))
114
- # i_name_y = subject.insert(var('y'))
115
-
116
- # # Search for unknown i_name
117
- # expect(subject.lookup_i_name('dummy')).to be_nil
118
-
119
- # curr_scope = subject.current_env
120
- # # # Search for existing unique names
121
- # expect(subject.lookup_i_name(i_name_y)).to eq(curr_scope.defns['y'])
122
- # expect(subject.lookup_i_name(i_name_x)).to eq(subject.root.defns['x'])
123
-
124
- # # Search for redefined name
125
- # expect(subject.lookup_i_name(i_name_q2)).to eq(curr_scope.defns['q'])
126
- # end
127
-
128
109
  it 'should list all the variables defined in all the szcope chain' do
129
110
  subject.insert(var('q'))
130
111
  subject.enter_environment(BackEnd::Environment.new)
@@ -38,41 +38,6 @@ module Loxxy
38
38
  instance = Variable.new(sample_name)
39
39
  expect(instance.value).to eq(Datatype::Nil.instance)
40
40
  end
41
-
42
- # it 'should know its default internal name' do
43
- # # By default: internal name == label
44
- # expect(subject.i_name).to eq(subject.label)
45
- # end
46
-
47
- # it 'should have a nil suffix' do
48
- # expect(subject.suffix).to be_nil
49
- # end
50
- end # context
51
-
52
- context 'Provided service:' do
53
- let(:sample_suffix) { 'sample-suffix' }
54
- it 'should have a label equal to its user-defined name' do
55
- # expect(subject.label).to eq(subject.name)
56
- end
57
-
58
- it 'should accept a suffix' do
59
- # expect { subject.suffix = sample_suffix }.not_to raise_error
60
- # expect(subject.suffix).to eq(sample_suffix)
61
- end
62
-
63
- it 'should calculate its internal name' do
64
- # # Rule: empty suffix => internal name == label
65
- # subject.suffix = ''
66
- # expect(subject.i_name).to eq(subject.label)
67
-
68
- # # Rule: suffix starting underscore: internal name = label + suffix
69
- # subject.suffix = '_10'
70
- # expect(subject.i_name).to eq(subject.label + subject.suffix)
71
-
72
- # # Rule: ... otherwise: internal name == suffix
73
- # subject.suffix = sample_suffix
74
- # expect(subject.i_name).to eq(subject.suffix)
75
- end
76
41
  end # context
77
42
  end # describe
78
43
  end # module
@@ -124,18 +124,15 @@ LOX_END
124
124
 
125
125
  it 'should recognize number values' do
126
126
  input = <<-LOX_END
127
- 123 987654
128
- 0 -0
129
- 123.456 -0.001
130
- LOX_END
127
+ 123 987654
128
+ 0 123.456
129
+ LOX_END
131
130
 
132
131
  expectations = [
133
132
  ['123', 123],
134
133
  ['987654', 987654],
135
134
  ['0', 0],
136
- ['-0', 0],
137
- ['123.456', 123.456],
138
- ['-0.001', -0.001]
135
+ ['123.456', 123.456]
139
136
  ]
140
137
 
141
138
  subject.start_with(input)
@@ -149,6 +146,30 @@ LOX_END
149
146
  end
150
147
  end
151
148
 
149
+ it 'should recognize negative number values' do
150
+ input = <<-LOX_END
151
+ -0
152
+ -0.001
153
+ LOX_END
154
+
155
+ expectations = [
156
+ ['-', '0'],
157
+ ['-', '0.001']
158
+ ].flatten
159
+
160
+ subject.start_with(input)
161
+ tokens = subject.tokens
162
+ tokens.pop
163
+ i = 0
164
+ tokens.each_slice(2) do |(sign, lit)|
165
+ expect(sign.terminal).to eq('MINUS')
166
+ expect(sign.lexeme).to eq(expectations[i])
167
+ expect(lit.terminal).to eq('NUMBER')
168
+ expect(lit.lexeme).to eq(expectations[i + 1])
169
+ i += 2
170
+ end
171
+ end
172
+
152
173
  it 'should recognize leading and trailing dots as distinct tokens' do
153
174
  input = '.456 123.'
154
175
 
@@ -217,6 +238,13 @@ LOX_END
217
238
  expect(token_nil.lexeme).to eq('nil')
218
239
  expect(token_nil.value).to be_kind_of(Datatype::Nil)
219
240
  end
241
+
242
+ it 'should differentiate nil from variable spelled same' do
243
+ subject.start_with('Nil')
244
+ similar = subject.tokens[0]
245
+ expect(similar.terminal).to eq('IDENTIFIER')
246
+ expect(similar.lexeme).to eq('Nil')
247
+ end
220
248
  end # context
221
249
 
222
250
  context 'Handling comments:' do
@@ -148,6 +148,19 @@ module Loxxy
148
148
  end
149
149
  end
150
150
 
151
+ it 'should ignore spaces surrounding minus in subtraction of two numbers' do
152
+ [
153
+ ['1 - 1;', 0],
154
+ ['1 -1;', 0],
155
+ ['1- 1;', 0],
156
+ ['1-1;', 0]
157
+ ].each do |(source, predicted)|
158
+ lox = Loxxy::Interpreter.new
159
+ result = lox.evaluate(source)
160
+ expect(result.value == predicted).to be_truthy
161
+ end
162
+ end
163
+
151
164
  it 'should evaluate the negation of an object' do
152
165
  [
153
166
  ['!true;', false],
@@ -431,6 +444,17 @@ LOX_END
431
444
  expect(result).to eq(3)
432
445
  end
433
446
 
447
+ it 'should support return within statements inside a function' do
448
+ program = <<-LOX_END
449
+ fun foo() {
450
+ for (;;) return "done";
451
+ }
452
+ print foo(); // output: done
453
+ LOX_END
454
+ expect { subject.evaluate(program) }.not_to raise_error
455
+ expect(sample_cfg[:ostream].string).to eq('done')
456
+ end
457
+
434
458
  # rubocop: disable Style/StringConcatenation
435
459
  it 'should support local functions and closures' do
436
460
  program = <<-LOX_END
@@ -480,6 +504,18 @@ LOX_END
480
504
  snippet
481
505
  end
482
506
 
507
+ it 'should support field assignment expression' do
508
+ program = <<-LOX_END
509
+ class Foo {}
510
+
511
+ var foo = Foo();
512
+
513
+ print foo.bar = "bar value"; // expect: bar value
514
+ LOX_END
515
+ expect { subject.evaluate(program) }.not_to raise_error
516
+ expect(sample_cfg[:ostream].string).to eq('bar value')
517
+ end
518
+
483
519
  it 'should support class declaration' do
484
520
  program = <<-LOX_END
485
521
  #{duck_class}
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: loxxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.02
4
+ version: 0.3.00
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-21 00:00:00.000000000 Z
11
+ date: 2021-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -91,6 +91,7 @@ files:
91
91
  - lib/loxxy.rb
92
92
  - lib/loxxy/ast/all_lox_nodes.rb
93
93
  - lib/loxxy/ast/ast_builder.rb
94
+ - lib/loxxy/ast/ast_visitee.rb
94
95
  - lib/loxxy/ast/ast_visitor.rb
95
96
  - lib/loxxy/ast/lox_assign_expr.rb
96
97
  - lib/loxxy/ast/lox_binary_expr.rb
@@ -98,7 +99,6 @@ files:
98
99
  - lib/loxxy/ast/lox_call_expr.rb
99
100
  - lib/loxxy/ast/lox_class_stmt.rb
100
101
  - lib/loxxy/ast/lox_compound_expr.rb
101
- - lib/loxxy/ast/lox_for_stmt.rb
102
102
  - lib/loxxy/ast/lox_fun_stmt.rb
103
103
  - lib/loxxy/ast/lox_get_expr.rb
104
104
  - lib/loxxy/ast/lox_grouping_expr.rb
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'lox_compound_expr'
4
-
5
- module Loxxy
6
- module Ast
7
- class LoxForStmt < LoxCompoundExpr
8
- # @return [LoxNode] test expression
9
- attr_reader :test_expr
10
-
11
- # @return [LoxNode] update expression
12
- attr_reader :update_expr
13
-
14
- # @return [LoxNode] body statement
15
- attr_accessor :body_stmt
16
-
17
- # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
18
- # @param initialization [Loxxy::Ast::LoxNode]
19
- # @param testExpr [Loxxy::Ast::LoxNode]
20
- # @param updateExpr [Loxxy::Ast::LoxNode]
21
- def initialize(aPosition, initialization, testExpr, updateExpr)
22
- child = initialization ? [initialization] : []
23
- super(aPosition, child)
24
- @test_expr = testExpr
25
- @update_expr = updateExpr
26
- end
27
-
28
- # Part of the 'visitee' role in Visitor design pattern.
29
- # @param visitor [Ast::ASTVisitor] the visitor
30
- def accept(visitor)
31
- visitor.visit_for_stmt(self)
32
- end
33
-
34
- # Accessor to the condition expression
35
- # @return [LoxNode]
36
- def condition
37
- subnodes[0]
38
- end
39
- end # class
40
- end # module
41
- end # module