loxxy 0.2.06 → 0.4.00

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbb2f24e245891baabf20458e5b663784ba7f82ff902ab7892007d9b53d99347
4
- data.tar.gz: eab4ede6f48470449f731e3074b2ac2fe72b53449c9471231ea64d0007a873ae
3
+ metadata.gz: 47c5d65fa1f3c2d0025013577f46edf741572df743d8986e238f49a08ea32702
4
+ data.tar.gz: 72a3beb6828362e6dc7c73072555ed6f87ed5bf57b4052ac55e06503b9582e29
5
5
  SHA512:
6
- metadata.gz: 8037d3c239e39d47554c4e7f7698e5908cdaa5e460c8972fe8dc5b58ee5fd59647c413db95250ff739c08f9ca891c8f576eb8d78aa63cf40039e8d1b3328e9a2
7
- data.tar.gz: 795face50a76140ba2f6be2e6e6c44e671992baaea2ecade980acf1baed173dc59cbe7cd2ce419b509e8593068ffc3e637e0678dab2db72a96250d4e53ef0338
6
+ metadata.gz: e83ee511c1ead4592153bb1e8ed8167439894bfefa2bb54060100eb9d221d77c453b4bad3d3e4a36209f3761812a5b9b0aa98b02a53556314c022d1f0b605bca
7
+ data.tar.gz: 8d939d7ff60687dd64c4dc6e4beccffa512e3483f5b38042a83fffbc0a5cc83d218999f855b8765a47e24b8c0df7808ecd649e70cec11832a0591b5c141a659c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,53 @@
1
+ ## [0.4.00] - 2021-05-24
2
+ - Version bump. `Loxxy` is capable to run the LoxLox interpreter, an interpreter written in `Lox`.
3
+
4
+ ### New
5
+ - Method `BackEnd::LoxInstance#falsey?` added
6
+ - Method `BackEnd::LoxInstance#truthy?` added
7
+
8
+ ### Changed
9
+ - Method `BackEnd::Engine#after_variable_expr` the error message `Undefined variable` nows gives the location of the offending variable.
10
+ - Class `Ast::LoxClassStmt`is now a subclass of `LoxNode`
11
+
12
+ - File `README.md` added an explanation on how to run `LoxLox`interpreter.
13
+
14
+ ### Fixed
15
+ - Method `Ast::LoxClassStmt#initialize` fixed inconsistencies in its Yard/RDoc documentation.
16
+ - Method `Ast::LoxFunStmt#initialize` fixed inconsistencies in its Yard/RDoc documentation.
17
+ - Method `BackEnd::Engine#native_getc` now returns -1 when its reaches EOF.
18
+ - Method `BackEnd::Resolver#after_logical_expr` was missing and this caused the lack of resultation in the second operand.
19
+
20
+ ## [0.3.02] - 2021-05-22
21
+ - New built-in expressions `getc`, `chr`, `exit` and `print_eeror` , fixes with deeply nested returns, set expressions
22
+
23
+ ### New
24
+ - Class `BackEnd::Engine` new built-in functions `getc`, `chr`, `exit`, and `print_error`
25
+
26
+ ### Changed
27
+ - Class `Ast::LoxSetExpr` value to assign is now a distinct atrrbute instead of a subnode
28
+ - Class `BackEnd::Engine` two distinct stacks: one of expression evaluation, another for argument passing
29
+
30
+ ### Fixed
31
+ - Class `BackEnd::LoxClass`: in some contexts the init method returned twice 'this'.
32
+ - Method `LoxFunction#call` mismatch between deeply nested returns and environments
33
+
34
+
35
+ ## [0.3.01] - 2021-05-08
36
+ - Fix in `Scanner` class, added more tests in test suite.
37
+
38
+ ### New
39
+ - Added the new subfolder `extra` under `test_suite`. It will contain tests for non-standard features or tests not covered in standard test suite.
40
+
41
+ ### Fixed
42
+ - Class `FrontEnd::Scanner`: Couldn't correctly recognize a plus preceding directly a number literal
43
+
44
+ ## [0.3.00] - 2021-05-07
45
+ - Milestone: `Loxxy` passes all reference test suite.
46
+
47
+ ### Fixed
48
+ - Method `BackEnd::Resolver#before_variable_expr`: Standard `Lox` allows re-declaration of a variable at top-level scope
49
+
50
+
1
51
  ## [0.2.06] - 2021-05-04
2
52
  - Nearly passing the 'official' test suite, fixing non-compliant behavior, specialized exceptions for errors
3
53
 
data/README.md CHANGED
@@ -19,11 +19,18 @@ Although __Lox__ is fairly simple, it is far from being a toy language:
19
19
  ### Loxxy gem features
20
20
  - Complete tree-walking interpreter including lexer, parser and resolver
21
21
  - 100% pure Ruby with clean design (not a port from some other language)
22
+ - Passes the `jox` (THE reference `Lox` implementation) test suite
23
+ - Can run a Lox imterpreter implemented in ... `Lox` [LoxLox](https://github.com/benhoyt/loxlox),
22
24
  - Minimal runtime dependency (Rley gem). Won't drag a bunch of gems...
23
25
  - Ruby API for integrating a Lox interpreter with your code.
24
26
  - A command-line interpreter `loxxy`
25
27
  - Open for your language extensions...
26
28
 
29
+ ### Why `Loxxy` ?
30
+ - If programming languages are one of your subject interest...
31
+ - ... and you wanted learn how to implement one in Ruby...
32
+ - ... then `Loxxy` can help to understand and experiment in this rewarding craft.
33
+
27
34
  ## How to start in 1, 2, 3...?
28
35
  ... in less than 3 minutes...
29
36
 
@@ -47,6 +54,7 @@ And then execute:
47
54
  ### 2. Your first `Lox` program
48
55
  Create a text file and enter the following lines:
49
56
  ```javascript
57
+ // hello.lox
50
58
  // Your firs Lox program
51
59
  print "Hello, world.";
52
60
  ```
@@ -69,6 +77,7 @@ Let's admit it, the hello world example was unimpressive.
69
77
  To a get a taste of `Lox` object-oriented capabilities, let's try another `Hello world` variant:
70
78
 
71
79
  ```javascript
80
+ // oo_hello.lox
72
81
  // Object-oriented hello world
73
82
  class Greeter {
74
83
  // in Lox, initializers/constructors are named `init`
@@ -92,6 +101,7 @@ Our next assignment: compute the first 20 elements of the Fibbonacci sequence.
92
101
  Here's an answer using the `while` loop construct:
93
102
 
94
103
  ```javascript
104
+ // fibbonacci.lox
95
105
  // Compute the first 20 elements from the Fibbonacci sequence
96
106
 
97
107
  var a = 0; // Use the var keyword to declare a new variable
@@ -121,6 +131,7 @@ Fans of `for` loops will be pleased to find their favorite looping construct.
121
131
  Here again, the Fibbonacci sequence refactored with a `for` loop:
122
132
 
123
133
  ```javascript
134
+ // fibbonacci_v2.lox
124
135
  // Fibbonacci sequence - version 2
125
136
  var a = 0;
126
137
  var b = 1;
@@ -163,13 +174,69 @@ for (var i = 0; i < count; i = i + 1) {
163
174
  }
164
175
  ```
165
176
 
177
+ ### Loxxy goes meta...
178
+ The `Loxxy` is able to run the `LoxLox` interpreter.
179
+ [LoxLox](https://github.com/benhoyt/loxlox) is a Lox interpreter written in Lox by Ben Hoyt.
180
+ This interpreter with over 1900 lines long is (one of) the longest Lox pragram.
181
+ As such, it is a good testbed for any Lox interpreter.
182
+
183
+ Executing a lox program with the LoxLox interpreter that is itself running on top of Loxxy.
184
+ #### Step 1 Download `lox.lox´ file
185
+ Download the [LoxLox](https://github.com/benhoyt/loxlox) source file in Github.
186
+
187
+ #### Step 2 (alternative a): running from the command line
188
+
189
+ ```
190
+ $ loxxy lox.lox
191
+ ```
192
+
193
+ Once loxxy CLI starts its interpreter that, in turn, executes the LoxLox interpreter.
194
+ This may take a couple of seconds.
195
+ Don't be surprised, if the program seems unresponsive: it is waiting for you input.
196
+ Enter a line like this:
197
+ ```
198
+ print "Hello, world!";
199
+ ```
200
+ Then terminate with an end of file (crtl-D on Linuxes, crtl-z on Windows) followed by an enter key.
201
+ You should see the famous greeting.
202
+
203
+ #### Step 2 (alternative b): launching the interpreter from Ruby snippet
204
+ The following snippet executes the LoxLox interpreter and feeds to it the
205
+ input text. That input text is made available through a StringIO that replaces
206
+ the `$stdio` device.
207
+
208
+ ```ruby
209
+ require 'stringio'
210
+ require 'loxxy'
211
+
212
+ # Place your Lox pragram within the heredoc
213
+ program = <<-LOX_END
214
+ print "Hello, world!";
215
+ LOX_END
216
+
217
+ lox_filename = 'lox.lox'
218
+ File.open(lox_filename, 'r') do |f|
219
+ source = f.read
220
+ cfg = { istream: StringIO.new(program, 'r')}
221
+ lox = Loxxy::Interpreter.new(cfg)
222
+ lox.evaluate(source)
223
+ end
224
+ ```
225
+
226
+ Save this snippet as a Ruby file, launch Ruby with this file in command line.
227
+ After a couple of seconds, you'll see the Ruby interpreter that executes the
228
+ Loxxy interpreter that itself executes the LoxLox interpreter written in Lox.
229
+ That last interpreter is the one that run the hello world line.
230
+
231
+ That's definitively meta...
232
+
166
233
  This completes our quick tour of `Lox`, to learn more about the language,
167
234
  check the online book [Crafting Interpreters](https://craftinginterpreters.com/ )
168
235
 
169
236
  ## What's the fuss about Lox?
170
237
  ... Nothing...
171
238
  Bob Nystrom designed a language __simple__ enough so that he could present
172
- two implementations (an interpreter, then a compiler) in one single book.
239
+ two interpreter implementations (a tree-walking one, then a bytecode one) in one single book.
173
240
 
174
241
  In other words, __Lox__ contains interesting features found in most general-purpose
175
242
  languages. In addition to that, there are [numerous implementations](https://github.com/munificent/craftinginterpreters/wiki/Lox-implementations) in different languages
@@ -196,14 +263,11 @@ There are already a number of programming languages derived from `Lox`...
196
263
  ### Purpose of this project:
197
264
  - To deliver an open source example of a programming language fully implemented in Ruby
198
265
  (from the scanner and parser to an interpreter).
199
- - The implementation should be mature enough to run [LoxLox](https://github.com/benhoyt/loxlox),
200
- a Lox interpreter written in Lox.
201
266
 
202
267
  ### Roadmap
203
268
  - Extend the test suite
204
269
  - Improve the error handling
205
270
  - Improve the documentation
206
- - Ability run the LoxLox interpreter
207
271
 
208
272
  ## Hello world example
209
273
  The next examples show how to use the interpreter directly from Ruby code.
@@ -318,7 +318,7 @@ module Loxxy
318
318
  name_assignee = theChildren[1].token.lexeme.dup
319
319
  if theChildren[0].kind_of?(Ast::LoxSetExpr)
320
320
  theChildren[0].property = name_assignee
321
- theChildren[0].subnodes << theChildren[3]
321
+ theChildren[0].value = theChildren[3]
322
322
  theChildren[0]
323
323
  else
324
324
  Ast::LoxAssignExpr.new(tokens[1].position, name_assignee, theChildren[3])
@@ -395,9 +395,10 @@ module Loxxy
395
395
  end
396
396
 
397
397
  # rule('primary' => 'IDENTIFIER')
398
- def reduce_variable_expr(_production, _range, tokens, theChildren)
398
+ def reduce_variable_expr(_production, _range, _tokens, theChildren)
399
399
  var_name = theChildren[0].token.lexeme
400
- LoxVariableExpr.new(tokens[0].position, var_name)
400
+ pos = theChildren[0].token.position
401
+ LoxVariableExpr.new(pos, var_name)
401
402
  end
402
403
 
403
404
  # rule('primary' => 'THIS')
@@ -71,7 +71,6 @@ module Loxxy
71
71
  # @param aClassStmt [AST::LOXClassStmt] the for statement node to visit
72
72
  def visit_class_stmt(aClassStmt)
73
73
  broadcast(:before_class_stmt, aClassStmt)
74
- traverse_subnodes(aClassStmt) # The methods are visited here...
75
74
  broadcast(:after_class_stmt, aClassStmt, self)
76
75
  end
77
76
 
@@ -126,7 +125,6 @@ module Loxxy
126
125
  # @param aSetExpr [AST::LOXGetExpr] the get expression node to visit
127
126
  def visit_set_expr(aSetExpr)
128
127
  broadcast(:before_set_expr, aSetExpr, self)
129
- traverse_subnodes(aSetExpr)
130
128
  broadcast(:after_set_expr, aSetExpr, self)
131
129
  end
132
130
 
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lox_compound_expr'
3
+ require_relative 'lox_node'
4
4
 
5
5
  module Loxxy
6
6
  module Ast
7
- class LoxClassStmt < LoxCompoundExpr
7
+ # A parse tree node that represents a Lox class declaration.
8
+ class LoxClassStmt < LoxNode
8
9
  # @return [String] the class name
9
10
  attr_reader :name
10
11
 
@@ -14,11 +15,13 @@ module Loxxy
14
15
  # @return [Array<Ast::LoxFunStmt>] the methods
15
16
  attr_reader :body
16
17
 
18
+ # Constructor for a parse node that represents a Lox function declaration
17
19
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
18
- # @param condExpr [Loxxy::Ast::LoxNode] iteration condition
19
- # @param theBody [Array<Loxxy::Ast::LoxNode>]
20
+ # @param aName [String] the class name
21
+ # @param aSuperclassName [String] the super class name
22
+ # @param theMethods [Array<Loxxy::Ast::LoxFunStmt>] the methods
20
23
  def initialize(aPosition, aName, aSuperclassName, theMethods)
21
- super(aPosition, [])
24
+ super(aPosition)
22
25
  @name = aName.dup
23
26
  @superclass = aSuperclassName
24
27
  @body = theMethods
@@ -4,17 +4,25 @@ require_relative 'lox_node'
4
4
 
5
5
  module Loxxy
6
6
  module Ast
7
- # rubocop: disable Style/AccessorGrouping
7
+ # A parse tree node that represents a Lox function declaration.
8
8
  class LoxFunStmt < LoxNode
9
+ # @return [String] the function name
9
10
  attr_reader :name
11
+
12
+ # @return [Array<String>] the parameter names
10
13
  attr_reader :params
14
+
15
+ # @return [Ast::LoxBlockStmt] the parse tree representing the function's body
11
16
  attr_reader :body
17
+
18
+ # @return [Boolean] true if the function is a method
12
19
  attr_accessor :is_method
13
20
 
21
+ # Constructor for a parse node that represents a Lox function declaration
14
22
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
15
- # @param aName [String]
16
- # @param arguments [Array<String>]
17
- # @param body [Ast::LoxBlockStmt]
23
+ # @param aName [String] the function name
24
+ # @param paramList [Array<String>] the parameter names
25
+ # @param aBody [Ast::LoxBlockStmt] the parse tree representing the function's body
18
26
  def initialize(aPosition, aName, paramList, aBody)
19
27
  super(aPosition)
20
28
  @name = aName.dup
@@ -25,6 +33,5 @@ module Loxxy
25
33
 
26
34
  define_accept # Add `accept` method as found in Visitor design pattern
27
35
  end # class
28
- # rubocop: enable Style/AccessorGrouping
29
36
  end # module
30
37
  end # module
@@ -4,17 +4,20 @@ require_relative 'lox_compound_expr'
4
4
 
5
5
  module Loxxy
6
6
  module Ast
7
- class LoxSetExpr < LoxCompoundExpr
7
+ class LoxSetExpr < LoxNode
8
8
  # @return [Ast::LoxNode] the object to which the property belongs to
9
9
  attr_reader :object
10
10
 
11
11
  # @return [String] Name of an object property
12
12
  attr_accessor :property
13
13
 
14
+ # @return [LoxNode, Datatype] value to assign
15
+ attr_accessor :value
16
+
14
17
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
15
18
  # @param anObject [Ast::LoxNode] The object which the given property is being set
16
19
  def initialize(aPosition, anObject)
17
- super(aPosition, [])
20
+ super(aPosition)
18
21
  @object = anObject
19
22
  end
20
23
 
@@ -20,7 +20,7 @@ module Loxxy
20
20
  # @return [BackEnd::SymbolTable]
21
21
  attr_reader :symbol_table
22
22
 
23
- # @return [Array<Datatype::BuiltinDatatype>] Data stack for the expression results
23
+ # @return [Array<Datatype::BuiltinDatatype>] Data stack for arguments and return results
24
24
  attr_reader :stack
25
25
 
26
26
  # @return [Hash { Symbol => UnaryOperator}]
@@ -35,17 +35,31 @@ module Loxxy
35
35
  # @param theOptions [Hash]
36
36
  def initialize(theOptions)
37
37
  @config = theOptions
38
+ @istream = config.include?(:istream) ? config[:istream] : $stdin
38
39
  @ostream = config.include?(:ostream) ? config[:ostream] : $stdout
39
40
  @symbol_table = SymbolTable.new
40
41
  @stack = []
41
- @unary_operators = {}
42
- @binary_operators = {}
43
42
 
43
+ reset_expr_stack
44
44
  init_unary_operators
45
45
  init_binary_operators
46
46
  init_globals
47
47
  end
48
48
 
49
+ # Returns the current environment
50
+ # @return [Loxxy::BackEnd::Environment]
51
+ def current_env
52
+ symbol_table.current_env
53
+ end
54
+
55
+ def expr_stack
56
+ current_env.expr_stack
57
+ end
58
+
59
+ def reset_expr_stack
60
+ current_env.expr_stack.clear
61
+ end
62
+
49
63
  # Given an abstract syntax parse tree visitor, launch the visit
50
64
  # and execute the visit events in the output stream.
51
65
  # @param aVisitor [AST::ASTVisitor]
@@ -58,7 +72,7 @@ module Loxxy
58
72
  aVisitor.subscribe(self)
59
73
  aVisitor.start
60
74
  aVisitor.unsubscribe(self)
61
- stack.empty? ? Datatype::Nil.instance : stack.pop
75
+ expr_stack.empty? ? Datatype::Nil.instance : expr_stack.pop
62
76
  end
63
77
 
64
78
  ##########################################################################
@@ -69,10 +83,14 @@ module Loxxy
69
83
  # Do nothing, subnodes were already evaluated
70
84
  end
71
85
 
86
+ def before_class_stmt(_class_stmt)
87
+ reset_expr_stack
88
+ end
89
+
72
90
  def after_class_stmt(aClassStmt, aVisitor)
73
91
  if aClassStmt.superclass
74
92
  aClassStmt.superclass.accept(aVisitor)
75
- parent = stack.pop
93
+ parent = expr_stack.pop
76
94
  unless parent.kind_of?(LoxClass)
77
95
  raise Loxxy::RuntimeError, 'Superclass must be a class.'
78
96
  end
@@ -89,7 +107,7 @@ module Loxxy
89
107
  meths = aClassStmt.body.map do |func_node|
90
108
  func_node.is_method = true
91
109
  func_node.accept(aVisitor)
92
- mth = stack.pop
110
+ mth = expr_stack.pop
93
111
  mth.is_initializer = true if mth.name == 'init'
94
112
  mth
95
113
  end
@@ -104,11 +122,15 @@ module Loxxy
104
122
  symbol_table.insert(new_var)
105
123
  end
106
124
 
125
+ def before_var_stmt(_var_stmt)
126
+ reset_expr_stack
127
+ end
128
+
107
129
  def after_var_stmt(aVarStmt)
108
130
  new_var = Variable.new(aVarStmt.name, Datatype::Nil.instance)
109
131
  symbol_table.insert(new_var)
110
132
 
111
- value = stack.pop
133
+ value = expr_stack.pop
112
134
  new_var.assign(value)
113
135
  end
114
136
 
@@ -116,9 +138,14 @@ module Loxxy
116
138
  before_block_stmt(aForStmt)
117
139
  end
118
140
 
141
+ def before_if_stmt(_if_stmt)
142
+ reset_expr_stack
143
+ end
144
+
119
145
  def after_if_stmt(anIfStmt, aVisitor)
120
146
  # Retrieve the result of the condition evaluation
121
- condition = stack.pop
147
+ # condition = stack.pop
148
+ condition = expr_stack.pop
122
149
  if condition.truthy?
123
150
  anIfStmt.then_stmt.accept(aVisitor)
124
151
  elsif anIfStmt.else_stmt
@@ -126,18 +153,31 @@ module Loxxy
126
153
  end
127
154
  end
128
155
 
156
+ def before_print_stmt(_print_stmt)
157
+ reset_expr_stack
158
+ end
159
+
129
160
  def after_print_stmt(_printStmt)
130
- tos = stack.pop
161
+ tos = expr_stack.pop
131
162
  @ostream.print tos ? tos.to_str : 'nil'
132
163
  end
133
164
 
165
+ def before_return_stmt(_return_stmt)
166
+ reset_expr_stack
167
+ end
168
+
134
169
  def after_return_stmt(_returnStmt, _aVisitor)
170
+ stack.push(expr_stack.pop)
135
171
  throw(:return)
136
172
  end
137
173
 
174
+ def before_while_stmt(_while_stmt)
175
+ reset_expr_stack
176
+ end
177
+
138
178
  def after_while_stmt(aWhileStmt, aVisitor)
139
179
  loop do
140
- condition = stack.pop
180
+ condition = expr_stack.pop
141
181
  break unless condition.truthy?
142
182
 
143
183
  aWhileStmt.body.accept(aVisitor)
@@ -146,6 +186,7 @@ module Loxxy
146
186
  end
147
187
 
148
188
  def before_block_stmt(_aBlockStmt)
189
+ reset_expr_stack
149
190
  new_env = Environment.new
150
191
  symbol_table.enter_environment(new_env)
151
192
  end
@@ -159,40 +200,42 @@ module Loxxy
159
200
  variable = variable_lookup(anAssignExpr)
160
201
  raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless variable
161
202
 
162
- value = stack.last # ToS remains since an assignment produces a value
203
+ value = expr_stack.last # ToS remains since an assignment produces a value
163
204
  variable.assign(value)
164
205
  end
165
206
 
166
- def before_set_expr(aSetExpr, aVisitor)
167
- # Evaluate receiver object part
168
- aSetExpr.object.accept(aVisitor)
207
+ def before_set_expr(_set_expr, _visitor)
208
+ reset_expr_stack
169
209
  end
170
210
 
171
- def after_set_expr(aSetExpr, _visitor)
172
- value = stack.pop
173
- assignee = stack.pop
211
+ def after_set_expr(aSetExpr, aVisitor)
212
+ # Evaluate receiver object part (i.e. 'this')
213
+ aSetExpr.object.accept(aVisitor)
214
+ assignee = expr_stack.pop
174
215
  unless assignee.kind_of?(LoxInstance)
175
216
  raise Loxxy::RuntimeError, 'Only instances have fields.'
176
217
  end
177
218
 
219
+ aSetExpr.value.accept(aVisitor)
220
+ value = expr_stack.pop
221
+
178
222
  assignee.set(aSetExpr.property, value)
179
- stack.push value
223
+ expr_stack.push value
180
224
  end
181
225
 
182
226
  def after_logical_expr(aLogicalExpr, visitor)
183
227
  op = aLogicalExpr.operator
184
- operand1 = stack.pop # only first operand was evaluated
228
+ operand1 = expr_stack.pop # only first operand was evaluated
185
229
  result = nil
186
230
  if ((op == :and) && operand1.falsey?) || ((op == :or) && operand1.truthy?)
187
231
  result = operand1
188
232
  else
189
233
  raw_operand2 = aLogicalExpr.subnodes[1]
190
234
  raw_operand2.accept(visitor) # Visit means operand2 is evaluated
191
- operand2 = stack.pop
235
+ operand2 = expr_stack.pop
192
236
  result = logical_2nd_arg(operand2)
193
237
  end
194
-
195
- stack.push result
238
+ expr_stack.push result
196
239
  end
197
240
 
198
241
  def logical_2nd_arg(operand2)
@@ -212,14 +255,14 @@ module Loxxy
212
255
  end
213
256
 
214
257
  def after_binary_expr(aBinaryExpr)
215
- operand2 = stack.pop
216
- operand1 = stack.pop
258
+ operand2 = expr_stack.pop
259
+ operand1 = expr_stack.pop
217
260
  op = aBinaryExpr.operator
218
261
  operator = binary_operators[op]
219
262
  operator.validate_operands(operand1, operand2)
220
263
  if operand1.respond_to?(op)
221
264
  result = operand1.send(op, operand2)
222
- stack.push convert2lox_datatype(result)
265
+ expr_stack.push convert2lox_datatype(result)
223
266
  else
224
267
  msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
225
268
  raise StandardError, msg1
@@ -227,13 +270,13 @@ module Loxxy
227
270
  end
228
271
 
229
272
  def after_unary_expr(anUnaryExpr)
230
- operand = stack.pop
273
+ operand = expr_stack.pop
231
274
  op = anUnaryExpr.operator
232
275
  operator = unary_operators[op]
233
276
  operator.validate_operand(operand)
234
277
  if operand.respond_to?(op)
235
278
  result = operand.send(op)
236
- stack.push convert2lox_datatype(result)
279
+ expr_stack.push convert2lox_datatype(result)
237
280
  else
238
281
  msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
239
282
  raise StandardError, msg1
@@ -243,12 +286,17 @@ module Loxxy
243
286
  def after_call_expr(aCallExpr, aVisitor)
244
287
  # Evaluate callee part
245
288
  aCallExpr.callee.accept(aVisitor)
246
- callee = stack.pop
289
+ callee = expr_stack.pop
290
+ before_size = expr_stack.size
247
291
  aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
292
+ after_size = expr_stack.size
293
+ if after_size > before_size
294
+ stack.concat(expr_stack.pop(after_size - before_size))
295
+ end
248
296
 
249
297
  case callee
250
298
  when NativeFunction
251
- stack.push callee.call # Pass arguments
299
+ expr_stack.push callee.call # Pass arguments
252
300
  when LoxFunction, LoxClass
253
301
  arg_count = aCallExpr.arguments.size
254
302
  if arg_count != callee.arity
@@ -263,12 +311,12 @@ module Loxxy
263
311
 
264
312
  def after_get_expr(aGetExpr, aVisitor)
265
313
  aGetExpr.object.accept(aVisitor)
266
- instance = stack.pop
314
+ instance = expr_stack.pop
267
315
  unless instance.kind_of?(LoxInstance)
268
316
  raise Loxxy::RuntimeError, 'Only instances have properties.'
269
317
  end
270
318
 
271
- stack.push instance.get(aGetExpr.property)
319
+ expr_stack.push instance.get(aGetExpr.property)
272
320
  end
273
321
 
274
322
  def after_grouping_expr(_groupingExpr)
@@ -278,14 +326,17 @@ module Loxxy
278
326
  def after_variable_expr(aVarExpr, aVisitor)
279
327
  var_name = aVarExpr.name
280
328
  var = variable_lookup(aVarExpr)
281
- raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless var
329
+ unless var
330
+ pos = "line #{aVarExpr.position.line}:#{aVarExpr.position.column}"
331
+ raise Loxxy::RuntimeError, "[#{pos}] Undefined variable '#{var_name}'."
332
+ end
282
333
 
283
334
  var.value.accept(aVisitor) # Evaluate variable value then push on stack
284
335
  end
285
336
 
286
337
  # @param literalExpr [Ast::LoxLiteralExpr]
287
338
  def before_literal_expr(literalExpr)
288
- stack.push(literalExpr.literal)
339
+ expr_stack.push(literalExpr.literal)
289
340
  end
290
341
 
291
342
  def after_this_expr(aThisExpr, aVisitor)
@@ -304,18 +355,22 @@ module Loxxy
304
355
  raise Loxxy::RuntimeError, "Undefined property '#{aSuperExpr.property}'."
305
356
  end
306
357
 
307
- stack.push method.bind(instance)
358
+ expr_stack.push method.bind(instance)
308
359
  end
309
360
 
310
361
  # @param aValue [Ast::BuiltinDattype] the built-in datatype value
311
362
  def before_visit_builtin(aValue)
312
- stack.push(aValue)
363
+ expr_stack.push(aValue)
364
+ end
365
+
366
+ def before_fun_stmt(_fun_stmt, _visitor)
367
+ reset_expr_stack
313
368
  end
314
369
 
315
370
  def after_fun_stmt(aFunStmt, _visitor)
316
371
  function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
317
372
  if aFunStmt.is_method
318
- stack.push function
373
+ expr_stack.push function
319
374
  else
320
375
  new_var = Variable.new(aFunStmt.name, function)
321
376
  symbol_table.insert(new_var)
@@ -337,21 +392,9 @@ module Loxxy
337
392
  env.defns[aVarNode.name]
338
393
  end
339
394
 
340
- NativeFunction = Struct.new(:callable, :interp) do
341
- def accept(_visitor)
342
- interp.stack.push self
343
- end
344
-
345
- def call
346
- callable.call
347
- end
348
-
349
- def to_str
350
- '<native fn>'
351
- end
352
- end
353
-
354
395
  def init_unary_operators
396
+ @unary_operators = {}
397
+
355
398
  negate_op = UnaryOperator.new('-', [Datatype::Number])
356
399
  unary_operators[:-@] = negate_op
357
400
 
@@ -361,6 +404,8 @@ module Loxxy
361
404
  end
362
405
 
363
406
  def init_binary_operators
407
+ @binary_operators = {}
408
+
364
409
  plus_op = BinaryOperator.new('+', [[Datatype::Number, :idem],
365
410
  [Datatype::LXString, :idem]])
366
411
  binary_operators[:+] = plus_op
@@ -395,6 +440,24 @@ module Loxxy
395
440
 
396
441
  def init_globals
397
442
  add_native_fun('clock', native_clock)
443
+ add_native_fun('getc', native_getc)
444
+ add_native_fun('chr', native_chr)
445
+ add_native_fun('exit', native_exit)
446
+ add_native_fun('print_error', native_print_error)
447
+ end
448
+
449
+ NativeFunction = Struct.new(:callable, :interp) do
450
+ def accept(_visitor)
451
+ interp.expr_stack.push self
452
+ end
453
+
454
+ def call
455
+ callable.call
456
+ end
457
+
458
+ def to_str
459
+ '<native fn>'
460
+ end
398
461
  end
399
462
 
400
463
  def add_native_fun(aName, aProc)
@@ -411,6 +474,40 @@ module Loxxy
411
474
  end
412
475
  end
413
476
 
477
+ # Read a single character and return the character code as an integer.
478
+ # LoxLox requires the end of input to be a negative number
479
+ def native_getc
480
+ proc do
481
+ ch = @istream.getc
482
+ val = ch ? ch.codepoints[0] : -1
483
+ Datatype::Number.new(val)
484
+ end
485
+ end
486
+
487
+ # chr(ch): Convert given character code number to a single-character string.
488
+ def native_chr
489
+ proc do
490
+ codepoint = stack.pop
491
+ Datatype::LXString.new(codepoint.value.chr)
492
+ end
493
+ end
494
+
495
+ # exit(status): Exit with given status code.
496
+ def native_exit
497
+ proc do
498
+ status = stack.pop
499
+ exit(status.value)
500
+ end
501
+ end
502
+
503
+ # print_error(message): Print message string on stderr.
504
+ def native_print_error
505
+ proc do
506
+ message = stack.pop
507
+ $stderr.print message.value
508
+ end
509
+ end
510
+
414
511
  def convert2lox_datatype(item)
415
512
  case item
416
513
  when TrueClass then Datatype::True.instance