loxxy 0.1.16 → 0.2.03

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 +57 -0
  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 +49 -11
  35. data/lib/loxxy/back_end/lox_class.rb +22 -4
  36. data/lib/loxxy/back_end/lox_function.rb +6 -0
  37. data/lib/loxxy/back_end/lox_instance.rb +1 -5
  38. data/lib/loxxy/back_end/resolver.rb +38 -2
  39. data/lib/loxxy/cli_parser.rb +68 -0
  40. data/lib/loxxy/error.rb +3 -0
  41. data/lib/loxxy/front_end/grammar.rb +2 -2
  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 +65 -1
  47. metadata +10 -4
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse' # Use standard OptionParser class for command-line parsing
4
+
5
+ module Loxxy
6
+ # A command-line option parser for the Loxxy interpreter.
7
+ # It is a specialisation of the OptionParser class.
8
+ class CLIParser < OptionParser
9
+ # @return [Hash{Symbol=>String, Array}]
10
+ attr_reader(:parsed_options)
11
+
12
+ # Constructor.
13
+ def initialize(prog_name, ver)
14
+ super()
15
+ reset(prog_name, ver)
16
+
17
+ heading
18
+ separator 'Options:'
19
+ separator ''
20
+ add_tail_options
21
+ end
22
+
23
+ def parse!(args)
24
+ super
25
+ parsed_options
26
+ end
27
+
28
+ private
29
+
30
+ def reset(prog_name, ver)
31
+ @program_name = prog_name
32
+ @version = ver
33
+ @banner = "Usage: #{prog_name} LOX_FILE [options]"
34
+ @parsed_options = {}
35
+ end
36
+
37
+ def description
38
+ <<-DESCR
39
+ Description:
40
+ loxxy is a Lox interpreter, it executes the Lox file(s) given in command-line.
41
+ More on Lox Language: https://craftinginterpreters.com/the-lox-language.html
42
+
43
+ Example:
44
+ #{program_name} hello.lox
45
+ DESCR
46
+ end
47
+
48
+ def heading
49
+ banner
50
+ separator ''
51
+ separator description
52
+ separator ''
53
+ end
54
+
55
+ def add_tail_options
56
+ on_tail('--version', 'Display the program version then quit.') do
57
+ puts version
58
+ exit(0)
59
+ end
60
+
61
+ on_tail('-?', '-h', '--help', 'Display this help then quit.') do
62
+ puts help
63
+ exit(0)
64
+ end
65
+ end
66
+ end # class
67
+ end # module
68
+ # End of file
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'
@@ -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.16'
4
+ VERSION = '0.2.03'
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.
@@ -479,6 +480,18 @@ LOX_END
479
480
  snippet
480
481
  end
481
482
 
483
+ it 'should support field assignment expression' do
484
+ program = <<-LOX_END
485
+ class Foo {}
486
+
487
+ var foo = Foo();
488
+
489
+ print foo.bar = "bar value"; // expect: bar value
490
+ LOX_END
491
+ expect { subject.evaluate(program) }.not_to raise_error
492
+ expect(sample_cfg[:ostream].string).to eq('bar value')
493
+ end
494
+
482
495
  it 'should support class declaration' do
483
496
  program = <<-LOX_END
484
497
  #{duck_class}
@@ -543,7 +556,7 @@ LOX_END
543
556
  closure;
544
557
  LOX_END
545
558
  # Expected result: Backend::LoxFunction('closure')
546
- # Expected function's closure ()environment layout):
559
+ # Expected function's closure (environment layout):
547
560
  # Environment('global')
548
561
  # defns
549
562
  # +- ['clock'] => BackEnd::Engine::NativeFunction
@@ -567,7 +580,58 @@ LOX_END
567
580
  expect(global_env.defns['clock'].value).to be_kind_of(BackEnd::Engine::NativeFunction)
568
581
  expect(global_env.defns['Foo'].value).to be_kind_of(BackEnd::LoxClass)
569
582
  end
583
+
584
+ it 'should support custom initializer' do
585
+ lox_snippet = <<-LOX_END
586
+ // From section 3.9.5
587
+ class Breakfast {
588
+ init(meat, bread) {
589
+ this.meat = meat;
590
+ this.bread = bread;
591
+ }
592
+
593
+ serve(who) {
594
+ print "Enjoy your " + this.meat + " and " +
595
+ this.bread + ", " + who + ".";
596
+ }
597
+ }
598
+
599
+ var baconAndToast = Breakfast("bacon", "toast");
600
+ baconAndToast.serve("Dear Reader");
601
+ // Output: "Enjoy your bacon and toast, Dear Reader."
602
+ LOX_END
603
+ expect { subject.evaluate(lox_snippet) }.not_to raise_error
604
+ predicted = 'Enjoy your bacon and toast, Dear Reader.'
605
+ expect(sample_cfg[:ostream].string).to eq(predicted)
606
+ end
607
+
608
+ it 'should support class inheritance and super keyword' do
609
+ lox_snippet = <<-LOX_END
610
+ class A {
611
+ method() {
612
+ print "A method";
613
+ }
614
+ }
615
+
616
+ class B < A {
617
+ method() {
618
+ print "B method";
619
+ }
620
+
621
+ test() {
622
+ super.method();
623
+ }
624
+ }
625
+
626
+ class C < B {}
627
+
628
+ C().test();
629
+ LOX_END
630
+ expect { subject.evaluate(lox_snippet) }.not_to raise_error
631
+ expect(sample_cfg[:ostream].string).to eq('A method')
632
+ end
570
633
  end # context
571
634
  end # describe
572
635
  # rubocop: enable Metrics/BlockLength
573
636
  end # module
637
+ # 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.16
4
+ version: 0.2.03
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-10 00:00:00.000000000 Z
11
+ date: 2021-04-24 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