loxxy 0.1.01 → 0.1.06

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: c477fc7db959df775fc4656e21d577dbb3fce65724b07c8a7ec020496c196100
4
- data.tar.gz: f941317a01b0dbcdca250697a4e84b88f85b572fa7f4f67be3de7c162bc8e2cb
3
+ metadata.gz: 293bf5f6fb0eb5a1daf2723484a2867909c03cc99b20270e562d288d334f0bdc
4
+ data.tar.gz: 9e26854c6d4334f9869d0cd420ebd4365c0f978666e7ede8310d25e600cf5834
5
5
  SHA512:
6
- metadata.gz: b379767ee5cdf986a1b5e704c0254bb8901123811504ba58c59d3996897c29413e84298f6d3c65db849ad289183261d30657e1605d076c4ab9a8e9b0c38318da
7
- data.tar.gz: 258f5a4d501bedbbec5f235ca7d858a4195c9088dcef8d63a6a16b9c25306167eecf93ff3b51aafc8ecf17fd6ed40f9dfe220797a033021f377eb62200de99c3
6
+ metadata.gz: 2ac692f16a41b162001fee869d426abf634f1a2bb82bf17eac26d8c35939a8ca63234246a6ff88cc04db3833057b445e2650773652ce102a0dcc4f3aa90182e8
7
+ data.tar.gz: 2764cb3d1703fdc948180cb2060f93a0ef72148f2877f5b711ecacd17d2ba52a9d3b9f7736eb53130850635cb0d127f2a5161a8361cb4a2268fc6275d15a6b1c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,57 @@
1
+ ## [0.1.06] - 2021-03-06
2
+ - Parameters/arguments checks in function declaration and call
3
+
4
+ ### Changed
5
+ - Method `Ast::AstBuilder#reduce_call_arglist` raises a `Loxxy::RuntimeError` when more than 255 arguments are used.
6
+ - Method `BackEnd::Engine#after_call_expr` raises a `Loxxy::RuntimeError` when argument count doesn't match the arity of function.
7
+
8
+ - Class `BackEnd::Function` renamed to `LoxFunction`
9
+
10
+ ## [0.1.05] - 2021-03-05
11
+ - Test for Fibbonacci recursive function is now passing.
12
+
13
+ ### Fixed
14
+ - Method `BackEnd::Function#call` a call doesn't no more generate of TWO scopes
15
+
16
+ ## [0.1.04] - 2021-02-28
17
+
18
+ ### Added
19
+ - Class `Ast::LoxReturnStmt` a node that represents a return statement
20
+ - Method `Ast::ASTBuilder#reduce_return_stmt`
21
+ - Method `Ast::ASTVisitor#visit_return_stmt` for visiting an `Ast::LoxReturnStmt` node
22
+ - Method `BackEnd::Engine#after_return_stmt` to handle return statement
23
+ - Method `BackEnd::Function#!` implementing the logical negation of a function (as value).
24
+ - Test suite for logical operators (in project repository)
25
+ - Test suite for block code
26
+ - Test suite for call and function declaration (initial)
27
+
28
+ ### Changed
29
+ - Method `BackEnd::Engine#after_call_expr` now generate a `catch` and `throw` events
30
+
31
+ ## [0.1.03] - 2021-02-26
32
+ - Runtime argument checking for arithmetic and comparison operators
33
+
34
+ ### Added
35
+ - Test suite for arithmetic and comparison operators (in project repository)
36
+ - Class `BackEnd::UnaryOperator`: runtime argument validation
37
+ - Class `BackEnd::BinaryOperator`: runtime argument validation
38
+
39
+ ### Changed
40
+ - File `console` renamed to `loxxy`. Very basic command-line interface.
41
+ - Custom exception classes
42
+ - File `README.md` updated list of supported `Lox` keywords.
43
+
44
+
45
+ ## [0.1.02] - 2021-02-21
46
+ - Function definition and call documented in `README.md`
47
+
48
+ ### Changed
49
+ - File `README.md` updated description of function definition and function call.
50
+
51
+ ### Fixed
52
+ - Method `BackEnd::Engine#after_print_stmt` now handles of empty stack or nil data.
53
+ - Method `BackEnd::Engine#after_call_expr` was pushing one spurious item onto data stack.
54
+
1
55
  ## [0.1.01] - 2021-02-20
2
56
  ### Fixed
3
57
  - Fixed most offences for Rubocop.
@@ -197,11 +251,11 @@
197
251
  - The interpreter can evaluate substraction between two numbers.
198
252
 
199
253
  ### Added
200
- - Method `Datatype::Number#-` implmenting the subtraction operation
254
+ - Method `Datatype::Number#-` implementing the subtraction operation
201
255
 
202
256
  ### Changed
203
257
  - File `README.md` minor editorial changes.
204
- - File `lx_string_spec.rb` Added test for string concatentation
258
+ - File `lx_string_spec.rb` Added test for string concatenation
205
259
  - File `number_spec.rb` Added tests for addition and subtraction operations
206
260
  - File `interpreter_spec.rb` Added tests for subtraction operation
207
261
 
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])
@@ -268,7 +273,7 @@ module Loxxy
268
273
  def reduce_call_arglist(_production, _range, tokens, theChildren)
269
274
  args = theChildren[1] || []
270
275
  if args.size > 255
271
- raise StandardError, "Can't have more than 255 arguments."
276
+ raise Loxxy::RuntimeError, "Can't have more than 255 arguments."
272
277
  end
273
278
 
274
279
  LoxCallExpr.new(tokens[0].position, args)
@@ -298,6 +303,10 @@ module Loxxy
298
303
  def reduce_function(_production, _range, _tokens, theChildren)
299
304
  first_child = theChildren.first
300
305
  pos = first_child.token.position
306
+ if theChildren[2] && theChildren[2].size > 255
307
+ msg = "Can't have more than 255 parameters."
308
+ raise Loxxy::SyntaxError, msg
309
+ end
301
310
  LoxFunStmt.new(pos, first_child.token.lexeme, theChildren[2], theChildren[4])
302
311
  end
303
312
 
@@ -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)
@@ -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 'function'
5
+ require_relative 'binary_operator'
6
+ require_relative 'lox_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,18 +196,18 @@ 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
181
- 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)
202
+ when LoxFunction
203
+ arg_count = aCallExpr.arguments.size
204
+ if arg_count != callee.arity
205
+ msg = "Expected #{callee.arity} arguments but got #{arg_count}."
206
+ raise Loxxy::RuntimeError, msg
187
207
  end
188
- stack.push callee.call(aVisitor)
189
-
190
- symbol_table.leave_environment
208
+ callee.call(self, aVisitor)
209
+ else
210
+ raise Loxxy::RuntimeError, 'Can only call functions and classes.'
191
211
  end
192
212
  end
193
213
 
@@ -214,7 +234,7 @@ module Loxxy
214
234
  end
215
235
 
216
236
  def after_fun_stmt(aFunStmt, _visitor)
217
- function = Function.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, stack)
237
+ function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, stack)
218
238
  new_var = Variable.new(aFunStmt.name, function)
219
239
  symbol_table.insert(new_var)
220
240
  end
@@ -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::LoxFunction])
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
@@ -7,7 +7,7 @@ module Loxxy
7
7
  # rubocop: disable Style/AccessorGrouping
8
8
  # Representation of a Lox function.
9
9
  # It is a named slot that can be associated with a value at the time.
10
- class Function
10
+ class LoxFunction
11
11
  # @return [String]
12
12
  attr_reader :name
13
13
 
@@ -22,16 +22,40 @@ module Loxxy
22
22
  def initialize(aName, parameterList, aBody, aStack)
23
23
  @name = aName.dup
24
24
  @parameters = parameterList
25
- @body = aBody
25
+ @body = aBody.kind_of?(Ast::LoxNoopExpr) ? aBody : aBody.subnodes[0]
26
26
  @stack = aStack
27
27
  end
28
28
 
29
+ def arity
30
+ parameters ? parameters.size : 0
31
+ end
32
+
29
33
  def accept(_visitor)
30
34
  stack.push self
31
35
  end
32
36
 
33
- def call(aVisitor)
34
- body.empty? ? Datatype::Nil.instance : body.accept(aVisitor)
37
+ def call(engine, aVisitor)
38
+ new_env = Environment.new(engine.symbol_table.current_env)
39
+ engine.symbol_table.enter_environment(new_env)
40
+
41
+ parameters&.each do |param_name|
42
+ local = Variable.new(param_name, stack.pop)
43
+ engine.symbol_table.insert(local)
44
+ end
45
+
46
+ catch(:return) do
47
+ (body.nil? || body.kind_of?(Ast::LoxNoopExpr)) ? Datatype::Nil.instance : body.accept(aVisitor)
48
+ throw(:return)
49
+ end
50
+
51
+ engine.symbol_table.leave_environment
52
+ end
53
+
54
+ # Logical negation.
55
+ # As a function is a truthy thing, its negation is thus false.
56
+ # @return [Datatype::False]
57
+ def !
58
+ Datatype::False.instance
35
59
  end
36
60
 
37
61
  # Text representation of a Lox function
@@ -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,12 @@
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
+
10
+ # Error occurring while Loxxy parses some invalid Lox code.
11
+ class SyntaxError < Error; end
12
+ 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.01'
4
+ VERSION = '0.1.06'
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)
@@ -424,11 +424,34 @@ LOX_END
424
424
  expect(sample_cfg[:ostream].string).to eq('<fn foo><native fn>')
425
425
  end
426
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
+
427
441
  it 'should print the hello world message' do
428
442
  expect { subject.evaluate(hello_world) }.not_to raise_error
429
443
  expect(sample_cfg[:ostream].string).to eq('Hello, world!')
430
444
  end
431
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
432
455
  end # describe
433
456
  # rubocop: enable Metrics/BlockLength
434
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.01
4
+ version: 0.1.06
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-06 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
- - lib/loxxy/back_end/function.rb
116
+ - lib/loxxy/back_end/lox_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