loxxy 0.1.17 → 0.2.04

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +47 -11
  3. data/CHANGELOG.md +72 -2
  4. data/README.md +188 -90
  5. data/bin/loxxy +54 -6
  6. data/lib/loxxy.rb +1 -0
  7. data/lib/loxxy/ast/all_lox_nodes.rb +1 -0
  8. data/lib/loxxy/ast/ast_builder.rb +20 -1
  9. data/lib/loxxy/ast/ast_visitee.rb +53 -0
  10. data/lib/loxxy/ast/ast_visitor.rb +8 -1
  11. data/lib/loxxy/ast/lox_assign_expr.rb +1 -5
  12. data/lib/loxxy/ast/lox_binary_expr.rb +1 -6
  13. data/lib/loxxy/ast/lox_block_stmt.rb +1 -5
  14. data/lib/loxxy/ast/lox_call_expr.rb +1 -5
  15. data/lib/loxxy/ast/lox_class_stmt.rb +6 -6
  16. data/lib/loxxy/ast/lox_for_stmt.rb +2 -6
  17. data/lib/loxxy/ast/lox_fun_stmt.rb +1 -5
  18. data/lib/loxxy/ast/lox_get_expr.rb +1 -6
  19. data/lib/loxxy/ast/lox_grouping_expr.rb +1 -5
  20. data/lib/loxxy/ast/lox_if_stmt.rb +2 -6
  21. data/lib/loxxy/ast/lox_literal_expr.rb +1 -5
  22. data/lib/loxxy/ast/lox_logical_expr.rb +1 -6
  23. data/lib/loxxy/ast/lox_node.rb +9 -1
  24. data/lib/loxxy/ast/lox_print_stmt.rb +1 -5
  25. data/lib/loxxy/ast/lox_return_stmt.rb +1 -5
  26. data/lib/loxxy/ast/lox_seq_decl.rb +1 -5
  27. data/lib/loxxy/ast/lox_set_expr.rb +1 -5
  28. data/lib/loxxy/ast/lox_super_expr.rb +31 -0
  29. data/lib/loxxy/ast/lox_this_expr.rb +1 -5
  30. data/lib/loxxy/ast/lox_unary_expr.rb +1 -6
  31. data/lib/loxxy/ast/lox_var_stmt.rb +1 -5
  32. data/lib/loxxy/ast/lox_variable_expr.rb +1 -5
  33. data/lib/loxxy/ast/lox_while_stmt.rb +2 -6
  34. data/lib/loxxy/back_end/engine.rb +57 -10
  35. data/lib/loxxy/back_end/lox_class.rb +13 -2
  36. data/lib/loxxy/back_end/lox_instance.rb +1 -1
  37. data/lib/loxxy/back_end/resolver.rb +31 -1
  38. data/lib/loxxy/cli_parser.rb +68 -0
  39. data/lib/loxxy/error.rb +3 -0
  40. data/lib/loxxy/front_end/grammar.rb +2 -2
  41. data/lib/loxxy/front_end/parser.rb +1 -1
  42. data/lib/loxxy/front_end/scanner.rb +32 -7
  43. data/lib/loxxy/version.rb +1 -1
  44. data/loxxy.gemspec +6 -2
  45. data/spec/front_end/scanner_spec.rb +34 -0
  46. data/spec/interpreter_spec.rb +51 -0
  47. metadata +10 -4
data/lib/loxxy/error.rb CHANGED
@@ -7,6 +7,9 @@ module Loxxy
7
7
  # Error occurring while Loxxy executes some invalid Lox code.
8
8
  class RuntimeError < Error; end
9
9
 
10
+ # Error occurring while Loxxy scans invalid input.
11
+ class ScanError < Error; end
12
+
10
13
  # Error occurring while Loxxy parses some invalid Lox code.
11
14
  class SyntaxError < Error; end
12
15
  end
@@ -38,7 +38,7 @@ module Loxxy
38
38
  rule('declaration' => 'stmt')
39
39
 
40
40
  rule('classDecl' => 'CLASS classNaming class_body').as 'class_decl'
41
- rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER')
41
+ rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER').as 'class_subclassing'
42
42
  rule('classNaming' => 'IDENTIFIER').as 'class_name'
43
43
  rule('class_body' => 'LEFT_BRACE methods_opt RIGHT_BRACE').as 'class_body'
44
44
  rule('methods_opt' => 'method_plus')
@@ -143,7 +143,7 @@ module Loxxy
143
143
  rule('primary' => 'STRING').as 'literal_expr'
144
144
  rule('primary' => 'IDENTIFIER').as 'variable_expr'
145
145
  rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN').as 'grouping_expr'
146
- rule('primary' => 'SUPER DOT IDENTIFIER')
146
+ rule('primary' => 'SUPER DOT IDENTIFIER').as 'super_expr'
147
147
 
148
148
  # Utility rules
149
149
  rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
@@ -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)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'strscan'
4
4
  require 'rley'
5
+ require_relative '../error'
5
6
  require_relative '../datatype/all_datatypes'
6
7
  require_relative 'literal'
7
8
 
@@ -60,8 +61,6 @@ module Loxxy
60
61
  PRINT RETURN SUPER THIS TRUE VAR WHILE
61
62
  ].map { |x| [x, x] }.to_h
62
63
 
63
- class ScanError < StandardError; end
64
-
65
64
  # Constructor. Initialize a tokenizer for Lox input.
66
65
  # @param source [String] Lox text to tokenize.
67
66
  def initialize(source = nil)
@@ -117,11 +116,14 @@ module Loxxy
117
116
  keyw = @@keywords[lexeme.upcase]
118
117
  tok_type = keyw || 'IDENTIFIER'
119
118
  token = build_token(tok_type, lexeme)
119
+ elsif scanner.scan(/"(?:\\"|[^"])*\z/)
120
+ # Error: unterminated string...
121
+ col = scanner.pos - @line_start + 1
122
+ raise ScanError, "Error: [line #{lineno}:#{col}]: Unterminated string."
120
123
  else # Unknown token
121
- erroneous = curr_ch.nil? ? '' : scanner.scan(/./)
122
- sequel = scanner.scan(/.{1,20}/)
123
- erroneous += sequel unless sequel.nil?
124
- raise ScanError, "Unknown token #{erroneous} on line #{lineno}"
124
+ col = scanner.pos - @line_start + 1
125
+ _erroneous = curr_ch.nil? ? '' : scanner.scan(/./)
126
+ raise ScanError, "Error: [line #{lineno}:#{col}]: Unexpected character."
125
127
  end
126
128
 
127
129
  return token
@@ -156,7 +158,7 @@ module Loxxy
156
158
  when 'NUMBER'
157
159
  value = Datatype::Number.new(aLexeme)
158
160
  when 'STRING'
159
- value = Datatype::LXString.new(aLexeme)
161
+ value = Datatype::LXString.new(unescape_string(aLexeme))
160
162
  when 'TRUE'
161
163
  value = Datatype::True.instance
162
164
  else
@@ -166,6 +168,29 @@ module Loxxy
166
168
  return [value, symb]
167
169
  end
168
170
 
171
+ # Replace any sequence sequence by their "real" value.
172
+ def unescape_string(aText)
173
+ result = +''
174
+ previous = nil
175
+
176
+ aText.each_char do |ch|
177
+ if previous
178
+ if ch == ?n
179
+ result << "\n"
180
+ else
181
+ result << ch
182
+ end
183
+ previous = nil
184
+ elsif ch == '\\'
185
+ previous = ?\
186
+ else
187
+ result << ch
188
+ end
189
+ end
190
+
191
+ result
192
+ end
193
+
169
194
  # Skip non-significant whitespaces and comments.
170
195
  # Advance the scanner until something significant is found.
171
196
  def skip_intertoken_spaces
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.1.17'
4
+ VERSION = '0.2.04'
5
5
  end
data/loxxy.gemspec CHANGED
@@ -40,8 +40,12 @@ 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 = 'An implementation of the Lox programming language. WIP'
44
- spec.description = 'An implementation of the Lox programming language. WIP'
43
+ spec.summary = 'An implementation of the Lox programming language.'
44
+ spec.description = <<-DESCR_END
45
+ A Ruby implementation of the Lox programming language. Lox is a dynamically typed,
46
+ object-oriented programming language that features first-class functions, closures,
47
+ classes, and inheritance.
48
+ DESCR_END
45
49
  spec.homepage = 'https://github.com/famished-tiger/loxxy'
46
50
  spec.license = 'MIT'
47
51
  spec.required_ruby_version = '~> 2.4'
@@ -189,6 +189,26 @@ LOX_END
189
189
  end
190
190
  end
191
191
 
192
+ it 'should recognize escaped quotes' do
193
+ embedded_quotes = %q{she said: \"Hello\"}
194
+ result = subject.send(:unescape_string, embedded_quotes)
195
+ expect(result).to eq('she said: "Hello"')
196
+ end
197
+
198
+ it 'should recognize escaped backslash' do
199
+ embedded_backslash = 'backslash>\\\\'
200
+ result = subject.send(:unescape_string, embedded_backslash)
201
+ expect(result).to eq('backslash>\\')
202
+ end
203
+
204
+ # rubocop: disable Style/StringConcatenation
205
+ it 'should recognize newline escape sequence' do
206
+ embedded_newline = 'line1\\nline2'
207
+ result = subject.send(:unescape_string, embedded_newline)
208
+ expect(result).to eq('line1' + "\n" + 'line2')
209
+ end
210
+ # rubocop: enable Style/StringConcatenation
211
+
192
212
  it 'should recognize a nil token' do
193
213
  subject.start_with('nil')
194
214
  token_nil = subject.tokens[0]
@@ -237,6 +257,20 @@ LOX_END
237
257
  ]
238
258
  match_expectations(subject, expectations)
239
259
  end
260
+
261
+ it 'should complain if it finds an unterminated string' do
262
+ subject.start_with('var a = "Unfinished;')
263
+ err = Loxxy::ScanError
264
+ err_msg = 'Error: [line 1:21]: Unterminated string.'
265
+ expect { subject.tokens }.to raise_error(err, err_msg)
266
+ end
267
+
268
+ it 'should complain if it finds an unexpected character' do
269
+ subject.start_with('var a = ?1?;')
270
+ err = Loxxy::ScanError
271
+ err_msg = 'Error: [line 1:9]: Unexpected character.'
272
+ expect { subject.tokens }.to raise_error(err, err_msg)
273
+ end
240
274
  end # context
241
275
  end # describe
242
276
  end # module
@@ -6,6 +6,7 @@ require 'stringio'
6
6
  # Load the class under test
7
7
  require_relative '../lib/loxxy/interpreter'
8
8
 
9
+ # rubocop: disable Metrics/ModuleLength
9
10
  module Loxxy
10
11
  # This spec contains the bare bones test for the Interpreter class.
11
12
  # The execution of Lox code is tested elsewhere.
@@ -430,6 +431,17 @@ LOX_END
430
431
  expect(result).to eq(3)
431
432
  end
432
433
 
434
+ it 'should support return within statements inside a function' do
435
+ program = <<-LOX_END
436
+ fun foo() {
437
+ for (;;) return "done";
438
+ }
439
+ print foo(); // output: done
440
+ LOX_END
441
+ expect { subject.evaluate(program) }.not_to raise_error
442
+ expect(sample_cfg[:ostream].string).to eq('done')
443
+ end
444
+
433
445
  # rubocop: disable Style/StringConcatenation
434
446
  it 'should support local functions and closures' do
435
447
  program = <<-LOX_END
@@ -479,6 +491,18 @@ LOX_END
479
491
  snippet
480
492
  end
481
493
 
494
+ it 'should support field assignment expression' do
495
+ program = <<-LOX_END
496
+ class Foo {}
497
+
498
+ var foo = Foo();
499
+
500
+ print foo.bar = "bar value"; // expect: bar value
501
+ LOX_END
502
+ expect { subject.evaluate(program) }.not_to raise_error
503
+ expect(sample_cfg[:ostream].string).to eq('bar value')
504
+ end
505
+
482
506
  it 'should support class declaration' do
483
507
  program = <<-LOX_END
484
508
  #{duck_class}
@@ -591,7 +615,34 @@ LOX_END
591
615
  predicted = 'Enjoy your bacon and toast, Dear Reader.'
592
616
  expect(sample_cfg[:ostream].string).to eq(predicted)
593
617
  end
618
+
619
+ it 'should support class inheritance and super keyword' do
620
+ lox_snippet = <<-LOX_END
621
+ class A {
622
+ method() {
623
+ print "A method";
624
+ }
625
+ }
626
+
627
+ class B < A {
628
+ method() {
629
+ print "B method";
630
+ }
631
+
632
+ test() {
633
+ super.method();
634
+ }
635
+ }
636
+
637
+ class C < B {}
638
+
639
+ C().test();
640
+ LOX_END
641
+ expect { subject.evaluate(lox_snippet) }.not_to raise_error
642
+ expect(sample_cfg[:ostream].string).to eq('A method')
643
+ end
594
644
  end # context
595
645
  end # describe
596
646
  # rubocop: enable Metrics/BlockLength
597
647
  end # module
648
+ # rubocop: enable Metrics/ModuleLength
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.1.17
4
+ version: 0.2.04
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-11 00:00:00.000000000 Z
11
+ date: 2021-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -66,7 +66,10 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.0'
69
- description: An implementation of the Lox programming language. WIP
69
+ description: |2
70
+ A Ruby implementation of the Lox programming language. Lox is a dynamically typed,
71
+ object-oriented programming language that features first-class functions, closures,
72
+ classes, and inheritance.
70
73
  email:
71
74
  - famished.tiger@yahoo.com
72
75
  executables:
@@ -88,6 +91,7 @@ files:
88
91
  - lib/loxxy.rb
89
92
  - lib/loxxy/ast/all_lox_nodes.rb
90
93
  - lib/loxxy/ast/ast_builder.rb
94
+ - lib/loxxy/ast/ast_visitee.rb
91
95
  - lib/loxxy/ast/ast_visitor.rb
92
96
  - lib/loxxy/ast/lox_assign_expr.rb
93
97
  - lib/loxxy/ast/lox_binary_expr.rb
@@ -108,6 +112,7 @@ files:
108
112
  - lib/loxxy/ast/lox_return_stmt.rb
109
113
  - lib/loxxy/ast/lox_seq_decl.rb
110
114
  - lib/loxxy/ast/lox_set_expr.rb
115
+ - lib/loxxy/ast/lox_super_expr.rb
111
116
  - lib/loxxy/ast/lox_this_expr.rb
112
117
  - lib/loxxy/ast/lox_unary_expr.rb
113
118
  - lib/loxxy/ast/lox_var_stmt.rb
@@ -124,6 +129,7 @@ files:
124
129
  - lib/loxxy/back_end/symbol_table.rb
125
130
  - lib/loxxy/back_end/unary_operator.rb
126
131
  - lib/loxxy/back_end/variable.rb
132
+ - lib/loxxy/cli_parser.rb
127
133
  - lib/loxxy/datatype/all_datatypes.rb
128
134
  - lib/loxxy/datatype/boolean.rb
129
135
  - lib/loxxy/datatype/builtin_datatype.rb
@@ -178,7 +184,7 @@ requirements: []
178
184
  rubygems_version: 3.1.4
179
185
  signing_key:
180
186
  specification_version: 4
181
- summary: An implementation of the Lox programming language. WIP
187
+ summary: An implementation of the Lox programming language.
182
188
  test_files:
183
189
  - spec/back_end/engine_spec.rb
184
190
  - spec/back_end/environment_spec.rb