loxxy 0.0.24 → 0.1.0

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.
@@ -9,9 +9,9 @@ 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 parent (enclosing) environment.
12
+ # The enclosing (parent) environment.
13
13
  # @return [Environment, NilClass]
14
- attr_accessor :parent
14
+ attr_accessor :enclosing
15
15
 
16
16
  # Mapping from user-defined name to related definition
17
17
  # (say, a variable object)
@@ -21,7 +21,7 @@ module Loxxy
21
21
  # Construct a environment instance.
22
22
  # @param aParent [Environment, NilClass] Parent environment to this one.
23
23
  def initialize(aParent = nil)
24
- @parent = aParent
24
+ @enclosing = aParent
25
25
  @defns = {}
26
26
  end
27
27
 
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../datatype/all_datatypes'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ # Representation of a Lox function.
8
+ # It is a named slot that can be associated with a value at the time.
9
+ class Function
10
+
11
+ # @return [String]
12
+ attr_reader :name
13
+
14
+ # @return [Array<>] the parameters
15
+ attr_reader :parameters
16
+
17
+ attr_reader :body
18
+
19
+ attr_reader :stack
20
+
21
+ # Create a variable with given name and initial value
22
+ # @param aName [String] The name of the variable
23
+ # @param aValue [Datatype::BuiltinDatatype] the initial assigned value
24
+ def initialize(aName, parameterList, aBody, aStack)
25
+ @name = aName.dup
26
+ @parameters = parameterList
27
+ @body = aBody
28
+ @stack = aStack
29
+ end
30
+
31
+ def accept(_visitor)
32
+ stack.push self
33
+ end
34
+
35
+ def call(aVisitor)
36
+ body.empty? ? Datatype::Nil.instance : body.accept(aVisitor)
37
+ end
38
+
39
+ # Text representation of a Lox function
40
+ def to_str
41
+ "<fn #{name}>"
42
+ end
43
+ end # class
44
+ end # module
45
+ end # module
@@ -44,7 +44,7 @@ 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.parent = current_env
47
+ anEnv.enclosing = current_env
48
48
  @current_env = anEnv
49
49
  end
50
50
 
@@ -60,7 +60,7 @@ module Loxxy
60
60
  end
61
61
  raise StandardError, 'Cannot remove root environment.' if current_env == root
62
62
 
63
- @current_env = current_env.parent
63
+ @current_env = current_env.enclosing
64
64
  end
65
65
 
66
66
  # Add an entry with given name to current environment.
@@ -114,7 +114,7 @@ module Loxxy
114
114
  while skope
115
115
  vars_of_environment = skope.defns.select { |_, item| item.kind_of?(Variable) }
116
116
  vars = vars_of_environment.values.concat(vars)
117
- skope = skope.parent
117
+ skope = skope.enclosing
118
118
  end
119
119
 
120
120
  vars
@@ -44,7 +44,7 @@ module Loxxy
44
44
  rule('function_star' => 'function_star function')
45
45
  rule('function_star' => [])
46
46
 
47
- rule('funDecl' => 'FUN function')
47
+ rule('funDecl' => 'FUN function').as 'fun_decl'
48
48
 
49
49
  rule('varDecl' => 'VAR IDENTIFIER SEMICOLON').as 'var_declaration'
50
50
  rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON').as 'var_initialization'
@@ -60,12 +60,12 @@ module Loxxy
60
60
 
61
61
  rule('exprStmt' => 'expression SEMICOLON').as 'exprStmt'
62
62
 
63
- rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
64
- rule('forControl' => 'forInitialization forTest forUpdate')
63
+ rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement').as 'for_stmt'
64
+ rule('forControl' => 'forInitialization forTest forUpdate').as 'for_control'
65
65
  rule('forInitialization' => 'varDecl')
66
66
  rule('forInitialization' => 'exprStmt')
67
- rule('forInitialization' => 'SEMICOLON')
68
- rule('forTest' => 'expression_opt SEMICOLON')
67
+ rule('forInitialization' => 'SEMICOLON').as 'empty_for_initialization'
68
+ rule('forTest' => 'expression_opt SEMICOLON').as 'for_test'
69
69
  rule('forUpdate' => 'expression_opt')
70
70
 
71
71
  rule('ifStmt' => 'IF ifCondition statement elsePart_opt').as 'if_stmt'
@@ -75,9 +75,9 @@ module Loxxy
75
75
 
76
76
  rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
77
77
  rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
78
- rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement')
79
- rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE')
80
- rule('block' => 'LEFT_BRACE RIGHT_BRACE')
78
+ rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement').as 'while_stmt'
79
+ rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE').as 'block_stmt'
80
+ rule('block' => 'LEFT_BRACE RIGHT_BRACE').as 'block_empty'
81
81
 
82
82
  # Expressions: produce values
83
83
  rule('expression_opt' => 'expression')
@@ -88,37 +88,37 @@ module Loxxy
88
88
  rule('owner_opt' => 'call DOT')
89
89
  rule('owner_opt' => [])
90
90
  rule('logic_or' => 'logic_and')
91
- rule('logic_or' => 'logic_and disjunct_plus').as 'logic_or_plus'
92
- rule('disjunct_plus' => 'disjunct_plus OR logic_and').as 'logic_or_plus_more'
93
- rule('disjunct_plus' => 'OR logic_and').as 'logic_or_plus_end'
91
+ rule('logic_or' => 'logic_and disjunct_plus').as 'logical_expr'
92
+ rule('disjunct_plus' => 'disjunct_plus OR logic_and').as 'binary_plus_more'
93
+ rule('disjunct_plus' => 'OR logic_and').as 'binary_plus_end'
94
94
  rule('logic_and' => 'equality')
95
- rule('logic_and' => 'equality conjunct_plus').as 'logic_and_plus'
96
- rule('conjunct_plus' => 'conjunct_plus AND equality').as 'logic_and_plus_more'
97
- rule('conjunct_plus' => 'AND equality').as 'logic_and_plus_end'
95
+ rule('logic_and' => 'equality conjunct_plus').as 'logical_expr'
96
+ rule('conjunct_plus' => 'conjunct_plus AND equality').as 'binary_plus_more'
97
+ rule('conjunct_plus' => 'AND equality').as 'binary_plus_end'
98
98
  rule('equality' => 'comparison')
99
- rule('equality' => 'comparison equalityTest_plus').as 'equality_plus'
100
- rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison').as 'equality_t_plus_more'
101
- rule('equalityTest_plus' => 'equalityTest comparison').as 'equality_t_plus_end'
99
+ rule('equality' => 'comparison equalityTest_plus').as 'binary_operator'
100
+ rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison').as 'binary_plus_more'
101
+ rule('equalityTest_plus' => 'equalityTest comparison').as 'binary_plus_end'
102
102
  rule('equalityTest' => 'BANG_EQUAL')
103
103
  rule('equalityTest' => 'EQUAL_EQUAL')
104
104
  rule('comparison' => 'term')
105
- rule('comparison' => 'term comparisonTest_plus').as 'comparison_plus'
105
+ rule('comparison' => 'term comparisonTest_plus').as 'binary_operator'
106
106
  rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
107
- rule('comparisonTest_plus' => 'comparisonTest term').as 'comparison_t_plus_end'
107
+ rule('comparisonTest_plus' => 'comparisonTest term').as 'binary_plus_end'
108
108
  rule('comparisonTest' => 'GREATER')
109
109
  rule('comparisonTest' => 'GREATER_EQUAL')
110
110
  rule('comparisonTest' => 'LESS')
111
111
  rule('comparisonTest' => 'LESS_EQUAL')
112
112
  rule('term' => 'factor')
113
- rule('term' => 'factor additive_plus').as 'term_additive'
114
- rule('additive_plus' => 'additive_plus additionOp factor').as 'additive_plus_more'
115
- rule('additive_plus' => 'additionOp factor').as 'additive_plus_end'
113
+ rule('term' => 'factor additive_plus').as 'binary_operator'
114
+ rule('additive_plus' => 'additive_plus additionOp factor').as 'binary_plus_more'
115
+ rule('additive_plus' => 'additionOp factor').as 'binary_plus_end'
116
116
  rule('additionOp' => 'MINUS')
117
117
  rule('additionOp' => 'PLUS')
118
118
  rule('factor' => 'unary')
119
- rule('factor' => 'unary multiplicative_plus').as 'factor_multiplicative'
120
- rule('multiplicative_plus' => 'multiplicative_plus multOp unary').as 'multiplicative_plus_more'
121
- rule('multiplicative_plus' => 'multOp unary').as 'multiplicative_plus_end'
119
+ rule('factor' => 'unary multiplicative_plus').as 'binary_operator'
120
+ rule('multiplicative_plus' => 'multiplicative_plus multOp unary').as 'binary_plus_more'
121
+ rule('multiplicative_plus' => 'multOp unary').as 'binary_plus_end'
122
122
  rule('multOp' => 'SLASH')
123
123
  rule('multOp' => 'STAR')
124
124
  rule('unary' => 'unaryOp unary').as 'unary_expr'
@@ -126,10 +126,10 @@ module Loxxy
126
126
  rule('unaryOp' => 'BANG')
127
127
  rule('unaryOp' => 'MINUS')
128
128
  rule('call' => 'primary')
129
- rule('call' => 'primary refinement_plus')
130
- rule('refinement_plus' => 'refinement_plus refinement')
131
- rule('refinement_plus' => 'refinement')
132
- rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN')
129
+ rule('call' => 'primary refinement_plus').as 'call_expr'
130
+ rule('refinement_plus' => 'refinement_plus refinement') # .as 'refinement_plus_more'
131
+ rule('refinement_plus' => 'refinement').as 'refinement_plus_end'
132
+ rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN').as 'call_arglist'
133
133
  rule('refinement' => 'DOT IDENTIFIER')
134
134
  rule('primary' => 'TRUE').as 'literal_expr'
135
135
  rule('primary' => 'FALSE').as 'literal_expr'
@@ -142,15 +142,15 @@ module Loxxy
142
142
  rule('primary' => 'SUPER DOT IDENTIFIER')
143
143
 
144
144
  # Utility rules
145
- rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block')
145
+ rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
146
146
  rule('params_opt' => 'parameters')
147
147
  rule('params_opt' => [])
148
- rule('parameters' => 'parameters COMMA IDENTIFIER')
149
- rule('parameters' => 'IDENTIFIER')
148
+ rule('parameters' => 'parameters COMMA IDENTIFIER').as 'parameters_plus_more'
149
+ rule('parameters' => 'IDENTIFIER').as 'parameters_plus_end'
150
150
  rule('arguments_opt' => 'arguments')
151
151
  rule('arguments_opt' => [])
152
- rule('arguments' => 'arguments COMMA expression')
153
- rule('arguments' => 'expression')
152
+ rule('arguments' => 'arguments COMMA expression').as 'arguments_plus_more'
153
+ rule('arguments' => 'expression').as 'arguments_plus_end'
154
154
  end
155
155
 
156
156
  unless defined?(Grammar)
@@ -11,7 +11,7 @@ module Loxxy
11
11
 
12
12
  # Constructor.
13
13
  # @param aValue [Datatype::BuiltinDatatype] the Lox data value
14
- # @param theLexeme [String] the lexeme (= piece of text from input)
14
+ # @param aLexeme [String] the lexeme (= piece of text from input)
15
15
  # @param aTerminal [Rley::Syntax::Terminal, String]
16
16
  # The terminal symbol corresponding to the lexeme.
17
17
  # @param aPosition [Rley::Lexical::Position] The position of lexeme
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.0.24'
4
+ VERSION = '0.1.0'
5
5
  end
@@ -32,7 +32,7 @@ module Loxxy
32
32
  end
33
33
 
34
34
  it 'should know its parent (if any)' do
35
- expect(subject.parent).to eq(mother)
35
+ expect(subject.enclosing).to eq(mother)
36
36
  end
37
37
  end # context
38
38
 
@@ -60,7 +60,7 @@ module Loxxy
60
60
  new_env = BackEnd::Environment.new
61
61
  expect { subject.enter_environment(new_env) }.not_to raise_error
62
62
  expect(subject.current_env).to eq(new_env)
63
- expect(subject.current_env.parent).to eq(subject.root)
63
+ expect(subject.current_env.enclosing).to eq(subject.root)
64
64
  expect(subject.name2envs['q']).to eq([subject.root])
65
65
  end
66
66
 
@@ -26,6 +26,7 @@ module Loxxy
26
26
  expect(subject.to_str).to eq(sample_text)
27
27
  end
28
28
 
29
+ # rubocop: disable Lint/BinaryOperatorWithIdenticalOperands
29
30
  it 'compares with another Lox string' do
30
31
  result = subject == LXString.new(sample_text.dup)
31
32
  expect(result).to be_true
@@ -40,6 +41,7 @@ module Loxxy
40
41
  result = LXString.new('') == LXString.new('')
41
42
  expect(result).to be_true
42
43
  end
44
+ # rubocop: enable Lint/BinaryOperatorWithIdenticalOperands
43
45
 
44
46
  it 'compares with a Ruby string' do
45
47
  result = subject == sample_text.dup
@@ -21,6 +21,7 @@ module Loxxy
21
21
  end
22
22
  end
23
23
 
24
+ # rubocop: disable Lint/FloatComparison
24
25
  context 'Provided services:' do
25
26
  it 'should compare with other Lox numbers' do
26
27
  result = subject == Number.new(sample_value)
@@ -51,7 +52,8 @@ module Loxxy
51
52
  subtraction = subject - Number.new(10)
52
53
  expect(subtraction == -22.34).to be_true
53
54
  end
54
- end
55
+ end # context
56
+ # rubocop: enable Lint/FloatComparison
55
57
  end # describe
56
58
  end # module
57
59
  end # module
@@ -208,6 +208,7 @@ LOX_END
208
208
  expect(eof_token.terminal).to eq('EOF')
209
209
  end
210
210
 
211
+ # rubocop: disable Lint/PercentStringArray
211
212
  it 'should skip end of line comments' do
212
213
  input = <<-LOX_END
213
214
  // first comment
@@ -223,6 +224,7 @@ LOX_END
223
224
  ]
224
225
  match_expectations(subject, expectations)
225
226
  end
227
+ # rubocop: enable Lint/PercentStringArray
226
228
 
227
229
  it 'should cope with single slash (divide) expression' do
228
230
  subject.start_with('8 / 2')
@@ -288,10 +288,10 @@ LOX_END
288
288
  program = <<-LOX_END
289
289
  var a = "before";
290
290
  print a; // output: before
291
-
291
+
292
292
  a = "after";
293
293
  print a; // output: after
294
-
294
+
295
295
  print a = "arg"; // output: arg
296
296
  print a; // output: arg
297
297
  LOX_END
@@ -299,6 +299,130 @@ LOX_END
299
299
  expect(sample_cfg[:ostream].string).to eq('beforeafterargarg')
300
300
  end
301
301
 
302
+ it 'should support variables local to a block' do
303
+ program = <<-LOX_END
304
+ {
305
+ var a = "first";
306
+ print a;
307
+ }
308
+ {
309
+ var a = "second";
310
+ print a;
311
+ }
312
+ LOX_END
313
+ expect { subject.evaluate(program) }.not_to raise_error
314
+ expect(sample_cfg[:ostream].string).to eq('firstsecond')
315
+ end
316
+
317
+ it 'should support the shadowing of variables in a block' do
318
+ program = <<-LOX_END
319
+ var a = "outer";
320
+
321
+ {
322
+ var a = "inner";
323
+ print a; // output: inner
324
+ }
325
+
326
+ print a; // output: outer
327
+ LOX_END
328
+ expect { subject.evaluate(program) }.not_to raise_error
329
+ expect(sample_cfg[:ostream].string).to eq('innerouter')
330
+ end
331
+
332
+ it 'should implement single statement while loops' do
333
+ program = <<-LOX_END
334
+ // Single-expression body.
335
+ var c = 0;
336
+ while (c < 3) print c = c + 1;
337
+ // output: 1
338
+ // output: 2
339
+ // output: 3
340
+ LOX_END
341
+ expect { subject.evaluate(program) }.not_to raise_error
342
+ expect(sample_cfg[:ostream].string).to eq('123')
343
+ end
344
+
345
+ it 'should implement block body while loops' do
346
+ program = <<-LOX_END
347
+ // Block body.
348
+ var a = 0;
349
+ while (a < 3) {
350
+ print a;
351
+ a = a + 1;
352
+ }
353
+ // output: 0
354
+ // output: 1
355
+ // output: 2
356
+ LOX_END
357
+ expect { subject.evaluate(program) }.not_to raise_error
358
+ expect(sample_cfg[:ostream].string).to eq('012')
359
+ end
360
+
361
+ it 'should implement single statement for loops' do
362
+ program = <<-LOX_END
363
+ // Single-expression body.
364
+ for (var c = 0; c < 3;) print c = c + 1;
365
+ // output: 1
366
+ // output: 2
367
+ // output: 3
368
+ LOX_END
369
+ expect { subject.evaluate(program) }.not_to raise_error
370
+ expect(sample_cfg[:ostream].string).to eq('123')
371
+ end
372
+
373
+ it 'should implement for loops with block body' do
374
+ program = <<-LOX_END
375
+ // Block body.
376
+ for (var a = 0; a < 3; a = a + 1) {
377
+ print a;
378
+ }
379
+ // output: 0
380
+ // output: 1
381
+ // output: 2
382
+ LOX_END
383
+ expect { subject.evaluate(program) }.not_to raise_error
384
+ expect(sample_cfg[:ostream].string).to eq('012')
385
+ end
386
+
387
+ it 'should implement nullary function calls' do
388
+ program = <<-LOX_END
389
+ print clock(); // Lox expects the 'clock' predefined native function
390
+ LOX_END
391
+ expect { subject.evaluate(program) }.not_to raise_error
392
+ tick = sample_cfg[:ostream].string
393
+ expect(Time.now.to_f - tick.to_f).to be < 0.1
394
+ end
395
+
396
+ it 'should implement function definition' do
397
+ program = <<-LOX_END
398
+ fun printSum(a, b) {
399
+ print a + b;
400
+ }
401
+ printSum(1, 2);
402
+ LOX_END
403
+ expect { subject.evaluate(program) }.not_to raise_error
404
+ expect(sample_cfg[:ostream].string).to eq('3')
405
+ end
406
+
407
+ it 'should support functions with empty body' do
408
+ program = <<-LOX_END
409
+ fun f() {}
410
+ print f();
411
+ LOX_END
412
+ expect { subject.evaluate(program) }.not_to raise_error
413
+ expect(sample_cfg[:ostream].string).to eq('nil')
414
+ end
415
+
416
+ it 'should provide print representation of functions' do
417
+ program = <<-LOX_END
418
+ fun foo() {}
419
+ print foo; // output: <fn foo>
420
+ print clock; // output: <native fn>
421
+ LOX_END
422
+ expect { subject.evaluate(program) }.not_to raise_error
423
+ expect(sample_cfg[:ostream].string).to eq('<fn foo><native fn>')
424
+ end
425
+
302
426
  it 'should print the hello world message' do
303
427
  expect { subject.evaluate(hello_world) }.not_to raise_error
304
428
  expect(sample_cfg[:ostream].string).to eq('Hello, world!')