loxxy 0.1.0 → 0.1.05

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42a88690625576399255ae7fadd3ac6aee022fc8d56a8e10c1206b55eca207ab
4
- data.tar.gz: 4042d268f199763405a264480765bbd5fe7aeaa998eb5e0a4353ae702c3bef2a
3
+ metadata.gz: c8eae41b5b093b55c02a81bbe81b08202eb4ecd64c1dd288246c0cd4af44c627
4
+ data.tar.gz: 208158a80e89e789d74c66f295ee4cbec6d37e6d9f3185dedfbcaa30a156fe35
5
5
  SHA512:
6
- metadata.gz: 21a6a82509ead9c0d9f38c6798147dda38c436d87b8800a33ca7027553631866ce4215f2eb90c482f3c5a9b0bb42e99b7868fca888b78fcf7eca67afcd7db756
7
- data.tar.gz: 71b53af0df630894c0d45639a84f427f34c7b92a5092e07cea16bc3786af666be13b0035a13c07b2910c374c76caa009c3acdbd675f430c8f6e4b7951c2b68a7
6
+ metadata.gz: dc1393417a3cdb013fdbcc085dcb3ba7dda45784e73a26ef44c1ee21b4322a9a18063d16c0cae7d3266be829ce31c6090773ba1631712211c388d84b21e0161d
7
+ data.tar.gz: 2f07cb2e882fbff31b73eee169a99849729b8f4136888335b2915a183ff32ed8aa78a66d93ea7765f0b09f0747b4b09cfae2aece5ca7478f41892b173a0ce2ab
data/CHANGELOG.md CHANGED
@@ -1,3 +1,53 @@
1
+ ## [0.1.05] - 2021-03-05
2
+ - Test for Fibbonacci recursive function is now passing.
3
+
4
+ ### Fixed
5
+ - Method `BackEnd::Function#call` a call doesn't no more generate of TWO scopes
6
+
7
+ ## [0.1.04] - 2021-02-28
8
+
9
+ ### Added
10
+ - Class `Ast::LoxReturnStmt` a node that represents a return statement
11
+ - Method `Ast::ASTBuilder#reduce_return_stmt`
12
+ - Method `Ast::ASTVisitor#visit_return_stmt` for visiting an `Ast::LoxReturnStmt` node
13
+ - Method `BackEnd::Engine#after_return_stmt` to handle return statement
14
+ - Method `BackEnd::Function#!` implementing the logical negation of a function (as value).
15
+ - Test suite for logical operators (in project repository)
16
+ - Test suite for block code
17
+ - Test suite for call and function declaration (initial)
18
+
19
+ ### Changed
20
+ - Method `BackEnd::Engine#after_call_expr` now generate a `catch` and `throw` events
21
+
22
+ ## [0.1.03] - 2021-02-26
23
+ - Runtime argument checking for arithmetic and comparison operators
24
+
25
+ ### Added
26
+ - Test suite for arithmetic and comparison operators (in project repository)
27
+ - Class `BackEnd::UnaryOperator`: runtime argument validation
28
+ - Class `BackEnd::BinaryOperator`: runtime argument validation
29
+
30
+ ### Changed
31
+ - File `console` renamed to `loxxy`. Very basic command-line interface.
32
+ - Custom exception classes
33
+ - File `README.md` updated list of supported `Lox` keywords.
34
+
35
+
36
+ ## [0.1.02] - 2021-02-21
37
+ - Function definition and call documented in `README.md`
38
+
39
+ ### Changed
40
+ - File `README.md` updated description of function definition and function call.
41
+
42
+ ### Fixed
43
+ - Method `BackEnd::Engine#after_print_stmt` now handles of empty stack or nil data.
44
+ - Method `BackEnd::Engine#after_call_expr` was pushing one spurious item onto data stack.
45
+
46
+ ## [0.1.01] - 2021-02-20
47
+ ### Fixed
48
+ - Fixed most offences for Rubocop.
49
+
50
+
1
51
  ## [0.1.00] - 2021-02-20
2
52
  - Version number bumped, `Loxxy` supports function definitions
3
53
 
@@ -192,11 +242,11 @@
192
242
  - The interpreter can evaluate substraction between two numbers.
193
243
 
194
244
  ### Added
195
- - Method `Datatype::Number#-` implmenting the subtraction operation
245
+ - Method `Datatype::Number#-` implementing the subtraction operation
196
246
 
197
247
  ### Changed
198
248
  - File `README.md` minor editorial changes.
199
- - File `lx_string_spec.rb` Added test for string concatentation
249
+ - File `lx_string_spec.rb` Added test for string concatenation
200
250
  - File `number_spec.rb` Added tests for addition and subtraction operations
201
251
  - File `interpreter_spec.rb` Added tests for subtraction operation
202
252
 
data/README.md CHANGED
@@ -14,14 +14,13 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
14
14
 
15
15
  ### Current status
16
16
  The project is still in inception and the interpreter is being implemented...
17
- Currently it can execute all allowed __Lox__ expressions and statement except:
18
- - Functions and closures,
17
+ Currently it can execute all allowed __Lox__ expressions and statements except:
18
+ - Closures,
19
19
  - Classes and objects.
20
20
 
21
21
  These will be implemented soon.
22
22
 
23
23
 
24
-
25
24
  ## What's the fuss about Lox?
26
25
  ... Nothing...
27
26
  Bob Nystrom designed a language __simple__ enough so that he could present
@@ -36,11 +35,11 @@ Although __Lox__ is fairly simple, it is far from a toy language:
36
35
  - Functions and closures
37
36
  - Object-orientation (classes, methods, inheritance).
38
37
 
39
- In other words, __Lox__ contains interesting features expected from most general-purpose
38
+ In other words, __Lox__ contains interesting features found in most general-purpose
40
39
  languages.
41
40
 
42
41
  ### What's missing in Lox?
43
- __Lox__ was constrained by design and therefore was not aimed to be a language used in real-world applications.
42
+ __Lox__ was constrained by design and was therefore not aimed to be a language used in real-world applications.
44
43
  Here are some missing parts to make it a _practical_ language:
45
44
  - Collections (arrays, maps, ...)
46
45
  - Modules (importing stuff from other packages/files)
@@ -70,6 +69,23 @@ lox = Loxxy::Interpreter.new
70
69
  lox.evaluate(lox_program) # Output: Hello, world!
71
70
  ```
72
71
 
72
+ ## A function definition example
73
+ ```ruby
74
+ require 'loxxy'
75
+
76
+ lox_program = <<LOX_END
77
+ fun add4(n) {
78
+ n + 4;
79
+ }
80
+
81
+ print add4(6); // Output: 10
82
+ LOX_END
83
+
84
+ lox = Loxxy::Interpreter.new
85
+ lox.evaluate(lox_program) # Output 10
86
+ ```
87
+
88
+
73
89
  ## Retrieving the result from a Lox program
74
90
  The __Loxxy__ interpreter returns the value of the last evaluated expression.
75
91
 
@@ -151,6 +167,7 @@ Here are the language features currently supported by the interpreter:
151
167
  - [Print Statement](#print-statement)
152
168
  - [While Statement](#while-statement)
153
169
  - [Block Statement](#block-statement)
170
+ - [Function declaration](#func-statement)
154
171
 
155
172
  ### Comments
156
173
 
@@ -163,7 +180,8 @@ Loxxy supports single line C-style comments.
163
180
  ### Keywords
164
181
  Loxxy implements the following __Lox__ reserved keywords:
165
182
  ```lang-none
166
- and, false, nil, or, print, true
183
+ and, else, false, for, fun, if,
184
+ nil, or, print, true, var, while
167
185
  ```
168
186
 
169
187
  ### Datatypes
@@ -177,19 +195,21 @@ loxxy supports all the standard __Lox__ datatypes:
177
195
  ### Statements
178
196
 
179
197
  Loxxy supports the following statements:
180
- - [Expressions](#expressions)
198
+ - [Expressions](#expressions)
181
199
  -[Arithmetic expressions](#arithmetic-expressions)
182
200
  -[String concatenation](#string-concatenation)
183
201
  -[Comparison expressions](#comparison-expressions)
184
202
  -[Logical expressions](#logical-expressions)
185
203
  -[Grouping expressions](#grouping-expressions)
186
- -[Variable expressions and assignments](#variable-expressions)
187
-
204
+ -[Variable expressions and assignments](#variable-expressions)
205
+ -[Function call](#function-call)
206
+
188
207
  -[Variable declarations](#var-statement)
189
208
  -[If Statement](#if-statement)
190
209
  -[Print Statement](#print-statement)
191
- -[While Statement](#while-statement)
192
- -[Block Statement](#block-statement)
210
+ -[While Statement](#while-statement)
211
+ -[Block Statement](#block-statement)
212
+ -[Function Declaration](#function-declaration)
193
213
 
194
214
  #### Expressions
195
215
 
@@ -284,6 +304,15 @@ var iAmNil; // __Lox__ initializes variables to nil by default;
284
304
  print iAmNil; // output: nil
285
305
  ```
286
306
 
307
+ #### Function call
308
+ ``` javascript
309
+ // Calling a function without argument
310
+ print clock();
311
+
312
+ // Assumption: there exists a function `add` that takes two arguments
313
+ print add(2, 3);
314
+ ```
315
+
287
316
  #### For statement
288
317
 
289
318
  Similar to the `for` statement in `C` language
@@ -362,6 +391,19 @@ var a = "outer";
362
391
  print a; // output: outer
363
392
  ```
364
393
 
394
+ #### Function Declaration
395
+ The keyword `fun` is used to begin a function declaration.
396
+ In __Lox__ a function has a name and a body (which may be empty).
397
+
398
+ ``` javascript
399
+ fun add4(n) // `add4` will be the name of the function
400
+ {
401
+ n + 4;
402
+ }
403
+
404
+ print add4(6); // output: 10
405
+ ```
406
+
365
407
  ## Installation
366
408
 
367
409
  Add this line to your application's Gemfile:
data/bin/loxxy ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'loxxy'
5
+
6
+ if ARGV[0]
7
+ lox = Loxxy::Interpreter.new
8
+ File.open(ARGV[0], 'r') do |f|
9
+ lox.evaluate(f.read)
10
+ end
11
+ end
data/lib/loxxy.rb CHANGED
@@ -6,8 +6,6 @@ require_relative 'loxxy/front_end/raw_parser'
6
6
 
7
7
  # Namespace for all classes and constants of __loxxy__ gem.
8
8
  module Loxxy
9
- class Error < StandardError; end
10
-
11
9
  # Shorthand method. Returns the sole object that represents
12
10
  # a Lox false literal.
13
11
  # @return [Loxxy::Datatype::False]
@@ -12,6 +12,7 @@ require_relative 'lox_logical_expr'
12
12
  require_relative 'lox_assign_expr'
13
13
  require_relative 'lox_block_stmt'
14
14
  require_relative 'lox_while_stmt'
15
+ require_relative 'lox_return_stmt'
15
16
  require_relative 'lox_print_stmt'
16
17
  require_relative 'lox_if_stmt'
17
18
  require_relative 'lox_for_stmt'
@@ -221,6 +221,11 @@ module Loxxy
221
221
  Ast::LoxPrintStmt.new(tokens[1].position, theChildren[1])
222
222
  end
223
223
 
224
+ # rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
225
+ def reduce_return_stmt(_production, _range, tokens, theChildren)
226
+ Ast::LoxReturnStmt.new(tokens[1].position, theChildren[1])
227
+ end
228
+
224
229
  # rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement').as ''
225
230
  def reduce_while_stmt(_production, _range, tokens, theChildren)
226
231
  Ast::LoxWhileStmt.new(tokens[1].position, theChildren[2], theChildren[4])
@@ -233,7 +238,7 @@ module Loxxy
233
238
  end
234
239
 
235
240
  # rule('block' => 'LEFT_BRACE RIGHT_BRACE').as 'block_empty'
236
- def reduce_block_empty(_production, _range, tokens, theChildren)
241
+ def reduce_block_empty(_production, _range, tokens, _children)
237
242
  Ast::LoxBlockStmt.new(tokens[0].position, nil)
238
243
  end
239
244
 
@@ -298,7 +303,7 @@ module Loxxy
298
303
  def reduce_function(_production, _range, _tokens, theChildren)
299
304
  first_child = theChildren.first
300
305
  pos = first_child.token.position
301
- fun_stmt = LoxFunStmt.new(pos, first_child.token.lexeme, theChildren[2], theChildren[4])
306
+ LoxFunStmt.new(pos, first_child.token.lexeme, theChildren[2], theChildren[4])
302
307
  end
303
308
 
304
309
  # rule('parameters' => 'parameters COMMA IDENTIFIER')
@@ -91,6 +91,14 @@ module Loxxy
91
91
  broadcast(:after_print_stmt, aPrintStmt)
92
92
  end
93
93
 
94
+ # Visit event. The visitor is about to visit a return statement.
95
+ # @param aReturnStmt [AST::LOXReturnStmt] the return statement node to visit
96
+ def visit_return_stmt(aReturnStmt)
97
+ broadcast(:before_return_stmt, aReturnStmt)
98
+ traverse_subnodes(aReturnStmt)
99
+ broadcast(:after_return_stmt, aReturnStmt, self)
100
+ end
101
+
94
102
  # Visit event. The visitor is about to visit a while statement node.
95
103
  # @param aWhileStmt [AST::LOXWhileStmt] the while statement node to visit
96
104
  def visit_while_stmt(aWhileStmt)
@@ -4,6 +4,7 @@ require_relative 'lox_compound_expr'
4
4
 
5
5
  module Loxxy
6
6
  module Ast
7
+ # rubocop: disable Style/AccessorGrouping
7
8
  class LoxFunStmt < LoxCompoundExpr
8
9
  attr_reader :name
9
10
  attr_reader :params
@@ -28,5 +29,6 @@ module Loxxy
28
29
 
29
30
  alias operands subnodes
30
31
  end # class
32
+ # rubocop: enable Style/AccessorGrouping
31
33
  end # module
32
34
  end # module
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_compound_expr'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxReturnStmt < LoxCompoundExpr
8
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
9
+ # @param anExpression [Ast::LoxNode] expression to return
10
+ def initialize(aPosition, anExpression)
11
+ super(aPosition, [anExpression])
12
+ end
13
+
14
+ # Part of the 'visitee' role in Visitor design pattern.
15
+ # @param visitor [Ast::ASTVisitor] the visitor
16
+ def accept(visitor)
17
+ visitor.visit_return_stmt(self)
18
+ end
19
+ end # class
20
+ end # module
21
+ end # module
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../error'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ Signature = Struct.new(:parameter_types)
8
+
9
+ # A Lox binary operator
10
+ class BinaryOperator
11
+ # @return [String] text representation of the operator
12
+ attr_reader :name
13
+
14
+ # @return [Array<Class>]
15
+ attr_reader :signatures
16
+
17
+ # @param aName [String] "name" of operator
18
+ # @param theSignatures [Array<Signature>] allowed signatures
19
+ def initialize(aName, theSignatures)
20
+ @name = aName
21
+ @signatures = theSignatures
22
+ end
23
+
24
+ # rubocop: disable Style/ClassEqualityComparison
25
+ def validate_operands(operand1, operand2)
26
+ compliant = signatures.find do |(type1, type2)|
27
+ next unless operand1.kind_of?(type1)
28
+
29
+ if type2 == :idem
30
+ (operand2.class == operand1.class)
31
+ else
32
+ operand2.kind_of?(type2)
33
+ end
34
+ end
35
+ # rubocop: enable Style/ClassEqualityComparison
36
+
37
+ unless compliant
38
+ err = Loxxy::RuntimeError
39
+ if signatures.size == 1 && signatures[0].last == :idem
40
+ raise err, "Operands must be #{datatype_name(signatures[0].first)}s."
41
+ elsif signatures.size == 2 && signatures.all? { |(_, second)| second == :idem }
42
+ type1 = datatype_name(signatures[0].first)
43
+ type2 = datatype_name(signatures[1].first)
44
+ raise err, "Operands must be two #{type1}s or two #{type2}s."
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def datatype_name(aClass)
52
+ # (?:(?:[^:](?!:|(?<=LX))))+$
53
+ aClass.name.sub(/^.+(?=::)::(?:LX)?/, '').downcase
54
+ end
55
+ end # class
56
+ end # module
57
+ end # module
@@ -2,8 +2,10 @@
2
2
 
3
3
  # Load all the classes implementing AST nodes
4
4
  require_relative '../ast/all_lox_nodes'
5
+ require_relative 'binary_operator'
5
6
  require_relative 'function'
6
7
  require_relative 'symbol_table'
8
+ require_relative 'unary_operator'
7
9
 
8
10
  module Loxxy
9
11
  module BackEnd
@@ -17,16 +19,26 @@ module Loxxy
17
19
  # @return [BackEnd::SymbolTable]
18
20
  attr_reader :symbol_table
19
21
 
20
- # @return [Array<Datatype::BuiltinDatatyp>] Stack for the values of expr
22
+ # @return [Array<Datatype::BuiltinDatatype>] Data stack for the expression results
21
23
  attr_reader :stack
22
24
 
25
+ # @return [Hash { Symbol => UnaryOperator}]
26
+ attr_reader :unary_operators
27
+
28
+ # @return [Hash { Symbol => BinaryOperator}]
29
+ attr_reader :binary_operators
30
+
23
31
  # @param theOptions [Hash]
24
32
  def initialize(theOptions)
25
33
  @config = theOptions
26
34
  @ostream = config.include?(:ostream) ? config[:ostream] : $stdout
27
35
  @symbol_table = SymbolTable.new
28
36
  @stack = []
37
+ @unary_operators = {}
38
+ @binary_operators = {}
29
39
 
40
+ init_unary_operators
41
+ init_binary_operators
30
42
  init_globals
31
43
  end
32
44
 
@@ -83,7 +95,11 @@ module Loxxy
83
95
 
84
96
  def after_print_stmt(_printStmt)
85
97
  tos = stack.pop
86
- @ostream.print tos.to_str
98
+ @ostream.print tos ? tos.to_str : 'nil'
99
+ end
100
+
101
+ def after_return_stmt(_returnStmt, _aVisitor)
102
+ throw(:return)
87
103
  end
88
104
 
89
105
  def after_while_stmt(aWhileStmt, aVisitor)
@@ -148,9 +164,11 @@ module Loxxy
148
164
  end
149
165
 
150
166
  def after_binary_expr(aBinaryExpr)
151
- op = aBinaryExpr.operator
152
167
  operand2 = stack.pop
153
168
  operand1 = stack.pop
169
+ op = aBinaryExpr.operator
170
+ operator = binary_operators[op]
171
+ operator.validate_operands(operand1, operand2)
154
172
  if operand1.respond_to?(op)
155
173
  stack.push operand1.send(op, operand2)
156
174
  else
@@ -160,12 +178,14 @@ module Loxxy
160
178
  end
161
179
 
162
180
  def after_unary_expr(anUnaryExpr)
163
- op = anUnaryExpr.operator
164
181
  operand = stack.pop
182
+ op = anUnaryExpr.operator
183
+ operator = unary_operators[op]
184
+ operator.validate_operand(operand)
165
185
  if operand.respond_to?(op)
166
186
  stack.push operand.send(op)
167
187
  else
168
- msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
188
+ msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
169
189
  raise StandardError, msg1
170
190
  end
171
191
  end
@@ -176,21 +196,21 @@ module Loxxy
176
196
  callee = stack.pop
177
197
  aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
178
198
 
179
- if callee.kind_of?(NativeFunction)
199
+ case callee
200
+ when NativeFunction
180
201
  stack.push callee.call # Pass arguments
202
+ when Function
203
+ callee.call(self, aVisitor)
181
204
  else
182
- new_env = Environment.new(symbol_table.current_env)
183
- symbol_table.enter_environment(new_env)
184
- callee.parameters&.each do |param_name|
185
- local = Variable.new(param_name, stack.pop)
186
- symbol_table.insert(local)
187
- end
188
- stack.push callee.call(aVisitor)
189
-
190
- symbol_table.leave_environment
205
+ raise Loxxy::RuntimeError, 'Can only call functions and classes.'
191
206
  end
192
207
  end
193
208
 
209
+ def complete_call
210
+ callee = ret_stack.pop
211
+ symbol_table.leave_environment if callee.kind_of?(Function)
212
+ end
213
+
194
214
  def after_grouping_expr(_groupingExpr)
195
215
  # Do nothing: work was already done by visiting /evaluating the subexpression
196
216
  end
@@ -213,7 +233,7 @@ module Loxxy
213
233
  stack.push(aValue)
214
234
  end
215
235
 
216
- def after_fun_stmt(aFunStmt, aVisitor)
236
+ def after_fun_stmt(aFunStmt, _visitor)
217
237
  function = Function.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, stack)
218
238
  new_var = Variable.new(aFunStmt.name, function)
219
239
  symbol_table.insert(new_var)
@@ -235,6 +255,48 @@ module Loxxy
235
255
  end
236
256
  end
237
257
 
258
+ def init_unary_operators
259
+ negate_op = UnaryOperator.new('-', [Datatype::Number])
260
+ unary_operators[:-@] = negate_op
261
+
262
+ negation_op = UnaryOperator.new('!', [Datatype::BuiltinDatatype,
263
+ BackEnd::Function])
264
+ unary_operators[:!] = negation_op
265
+ end
266
+
267
+ def init_binary_operators
268
+ plus_op = BinaryOperator.new('+', [[Datatype::Number, :idem],
269
+ [Datatype::LXString, :idem]])
270
+ binary_operators[:+] = plus_op
271
+
272
+ minus_op = BinaryOperator.new('-', [[Datatype::Number, :idem]])
273
+ binary_operators[:-] = minus_op
274
+
275
+ star_op = BinaryOperator.new('*', [[Datatype::Number, :idem]])
276
+ binary_operators[:*] = star_op
277
+
278
+ slash_op = BinaryOperator.new('/', [[Datatype::Number, :idem]])
279
+ binary_operators[:/] = slash_op
280
+
281
+ equal_equal_op = BinaryOperator.new('==', [[Datatype::BuiltinDatatype, Datatype::BuiltinDatatype]])
282
+ binary_operators[:==] = equal_equal_op
283
+
284
+ not_equal_op = BinaryOperator.new('!=', [[Datatype::BuiltinDatatype, Datatype::BuiltinDatatype]])
285
+ binary_operators[:!=] = not_equal_op
286
+
287
+ less_op = BinaryOperator.new('<', [[Datatype::Number, :idem]])
288
+ binary_operators[:<] = less_op
289
+
290
+ less_equal_op = BinaryOperator.new('<=', [[Datatype::Number, :idem]])
291
+ binary_operators[:<=] = less_equal_op
292
+
293
+ greater_op = BinaryOperator.new('>', [[Datatype::Number, :idem]])
294
+ binary_operators[:>] = greater_op
295
+
296
+ greater_equal_op = BinaryOperator.new('>=', [[Datatype::Number, :idem]])
297
+ binary_operators[:>=] = greater_equal_op
298
+ end
299
+
238
300
  def init_globals
239
301
  add_native_fun('clock', native_clock)
240
302
  end
@@ -4,18 +4,16 @@ require_relative '../datatype/all_datatypes'
4
4
 
5
5
  module Loxxy
6
6
  module BackEnd
7
+ # rubocop: disable Style/AccessorGrouping
7
8
  # Representation of a Lox function.
8
9
  # It is a named slot that can be associated with a value at the time.
9
10
  class Function
10
-
11
11
  # @return [String]
12
12
  attr_reader :name
13
13
 
14
14
  # @return [Array<>] the parameters
15
15
  attr_reader :parameters
16
-
17
16
  attr_reader :body
18
-
19
17
  attr_reader :stack
20
18
 
21
19
  # Create a variable with given name and initial value
@@ -24,7 +22,7 @@ module Loxxy
24
22
  def initialize(aName, parameterList, aBody, aStack)
25
23
  @name = aName.dup
26
24
  @parameters = parameterList
27
- @body = aBody
25
+ @body = aBody.kind_of?(Ast::LoxNoopExpr) ? aBody : aBody.subnodes[0]
28
26
  @stack = aStack
29
27
  end
30
28
 
@@ -32,8 +30,28 @@ module Loxxy
32
30
  stack.push self
33
31
  end
34
32
 
35
- def call(aVisitor)
36
- body.empty? ? Datatype::Nil.instance : body.accept(aVisitor)
33
+ def call(engine, aVisitor)
34
+ new_env = Environment.new(engine.symbol_table.current_env)
35
+ engine.symbol_table.enter_environment(new_env)
36
+
37
+ parameters&.each do |param_name|
38
+ local = Variable.new(param_name, stack.pop)
39
+ engine.symbol_table.insert(local)
40
+ end
41
+
42
+ catch(:return) do
43
+ (body.nil? || body.kind_of?(Ast::LoxNoopExpr)) ? Datatype::Nil.instance : body.accept(aVisitor)
44
+ throw(:return)
45
+ end
46
+
47
+ engine.symbol_table.leave_environment
48
+ end
49
+
50
+ # Logical negation.
51
+ # As a function is a truthy thing, its negation is thus false.
52
+ # @return [Datatype::False]
53
+ def !
54
+ Datatype::False.instance
37
55
  end
38
56
 
39
57
  # Text representation of a Lox function
@@ -41,5 +59,6 @@ module Loxxy
41
59
  "<fn #{name}>"
42
60
  end
43
61
  end # class
62
+ # rubocop: enable Style/AccessorGrouping
44
63
  end # module
45
64
  end # module
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../error'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ # A Lox unary operator
8
+ class UnaryOperator
9
+ # @return [String] text representation of the operator
10
+ attr_reader :name
11
+
12
+ # @return [Array<Class>]
13
+ attr_reader :signatures
14
+
15
+ # @param aName [String] "name" of operator
16
+ # @param theSignatures [Array<Class>] allowed signatures
17
+ def initialize(aName, theSignatures)
18
+ @name = aName
19
+ @signatures = theSignatures
20
+ end
21
+
22
+ def validate_operand(operand1)
23
+ compliant = signatures.find { |some_type| operand1.kind_of?(some_type) }
24
+
25
+ unless compliant
26
+ err = Loxxy::RuntimeError
27
+ # if signatures.size == 1
28
+ raise err, "Operand must be a #{datatype_name(signatures[0])}."
29
+ # end
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def datatype_name(aClass)
36
+ # (?:(?:[^:](?!:|(?<=LX))))+$
37
+ aClass.name.sub(/^.+(?=::)::(?:LX)?/, '').downcase
38
+ end
39
+ end # class
40
+ end # module
41
+ end # module
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Loxxy
4
+ # Abstract class. Generalization of Loxxy error classes.
5
+ class Error < StandardError; end
6
+
7
+ # Error occurring while Loxxy executes some invalid Lox code.
8
+ class RuntimeError < Error; end
9
+ end
@@ -74,7 +74,7 @@ module Loxxy
74
74
  rule('elsePart_opt' => [])
75
75
 
76
76
  rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
77
- rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
77
+ rule('returnStmt' => 'RETURN expression_opt SEMICOLON').as 'return_stmt'
78
78
  rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement').as 'while_stmt'
79
79
  rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE').as 'block_stmt'
80
80
  rule('block' => 'LEFT_BRACE RIGHT_BRACE').as 'block_empty'
@@ -92,6 +92,7 @@ module Loxxy
92
92
 
93
93
  private
94
94
 
95
+ # rubocop: disable Lint/DuplicateBranch
95
96
  def _next_token
96
97
  skip_intertoken_spaces
97
98
  curr_ch = scanner.peek(1)
@@ -125,6 +126,7 @@ module Loxxy
125
126
 
126
127
  return token
127
128
  end
129
+ # rubocop: enable Lint/DuplicateBranch
128
130
 
129
131
  def build_token(aSymbolName, aLexeme)
130
132
  begin
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.0'
4
+ VERSION = '0.1.05'
5
5
  end
data/loxxy.gemspec CHANGED
@@ -46,8 +46,8 @@ Gem::Specification.new do |spec|
46
46
  spec.license = 'MIT'
47
47
  spec.required_ruby_version = '~> 2.4'
48
48
 
49
- spec.bindir = 'exe'
50
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
49
+ spec.bindir = 'bin'
50
+ spec.executables = ['loxxy']
51
51
  spec.require_paths = ['lib']
52
52
 
53
53
  PkgExtending.pkg_files(spec)
@@ -9,6 +9,7 @@ require_relative '../lib/loxxy/interpreter'
9
9
  module Loxxy
10
10
  # This spec contains the bare bones test for the Interpreter class.
11
11
  # The execution of Lox code is tested elsewhere.
12
+ # rubocop: disable Metrics/BlockLength
12
13
  describe Interpreter do
13
14
  let(:sample_cfg) do
14
15
  { ostream: StringIO.new }
@@ -423,10 +424,34 @@ LOX_END
423
424
  expect(sample_cfg[:ostream].string).to eq('<fn foo><native fn>')
424
425
  end
425
426
 
427
+ it 'should support return statements' do
428
+ program = <<-LOX_END
429
+ fun max(a, b) {
430
+ if (a > b) return a;
431
+
432
+ return b;
433
+ }
434
+
435
+ max(3, 2);
436
+ LOX_END
437
+ result = subject.evaluate(program)
438
+ expect(result).to eq(3)
439
+ end
440
+
426
441
  it 'should print the hello world message' do
427
442
  expect { subject.evaluate(hello_world) }.not_to raise_error
428
443
  expect(sample_cfg[:ostream].string).to eq('Hello, world!')
429
444
  end
430
445
  end # context
446
+
447
+ context 'Test suite:' do
448
+ it "should complain if one argument isn't a number" do
449
+ source = '1 + nil;'
450
+ err = Loxxy::RuntimeError
451
+ err_msg = 'Operands must be two numbers or two strings.'
452
+ expect { subject.evaluate(source) }.to raise_error(err, err_msg)
453
+ end
454
+ end # context
431
455
  end # describe
456
+ # rubocop: enable Metrics/BlockLength
432
457
  end # module
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.0
4
+ version: 0.1.05
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-20 00:00:00.000000000 Z
11
+ date: 2021-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -69,7 +69,8 @@ dependencies:
69
69
  description: An implementation of the Lox programming language. WIP
70
70
  email:
71
71
  - famished.tiger@yahoo.com
72
- executables: []
72
+ executables:
73
+ - loxxy
73
74
  extensions: []
74
75
  extra_rdoc_files:
75
76
  - README.md
@@ -83,6 +84,7 @@ files:
83
84
  - LICENSE.txt
84
85
  - README.md
85
86
  - Rakefile
87
+ - bin/loxxy
86
88
  - lib/loxxy.rb
87
89
  - lib/loxxy/ast/all_lox_nodes.rb
88
90
  - lib/loxxy/ast/ast_builder.rb
@@ -101,16 +103,19 @@ files:
101
103
  - lib/loxxy/ast/lox_node.rb
102
104
  - lib/loxxy/ast/lox_noop_expr.rb
103
105
  - lib/loxxy/ast/lox_print_stmt.rb
106
+ - lib/loxxy/ast/lox_return_stmt.rb
104
107
  - lib/loxxy/ast/lox_seq_decl.rb
105
108
  - lib/loxxy/ast/lox_unary_expr.rb
106
109
  - lib/loxxy/ast/lox_var_stmt.rb
107
110
  - lib/loxxy/ast/lox_variable_expr.rb
108
111
  - lib/loxxy/ast/lox_while_stmt.rb
112
+ - lib/loxxy/back_end/binary_operator.rb
109
113
  - lib/loxxy/back_end/engine.rb
110
114
  - lib/loxxy/back_end/entry.rb
111
115
  - lib/loxxy/back_end/environment.rb
112
116
  - lib/loxxy/back_end/function.rb
113
117
  - lib/loxxy/back_end/symbol_table.rb
118
+ - lib/loxxy/back_end/unary_operator.rb
114
119
  - lib/loxxy/back_end/variable.rb
115
120
  - lib/loxxy/datatype/all_datatypes.rb
116
121
  - lib/loxxy/datatype/boolean.rb
@@ -120,6 +125,7 @@ files:
120
125
  - lib/loxxy/datatype/nil.rb
121
126
  - lib/loxxy/datatype/number.rb
122
127
  - lib/loxxy/datatype/true.rb
128
+ - lib/loxxy/error.rb
123
129
  - lib/loxxy/front_end/grammar.rb
124
130
  - lib/loxxy/front_end/literal.rb
125
131
  - lib/loxxy/front_end/parser.rb