loxxy 0.1.06 → 0.1.07

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: 293bf5f6fb0eb5a1daf2723484a2867909c03cc99b20270e562d288d334f0bdc
4
- data.tar.gz: 9e26854c6d4334f9869d0cd420ebd4365c0f978666e7ede8310d25e600cf5834
3
+ metadata.gz: f171f0046ff95784ed21b369751ca65d25e0ff757d1b2dccc534087a4b9c4a8d
4
+ data.tar.gz: 579badf6a30436962cb18e63dfee1ed361cfd152e9fecf54f2bfd25395db086b
5
5
  SHA512:
6
- metadata.gz: 2ac692f16a41b162001fee869d426abf634f1a2bb82bf17eac26d8c35939a8ca63234246a6ff88cc04db3833057b445e2650773652ce102a0dcc4f3aa90182e8
7
- data.tar.gz: 2764cb3d1703fdc948180cb2060f93a0ef72148f2877f5b711ecacd17d2ba52a9d3b9f7736eb53130850635cb0d127f2a5161a8361cb4a2268fc6275d15a6b1c
6
+ metadata.gz: 0470f022b121ceaff279f75024e07b78a218c3baa4d44e6f9bf54f4ebddf18341b27f697fe68c727eb2d61add2c55e7c102fbf73bfd29ef4fb3965024247f747
7
+ data.tar.gz: 320dbb075351069f624103f1d93aadf903ec6a5b7bb685871f53227b496760ac67ae9fa1dae85491efd9b94cfeb90eac8f66e104b4f70eaac9b0da081dfde567
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [0.1.07] - 2021-03-14
2
+ - `Loxxy` now supports nested functions and closures
3
+
4
+ ### Changed
5
+ - Method `Ast::AstBuilder#reduce_call_expr` now supports nested call expressions (e.g. `getCallback()();` )
6
+ - Class `BackEnd::Environment`: added the attributes `predecessor` and `embedding` to support closures.
7
+ - Class `BackeEnd::LoxFunction`: added the attribute `closure` that is equal to the environment where the function is declared.
8
+ - Constructor `BackEnd::LoxFunction#new` now takes a `BackEnd::Engine`as its fourth parameter
9
+ - Methods `BackEnd::SymbolTable#enter_environment`, `BackEnd::SymbolTable#leave_environment` take into account closures.
10
+
11
+ ### Fixed
12
+ - Method `Ast::AstBuilder#after_var_stmt` now takes into account the value from the top of stack
13
+
14
+
15
+
16
+
1
17
  ## [0.1.06] - 2021-03-06
2
18
  - Parameters/arguments checks in function declaration and call
3
19
 
data/README.md CHANGED
@@ -15,7 +15,6 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
15
15
  ### Current status
16
16
  The project is still in inception and the interpreter is being implemented...
17
17
  Currently it can execute all allowed __Lox__ expressions and statements except:
18
- - Closures,
19
18
  - Classes and objects.
20
19
 
21
20
  These will be implemented soon.
@@ -260,13 +260,26 @@ module Loxxy
260
260
 
261
261
  # rule('call' => 'primary refinement_plus').as 'call_expr'
262
262
  def reduce_call_expr(_production, _range, _tokens, theChildren)
263
- theChildren[1].callee = theChildren[0]
264
- theChildren[1]
263
+ members = theChildren.flatten
264
+ call_expr = nil
265
+ loop do
266
+ (callee, call_expr) = members.shift(2)
267
+ call_expr.callee = callee
268
+ members.unshift(call_expr)
269
+ break if members.size == 1
270
+ end
271
+
272
+ call_expr
273
+ end
274
+
275
+ # rule('refinement_plus' => 'refinement_plus refinement')
276
+ def reduce_refinement_plus_more(_production, _range, _tokens, theChildren)
277
+ theChildren[0] << theChildren[1]
265
278
  end
266
279
 
267
280
  # rule('refinement_plus' => 'refinement').
268
281
  def reduce_refinement_plus_end(_production, _range, _tokens, theChildren)
269
- theChildren[0]
282
+ theChildren
270
283
  end
271
284
 
272
285
  # rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN')
@@ -62,7 +62,7 @@ module Loxxy
62
62
  end
63
63
 
64
64
  def after_var_stmt(aVarStmt)
65
- new_var = Variable.new(aVarStmt.name, aVarStmt.subnodes[0])
65
+ new_var = Variable.new(aVarStmt.name, stack.pop)
66
66
  symbol_table.insert(new_var)
67
67
  end
68
68
 
@@ -126,9 +126,8 @@ module Loxxy
126
126
  variable = symbol_table.lookup(var_name)
127
127
  raise StandardError, "Unknown variable #{var_name}" unless variable
128
128
 
129
- value = stack.pop
129
+ value = stack.last # ToS remains since an assignment produces a value
130
130
  variable.assign(value)
131
- stack.push value # An expression produces a value
132
131
  end
133
132
 
134
133
  def after_logical_expr(aLogicalExpr, visitor)
@@ -234,7 +233,7 @@ module Loxxy
234
233
  end
235
234
 
236
235
  def after_fun_stmt(aFunStmt, _visitor)
237
- function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, stack)
236
+ function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
238
237
  new_var = Variable.new(aFunStmt.name, function)
239
238
  symbol_table.insert(new_var)
240
239
  end
@@ -9,10 +9,17 @@ module Loxxy
9
9
  # of a relation or a relation definition.
10
10
  # It contains a map of names to the objects they name (e.g. logical var)
11
11
  class Environment
12
- # The enclosing (parent) environment.
12
+ # The enclosing (parent) environment.
13
13
  # @return [Environment, NilClass]
14
14
  attr_accessor :enclosing
15
15
 
16
+ # The previous environment in the environment chain.
17
+ # @return [Environment, NilClass]
18
+ attr_accessor :predecessor
19
+
20
+ # @return [Boolean] true if this environment is part of a closure (contains an embedded function)
21
+ attr_accessor :embedding
22
+
16
23
  # Mapping from user-defined name to related definition
17
24
  # (say, a variable object)
18
25
  # @return [Hash{String => Variable}] Pairs of the kind
@@ -8,22 +8,24 @@ module Loxxy
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
10
  class LoxFunction
11
- # @return [String]
11
+ # @return [String] The name of the function (if any)
12
12
  attr_reader :name
13
13
 
14
14
  # @return [Array<>] the parameters
15
15
  attr_reader :parameters
16
16
  attr_reader :body
17
17
  attr_reader :stack
18
+ attr_reader :closure
18
19
 
19
- # Create a variable with given name and initial value
20
+ # Create a function with given name
20
21
  # @param aName [String] The name of the variable
21
- # @param aValue [Datatype::BuiltinDatatype] the initial assigned value
22
- def initialize(aName, parameterList, aBody, aStack)
22
+ def initialize(aName, parameterList, aBody, anEngine)
23
23
  @name = aName.dup
24
24
  @parameters = parameterList
25
25
  @body = aBody.kind_of?(Ast::LoxNoopExpr) ? aBody : aBody.subnodes[0]
26
- @stack = aStack
26
+ @stack = anEngine.stack
27
+ @closure = anEngine.symbol_table.current_env
28
+ anEngine.symbol_table.current_env.embedding = true
27
29
  end
28
30
 
29
31
  def arity
@@ -35,7 +37,8 @@ module Loxxy
35
37
  end
36
38
 
37
39
  def call(engine, aVisitor)
38
- new_env = Environment.new(engine.symbol_table.current_env)
40
+ # new_env = Environment.new(engine.symbol_table.current_env)
41
+ new_env = Environment.new(closure)
39
42
  engine.symbol_table.enter_environment(new_env)
40
43
 
41
44
  parameters&.each do |param_name|
@@ -44,23 +44,33 @@ module Loxxy
44
44
  # to be a child of current environment and to be itself the new current environment.
45
45
  # @param anEnv [BackEnd::Environment] the Environment that
46
46
  def enter_environment(anEnv)
47
- anEnv.enclosing = current_env
47
+ if anEnv.enclosing && (anEnv.enclosing != current_env)
48
+ anEnv.predecessor = current_env
49
+ else
50
+ anEnv.enclosing = current_env
51
+ end
48
52
  @current_env = anEnv
49
53
  end
50
54
 
51
55
  def leave_environment
52
- current_env.defns.each_pair do |nm, _item|
53
- environments = name2envs[nm]
54
- if environments.size == 1
55
- name2envs.delete(nm)
56
- else
57
- environments.pop
58
- name2envs[nm] = environments
56
+ unless current_env.embedding
57
+ current_env.defns.each_pair do |nm, _item|
58
+ environments = name2envs[nm]
59
+ if environments.size == 1
60
+ name2envs.delete(nm)
61
+ else
62
+ environments.pop
63
+ name2envs[nm] = environments
64
+ end
59
65
  end
60
66
  end
61
67
  raise StandardError, 'Cannot remove root environment.' if current_env == root
62
68
 
63
- @current_env = current_env.enclosing
69
+ if current_env.predecessor
70
+ @current_env = current_env.predecessor
71
+ else
72
+ @current_env = current_env.enclosing
73
+ end
64
74
  end
65
75
 
66
76
  # Add an entry with given name to current environment.
@@ -127,7 +127,7 @@ module Loxxy
127
127
  rule('unaryOp' => 'MINUS')
128
128
  rule('call' => 'primary')
129
129
  rule('call' => 'primary refinement_plus').as 'call_expr'
130
- rule('refinement_plus' => 'refinement_plus refinement') # .as 'refinement_plus_more'
130
+ rule('refinement_plus' => 'refinement_plus refinement').as 'refinement_plus_more'
131
131
  rule('refinement_plus' => 'refinement').as 'refinement_plus_end'
132
132
  rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN').as 'call_arglist'
133
133
  rule('refinement' => 'DOT IDENTIFIER')
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.06'
4
+ VERSION = '0.1.07'
5
5
  end
@@ -36,6 +36,8 @@ module Loxxy
36
36
 
37
37
 
38
38
  it "should react to 'after_var_stmt' event" do
39
+ # Precondition: value to assign is on top of stack
40
+ subject.stack.push(greeting)
39
41
  expect { subject.after_var_stmt(var_decl) }.not_to raise_error
40
42
  current_env = subject.symbol_table.current_env
41
43
  expect(current_env.defns['greeting']).to be_kind_of(Variable)
@@ -53,15 +53,6 @@ module Loxxy
53
53
  end # context
54
54
 
55
55
  context 'Evaluating Lox code:' do
56
- let(:hello_world) do
57
- lox = <<-LOX_END
58
- var greeting = "Hello"; // Declaring a variable
59
- print greeting + ", " + "world!"; // ... Playing with concatenation
60
- LOX_END
61
-
62
- lox
63
- end
64
-
65
56
  it 'should evaluate core data types' do
66
57
  result = subject.evaluate('true; // Not false')
67
58
  expect(result).to be_kind_of(Loxxy::Datatype::True)
@@ -269,9 +260,9 @@ LOX_END
269
260
 
270
261
  it 'should accept variable mention' do
271
262
  program = <<-LOX_END
272
- var foo = "bar";
273
- print foo; // => bar
274
- LOX_END
263
+ var foo = "bar";
264
+ print foo; // => bar
265
+ LOX_END
275
266
  expect { subject.evaluate(program) }.not_to raise_error
276
267
  expect(sample_cfg[:ostream].string).to eq('bar')
277
268
  end
@@ -438,18 +429,35 @@ LOX_END
438
429
  expect(result).to eq(3)
439
430
  end
440
431
 
441
- it 'should print the hello world message' do
442
- expect { subject.evaluate(hello_world) }.not_to raise_error
443
- expect(sample_cfg[:ostream].string).to eq('Hello, world!')
432
+ # rubocop: disable Style/StringConcatenation
433
+ it 'should support local functions and closures' do
434
+ program = <<-LOX_END
435
+ fun makeCounter() {
436
+ var i = 0;
437
+ fun count() {
438
+ i = i + 1;
439
+ print i;
440
+ }
441
+
442
+ return count;
443
+ }
444
+
445
+ var counter = makeCounter();
446
+ counter(); // "1".
447
+ counter(); // "2".
448
+ LOX_END
449
+ expect { subject.evaluate(program) }.not_to raise_error
450
+ expect(sample_cfg[:ostream].string).to eq('1' + '2')
444
451
  end
445
- end # context
452
+ # rubocop: enable Style/StringConcatenation
446
453
 
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)
454
+ it 'should print the hello world message' do
455
+ program = <<-LOX_END
456
+ var greeting = "Hello"; // Declaring a variable
457
+ print greeting + ", " + "world!"; // ... Playing with concatenation
458
+ LOX_END
459
+ expect { subject.evaluate(program) }.not_to raise_error
460
+ expect(sample_cfg[:ostream].string).to eq('Hello, world!')
453
461
  end
454
462
  end # context
455
463
  end # describe
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.06
4
+ version: 0.1.07
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-03-06 00:00:00.000000000 Z
11
+ date: 2021-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley