loxxy 0.0.20 → 0.0.21

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: e6628d902c258d0aa2d589fd0960ab812addbfe8f0fbf162cfb627a90fbc1a40
4
- data.tar.gz: c1fe1f5ce4b3dc7109c70783b78953fd72ffbbd837d58f8350d2a03615f80222
3
+ metadata.gz: fd4cbf32f4ee44a60c45630a76318425850dc35b4d9f6129274cefcbf4e91fcc
4
+ data.tar.gz: 711e0ae07d2a0b3a080c2fe408d9adf4f7b96bb070af686d66dd3f9dbae8391d
5
5
  SHA512:
6
- metadata.gz: 301160cc1a00e819cc13cfb3273985b6af1ddadd94b236e4e7be0b346744355327be69d3b2d338ece3b6148a905d10a91dc3f6ef828912b7750efcdb0f4e5238
7
- data.tar.gz: cc5bb436b6e487d7752180d42d046d5a09a6817878428d757b65b26f2955133985598b55e7d63c13d249ebef110914a3a2b8292777a2857b15dcba885c74c106
6
+ metadata.gz: 131f6fe3b23dc0a84b063efe70744b02ecc9edb68742540799964311298f35494a0d48db75bb18a240ddeed039286ab08ba2ef99e2ae4857d2b4d5dc4403d5ba
7
+ data.tar.gz: b46c18f54147fd01f9819e3ad06564ee78fe72de5c91d6ef8a5bef5945d9f1ad622cad848c4d521ae66f00c91b7825d01e57c831b2a097da830bec0d99f2cf06
@@ -1,3 +1,16 @@
1
+ ## [0.0.21] - 2021-01-1x
2
+ - The interpreter supports the declaration global variables.
3
+
4
+ ## Added
5
+ - Class `BackEnd::Entry`, mixin module for objects put in the symbol table
6
+ - Class `BackEnd::Environment` that keeps track of variables in a given context.
7
+ - Class `BackEnd::SymbolTable` that keeps track of environments.
8
+ - Class `BackEnd::Variable` internal representation of a `Lox` variable
9
+ - Method `Ast::ASTBuilder#reduce_var_declaration` and `Ast::ASTBuilder#reduce_var_declaration`
10
+ - Method `Ast::ASTVisitor#visit_var_stmt` for visiting `LoxVarStmt` nodes
11
+ - Attribute `Engine::symbol_table`for keeping track of variables
12
+ - Method `Engine::after_var_stmt` for the implementation of variable declarations
13
+
1
14
  ## [0.0.20] - 2021-01-15
2
15
  - The interpreter supports the `if` ... `else` statement.
3
16
 
data/README.md CHANGED
@@ -8,7 +8,7 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
8
8
 
9
9
  ### Purpose of this project:
10
10
  - To deliver an open source example of a programming language fully implemented in Ruby
11
- (from the scanner, parser, an interpreter).
11
+ (from the scanner and parser to an interpreter).
12
12
  - The implementation should be mature enough to run [LoxLox](https://github.com/benhoyt/loxlox),
13
13
  a Lox interpreter written in Lox.
14
14
 
@@ -141,7 +141,9 @@ Here are the language features currently supported by the interpreter:
141
141
  - [Datatypes](#datatypes)
142
142
  - [Statements](#statements)
143
143
  -[Expressions](#expressions)
144
- -[Print Statement](#print-statement)
144
+ - [Variable declarations](#var-statement)
145
+ - [If Statement](#if-statement)
146
+ - [Print Statement](#print-statement)
145
147
 
146
148
  ### Comments
147
149
 
@@ -168,13 +170,15 @@ loxxy supports all the standard __Lox__ datatypes:
168
170
  ### Statements
169
171
 
170
172
  Loxxy supports the following statements:
171
- -[Expressions](#expressions)
173
+ - [Expressions](#expressions)
172
174
  -[Arithmetic expressions](#arithmetic-expressions)
173
175
  -[String concatenation](#string-concatenation)
174
176
  -[Comparison expressions](#comparison-expressions)
175
177
  -[Logical expressions](#logical-expressions)
176
178
  -[Grouping expressions](#grouping-expressions)
177
- -[If Statement](#if-statement)
179
+
180
+ -[Variable declarations](#var-statement)
181
+ -[If Statement](#if-statement)
178
182
  -[Print Statement](#print-statement)
179
183
 
180
184
  #### Expressions
@@ -254,6 +258,16 @@ print 3 + 4 * 5; // => 23
254
258
  print (3 + 4) * 5; // => 35
255
259
  ```
256
260
 
261
+ #### Variable declarations
262
+ ``` javascript
263
+ var iAmAVariable = "my-initial-value";
264
+ var iAmNil; // __Lox__ initializes variables to nil by default;
265
+ ```
266
+
267
+ Warning: current version cannot retrieve the value of a variable.
268
+ Expect this capability to be implemented in the coming days.
269
+
270
+
257
271
  #### If statement
258
272
 
259
273
  Based on a given condition, an if statement executes one of two statements:
@@ -8,3 +8,4 @@ require_relative 'lox_binary_expr'
8
8
  require_relative 'lox_logical_expr'
9
9
  require_relative 'lox_print_stmt'
10
10
  require_relative 'lox_if_stmt'
11
+ require_relative 'lox_var_stmt'
@@ -158,6 +158,19 @@ module Loxxy
158
158
  return_first_child(range, tokens, theChildren) # Discard the semicolon
159
159
  end
160
160
 
161
+ # rule('varDecl' => 'VAR IDENTIFIER SEMICOLON')
162
+ def reduce_var_declaration(_production, _range, tokens, theChildren)
163
+ var_name = theChildren[1].token.lexeme.dup
164
+ Ast::LoxVarStmt.new(tokens[1].position, var_name, nil)
165
+ end
166
+
167
+ # rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON')
168
+ def reduce_var_initialization(_production, _range, tokens, theChildren)
169
+ var_name = theChildren[1].token.lexeme.dup
170
+ Ast::LoxVarStmt.new(tokens[1].position, var_name, theChildren[3])
171
+ end
172
+
173
+
161
174
  # rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
162
175
  def reduce_if_stmt(_production, _range, tokens, theChildren)
163
176
  condition = theChildren[1]
@@ -51,6 +51,14 @@ module Loxxy
51
51
  broadcast(:after_ptree, aParseTree)
52
52
  end
53
53
 
54
+ # Visit event. The visitor is about to visit a variable declaration statement.
55
+ # @param aPrintStmt [AST::LOXVarStmt] the variable declaration node to visit
56
+ def visit_var_stmt(aVarStmt)
57
+ broadcast(:before_var_stmt, aVarStmt)
58
+ traverse_subnodes(aVarStmt)
59
+ broadcast(:after_var_stmt, aVarStmt)
60
+ end
61
+
54
62
  # Visit event. The visitor is about to visit a if statement.
55
63
  # @param anIfStmt [AST::LOXIfStmt] the if statement node to visit
56
64
  def visit_if_stmt(anIfStmt)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_compound_expr'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ # This AST node represents a variable declaration
8
+ class LoxVarStmt < LoxCompoundExpr
9
+ # @return [String] variable name
10
+ attr_reader :name
11
+
12
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
13
+ # @param aName [String] name of the variable
14
+ # @param aValue [Loxxy::Ast::LoxNode, NilClass] initial value for the variable
15
+ def initialize(aPosition, aName, aValue)
16
+ initial_value = aValue ? [aValue] : []
17
+ super(aPosition, initial_value)
18
+ @name = aName
19
+ end
20
+
21
+ # Part of the 'visitee' role in Visitor design pattern.
22
+ # @param visitor [Ast::ASTVisitor] the visitor
23
+ def accept(visitor)
24
+ visitor.visit_var_stmt(self)
25
+ end
26
+ end # class
27
+ end # module
28
+ end # module
@@ -2,6 +2,7 @@
2
2
 
3
3
  # Load all the classes implementing AST nodes
4
4
  require_relative '../ast/all_lox_nodes'
5
+ require_relative 'symbol_table'
5
6
 
6
7
  module Loxxy
7
8
  module BackEnd
@@ -12,13 +13,17 @@ module Loxxy
12
13
  # @return [Hash] A set of configuration options
13
14
  attr_reader :config
14
15
 
15
- # @return [Array] Data stack used for passing data between statements
16
+ # @return [BackEnd::SymbolTable]
17
+ attr_reader :symbol_table
18
+
19
+ # @return [Array<Datatype::BuiltinDatatyp>] Stack for the values of expr
16
20
  attr_reader :stack
17
21
 
18
22
  # @param theOptions [Hash]
19
23
  def initialize(theOptions)
20
24
  @config = theOptions
21
25
  @ostream = config.include?(:ostream) ? config[:ostream] : $stdout
26
+ @symbol_table = SymbolTable.new
22
27
  @stack = []
23
28
  end
24
29
 
@@ -36,6 +41,11 @@ module Loxxy
36
41
  ##########################################################################
37
42
  # Visit event handling
38
43
  ##########################################################################
44
+ def after_var_stmt(aVarStmt)
45
+ new_var = Variable.new(aVarStmt.name, aVarStmt.subnodes[0])
46
+ symbol_table.insert(new_var)
47
+ end
48
+
39
49
  def after_if_stmt(anIfStmt, aVisitor)
40
50
  # Retrieve the result of the condition evaluation
41
51
  condition = stack.pop
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Loxxy
4
+ module BackEnd
5
+ # Mix-in module that implements the expected common behaviour of entries
6
+ # placed in the symbol table.
7
+ module Entry
8
+ # @return [String] User-defined name of the entry.
9
+ attr_reader :name
10
+
11
+ =begin
12
+ # @return [String] Suffix for building the internal name of the entry.
13
+ attr_accessor :suffix
14
+ =end
15
+
16
+ # Initialize the entry with given name
17
+ # @param aName [String] The name of the entry
18
+ def init_name(aName)
19
+ @name = aName.dup
20
+ @name.freeze
21
+ end
22
+ =begin
23
+ # Return the internal name of the entry
24
+ # Internal names used to disambiguate entry names.
25
+ # There might be homonyns between variable because:
26
+ # - A child Scope may have a entry with same name as one of its
27
+ # ancestor(s).
28
+ # - Multiple calls to same defrel or procedure may imply multiple creation
29
+ # of a entry given name...
30
+ # @return [String] internal name
31
+ def i_name
32
+ if suffix =~ /^_/
33
+ label + suffix
34
+ else
35
+ (suffix.nil? || suffix.empty?) ? label : suffix
36
+ end
37
+ end
38
+ =end
39
+ end # module
40
+ end # module
41
+ end # module
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'variable'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ # A environment is a name space that corresponds either to a specific
8
+ # delimited region in Loxxy source code or to an activation record
9
+ # of a relation or a relation definition.
10
+ # It contains a map of names to the objects they name (e.g. logical var)
11
+ class Environment
12
+ # The parent (enclosing) environment.
13
+ # @return [Environment, NilClass]
14
+ attr_accessor :parent
15
+
16
+ # Mapping from user-defined name to related definition
17
+ # (say, a variable object)
18
+ # @return [Hash{String => Variable}] Pairs of the kind
19
+ attr_reader :defns
20
+
21
+ # Construct a environment instance.
22
+ # @param aParent [Environment, NilClass] Parent environment to this one.
23
+ def initialize(aParent = nil)
24
+ @parent = aParent
25
+ @defns = {}
26
+ end
27
+
28
+ # Add a new variable to the environment.
29
+ # @param anEntry [BackEnd::Variable]
30
+ # @return [BackEnd::Variable] the variable
31
+ def insert(anEntry)
32
+ e = validated_entry(anEntry)
33
+ # e.suffix = default_suffix if e.kind_of?(BackEnd::Variable)
34
+ defns[e.name] = e
35
+
36
+ e
37
+ end
38
+
39
+ # Returns a string with a human-readable representation of the object.
40
+ # @return [String]
41
+ def inspect
42
+ +"#<#{self.class}:#{object_id.to_s(16)}>"
43
+ end
44
+
45
+ private
46
+
47
+ def validated_entry(anEntry)
48
+ name = anEntry.name
49
+ unless name.kind_of?(String) && !name.empty?
50
+ err_msg = 'Invalid variable name argument.'
51
+ raise StandardError, err_msg
52
+ end
53
+ if defns.include?(name) && !anEntry.kind_of?(Variable)
54
+ err_msg = "Variable with name '#{name}' already exists."
55
+ raise StandardError, err_msg
56
+ end
57
+
58
+ anEntry
59
+ end
60
+
61
+ # def default_suffix
62
+ # @default_suffix ||= "_#{object_id.to_s(16)}"
63
+ # end
64
+ end # class
65
+ end # module
66
+ end # module
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'environment'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ # A symbol table is basically a mapping from a name onto an object
8
+ # that holds information associated with that name. It is a data structure
9
+ # that keeps track of variables and their respective environment where they are
10
+ # declared. The key requirements for the symbol are:
11
+ # - To perform fast lookup operations: given a name, retrieve the corresponding
12
+ # object.
13
+ # - To allow the efficient insertion of names and related information
14
+ # - To support the nesting of environments
15
+ # - To handle the entry environment and exit environment events,
16
+ # - To cope with variable redefinition in nested environment
17
+ # The terminology 'symbol table' comes from the compiler design
18
+ # community.
19
+ class SymbolTable
20
+ # Mapping between a name and the environment(s) where it is defined
21
+ # @return [Hash{String => Array<Environment>}]
22
+ attr_reader :name2envs
23
+
24
+ # @return [Environment] The top-level environment (= root of environment tree)
25
+ attr_reader :root
26
+
27
+ # @return [Environment] The current environment.
28
+ attr_reader :current_env
29
+
30
+ # Build symbol table with given environment as root.
31
+ # @param anEnv [BackEnd::Environment,NilClass] The top-level Environment
32
+ def initialize(anEnv = nil)
33
+ @name2envs = {}
34
+ init_root(anEnv) # Set default (global) environment
35
+ end
36
+
37
+ # Returns iff there is no entry in the symbol table
38
+ # @return [Boolean]
39
+ def empty?
40
+ name2envs.empty?
41
+ end
42
+
43
+ # Use this method to signal the interpreter that a given environment
44
+ # to be a child of current environment and to be itself the new current environment.
45
+ # @param anEnv [BackEnd::Environment] the Environment that
46
+ def enter_environment(anEnv)
47
+ anEnv.parent = current_env
48
+ @current_env = anEnv
49
+ end
50
+
51
+ 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
59
+ end
60
+ end
61
+ raise StandardError, 'Cannot remove root environment.' if current_env == root
62
+
63
+ @current_env = current_env.parent
64
+ end
65
+
66
+ # Add an entry with given name to current environment.
67
+ # @param anEntry [Variable]
68
+ # @return [String] Internal name of the entry
69
+ def insert(anEntry)
70
+ current_env.insert(anEntry)
71
+ name = anEntry.name
72
+ if name2envs.include?(name)
73
+ name2envs[name] << current_env
74
+ else
75
+ name2envs[name] = [current_env]
76
+ end
77
+
78
+ anEntry.name # anEntry.i_name
79
+ end
80
+
81
+ # Search for the object with the given name
82
+ # @param aName [String]
83
+ # @return [BackEnd::Variable]
84
+ def lookup(aName)
85
+ environments = name2envs.fetch(aName, nil)
86
+ return nil if environments.nil?
87
+
88
+ sc = environments.last
89
+ sc.defns[aName]
90
+ end
91
+
92
+ # Search for the object with the given i_name
93
+ # @param anIName [String]
94
+ # @return [BackEnd::Variable]
95
+ # def lookup_i_name(anIName)
96
+ # found = nil
97
+ # environment = current_env
98
+
99
+ # begin
100
+ # found = environment.defns.values.find { |e| e.i_name == anIName }
101
+ # break if found
102
+
103
+ # environment = environment.parent
104
+ # end while environment
105
+
106
+ # found
107
+ # end
108
+
109
+ # Return all variables defined in the current .. root chain.
110
+ # Variables are sorted top-down and left-to-right.
111
+ def all_variables
112
+ vars = []
113
+ skope = current_env
114
+ while skope
115
+ vars_of_environment = skope.defns.select { |_, item| item.kind_of?(Variable) }
116
+ vars = vars_of_environment.values.concat(vars)
117
+ skope = skope.parent
118
+ end
119
+
120
+ vars
121
+ end
122
+
123
+ private
124
+
125
+ def init_root(anEnv)
126
+ @root = valid_environment(anEnv)
127
+ @current_env = @root
128
+ end
129
+
130
+ def valid_environment(anEnv)
131
+ anEnv.nil? ? Environment.new : anEnv
132
+ end
133
+ end # class
134
+ end # module
135
+ end # module
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'entry'
4
+ require_relative '../datatype/all_datatypes'
5
+
6
+ module Loxxy
7
+ module BackEnd
8
+ # Representation of a Lox variable.
9
+ # It is a named slot that can be associated with a value at the time.
10
+ class Variable
11
+ include Entry # Add expected behaviour for symbol table entries
12
+
13
+ # @return [Datatype::BuiltinDatatype] the value assigned to the variable
14
+ attr_accessor :value
15
+
16
+ # Create a variable with given name and initial value
17
+ # @param aName [String] The name of the variable
18
+ # @param aValue [Datatype::BuiltinDatatype] the initial assigned value
19
+ def initialize(aName, aValue = Datatype::Nil.instance)
20
+ init_name(aName)
21
+ @value = aValue
22
+ end
23
+ end # class
24
+ end # module
25
+ end # module
@@ -46,8 +46,8 @@ module Loxxy
46
46
 
47
47
  rule('funDecl' => 'FUN function')
48
48
 
49
- rule('varDecl' => 'VAR IDENTIFIER SEMICOLON')
50
- rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON')
49
+ rule('varDecl' => 'VAR IDENTIFIER SEMICOLON').as 'var_declaration'
50
+ rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON').as 'var_initialization'
51
51
 
52
52
  # Statements: produce side effects, but don't introduce bindings
53
53
  rule('statement' => 'exprStmt')
@@ -31,7 +31,7 @@ module Loxxy
31
31
  ast_tree = parser.parse(lox_input)
32
32
  visitor = Ast::ASTVisitor.new(ast_tree)
33
33
 
34
- # Back-end launches the tree walking & reponds to visit events
34
+ # Back-end launches the tree walking & responds to visit events
35
35
  # by executing the code determined by the visited AST node.
36
36
  engine = BackEnd::Engine.new(config)
37
37
  engine.execute(visitor)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.20'
4
+ VERSION = '0.0.21'
5
5
  end
@@ -31,8 +31,16 @@ module Loxxy
31
31
  context 'Listening to visitor events:' do
32
32
  let(:greeting) { Datatype::LXString.new('Hello, world') }
33
33
  let(:sample_pos) { double('fake-position') }
34
+ let(:var_decl) { Ast::LoxVarStmt.new(sample_pos, 'greeting', greeting) }
34
35
  let(:lit_expr) { Ast::LoxLiteralExpr.new(sample_pos, greeting) }
35
36
 
37
+ it "should react to 'after_var_stmt' event" do
38
+ expect { subject.after_var_stmt(var_decl) }.not_to raise_error
39
+ current_env = subject.symbol_table.current_env
40
+ expect(current_env.defns['greeting']).to be_kind_of(Variable)
41
+ expect(current_env.defns['greeting'].value).to eq(greeting)
42
+ end
43
+
36
44
  it "should react to 'before_literal_expr' event" do
37
45
  expect { subject.before_literal_expr(lit_expr) }.not_to raise_error
38
46
  expect(subject.stack.pop).to eq(greeting)
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+
5
+ # Load the class under test
6
+ require_relative '../../lib/loxxy/back_end/environment'
7
+
8
+ module Loxxy
9
+ module BackEnd
10
+ describe Environment do
11
+ let(:foo) { Datatype::LXString.new('foo') }
12
+ let(:bar) { Datatype::LXString.new('bar') }
13
+ let(:mother) { Environment.new }
14
+ subject { Environment.new(mother) }
15
+
16
+ # Shortand factory method.
17
+ def var(aName, aValue)
18
+ Variable.new(aName, aValue)
19
+ end
20
+
21
+ context 'Initialization:' do
22
+ it 'could be initialized without argument' do
23
+ expect { Environment.new }.not_to raise_error
24
+ end
25
+
26
+ it 'could be initialized with a parent environment' do
27
+ expect { Environment.new(mother) }.not_to raise_error
28
+ end
29
+
30
+ it "shouldn't have definitions by default" do
31
+ expect(subject.defns).to be_empty
32
+ end
33
+
34
+ it 'should know its parent (if any)' do
35
+ expect(subject.parent).to eq(mother)
36
+ end
37
+ end # context
38
+
39
+ context 'Provided services:' do
40
+ it 'should accept the addition of a variable' do
41
+ subject.insert(var('a', foo))
42
+ expect(subject.defns).not_to be_empty
43
+ var_a = subject.defns['a']
44
+ expect(var_a).to be_kind_of(Variable)
45
+ expect(var_a.name).to eq('a')
46
+ end
47
+
48
+ it 'should accept the addition of multiple variables' do
49
+ subject.insert(var('a', foo))
50
+ expect(subject.defns).not_to be_empty
51
+
52
+ subject.insert(var('b', bar))
53
+ var_b = subject.defns['b']
54
+ expect(var_b).to be_kind_of(Variable)
55
+ expect(var_b.name).to eq('b')
56
+ end
57
+
58
+ # it 'should set the suffix of just created variable' do
59
+ # subject.insert(var('a'))
60
+ # var_a = subject.defns['a']
61
+ # expect(var_a.suffix).to eq("_#{subject.object_id.to_s(16)}")
62
+ # end
63
+
64
+ # it 'should complain when variable names collide' do
65
+ # subject.insert(var('c'))
66
+ # expect(subject.defns['c']).to be_kind_of(Datatype::Variable)
67
+ # err = StandardError
68
+ # err_msg = "Variable with name 'c' already exists."
69
+ # expect { subject.insert(var('c')) }.to raise_error(err, err_msg)
70
+ # end
71
+ end # context
72
+ end # describe
73
+ end # module
74
+ end # module
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require_relative '../../lib/loxxy/back_end/variable'
5
+
6
+ # Load the class under test
7
+ require_relative '../../lib/loxxy/back_end/symbol_table'
8
+
9
+ module Loxxy
10
+ module BackEnd
11
+ describe SymbolTable do
12
+ subject { SymbolTable.new }
13
+
14
+ context 'Initialization:' do
15
+ it 'should be initialized without argument' do
16
+ expect { SymbolTable.new }.not_to raise_error
17
+ end
18
+
19
+ it 'should have a root BackEnd' do
20
+ expect(subject.root).not_to be_nil
21
+ expect(subject.current_env).to eq(subject.root)
22
+ expect(subject.root).to be_kind_of(BackEnd::Environment)
23
+ end
24
+
25
+ it "shouldn't have names at initialization" do
26
+ expect(subject.name2envs).to be_empty
27
+ end
28
+
29
+ it 'should be empty at initialization' do
30
+ expect(subject).to be_empty
31
+ end
32
+ end # context
33
+
34
+ context 'Provided services:' do
35
+ def var(aName)
36
+ Variable.new(aName)
37
+ end
38
+
39
+ it 'should allow the addition of a variable' do
40
+ expect { subject.insert(var('q')) }.not_to raise_error
41
+ expect(subject).not_to be_empty
42
+ expect(subject.name2envs['q']).to be_kind_of(Array)
43
+ expect(subject.name2envs['q'].size).to eq(1)
44
+ expect(subject.name2envs['q'].first).to eq(subject.current_env)
45
+ expect(subject.current_env.defns['q']).to be_kind_of(BackEnd::Variable)
46
+ end
47
+
48
+ it 'should allow the addition of several labels for same env' do
49
+ i_name = subject.insert(var('q'))
50
+ #expect(i_name).to match(/^q_[0-9a-z]*$/)
51
+
52
+ expect { subject.insert(var('x')) }.not_to raise_error
53
+ expect(subject.name2envs['x']).to be_kind_of(Array)
54
+ expect(subject.name2envs['x'].first).to eq(subject.current_env)
55
+ expect(subject.current_env.defns['x']).to be_kind_of(BackEnd::Variable)
56
+ end
57
+
58
+ it 'should allow the entry into a new scope' do
59
+ subject.insert(var('q'))
60
+ new_env = BackEnd::Environment.new
61
+ expect { subject.enter_environment(new_env) }.not_to raise_error
62
+ expect(subject.current_env).to eq(new_env)
63
+ expect(subject.current_env.parent).to eq(subject.root)
64
+ expect(subject.name2envs['q']).to eq([subject.root])
65
+ end
66
+
67
+ it 'should allow the addition of same name in different scopes' do
68
+ subject.insert(var('q'))
69
+ subject.enter_environment(BackEnd::Environment.new)
70
+ subject.insert(var('q'))
71
+ expect(subject.name2envs['q']).to be_kind_of(Array)
72
+ expect(subject.name2envs['q'].size).to eq(2)
73
+ expect(subject.name2envs['q'].first).to eq(subject.root)
74
+ expect(subject.name2envs['q'].last).to eq(subject.current_env)
75
+ expect(subject.current_env.defns['q']).to be_kind_of(BackEnd::Variable)
76
+ end
77
+
78
+ it 'should allow the removal of a scope' do
79
+ subject.insert(var('q'))
80
+ new_env = BackEnd::Environment.new
81
+ subject.enter_environment(new_env)
82
+ subject.insert(var('q'))
83
+ expect(subject.name2envs['q'].size).to eq(2)
84
+
85
+ expect { subject.leave_environment }.not_to raise_error
86
+ expect(subject.current_env).to eq(subject.root)
87
+ expect(subject.name2envs['q'].size).to eq(1)
88
+ expect(subject.name2envs['q']).to eq([subject.root])
89
+ end
90
+
91
+ it 'should allow the search of an entry based on its name' do
92
+ subject.insert(var('q'))
93
+ subject.insert(var('x'))
94
+ subject.enter_environment(BackEnd::Environment.new)
95
+ subject.insert(var('q'))
96
+ subject.insert(var('y'))
97
+
98
+ # Search for unknown name
99
+ expect(subject.lookup('z')).to be_nil
100
+
101
+ # Search for existing unique names
102
+ expect(subject.lookup('y')).to eq(subject.current_env.defns['y'])
103
+ expect(subject.lookup('x')).to eq(subject.root.defns['x'])
104
+
105
+ # Search for redefined name
106
+ expect(subject.lookup('q')).to eq(subject.current_env.defns['q'])
107
+ end
108
+
109
+ # it 'should allow the search of an entry based on its i_name' do
110
+ # subject.insert(var('q'))
111
+ # i_name_x = subject.insert(var('x'))
112
+ # subject.enter_environment(BackEnd::Environment.new)
113
+ # i_name_q2 = subject.insert(var('q'))
114
+ # i_name_y = subject.insert(var('y'))
115
+
116
+ # # Search for unknown i_name
117
+ # expect(subject.lookup_i_name('dummy')).to be_nil
118
+
119
+ # curr_scope = subject.current_env
120
+ # # # Search for existing unique names
121
+ # expect(subject.lookup_i_name(i_name_y)).to eq(curr_scope.defns['y'])
122
+ # expect(subject.lookup_i_name(i_name_x)).to eq(subject.root.defns['x'])
123
+
124
+ # # Search for redefined name
125
+ # expect(subject.lookup_i_name(i_name_q2)).to eq(curr_scope.defns['q'])
126
+ # end
127
+
128
+ it 'should list all the variables defined in all the szcope chain' do
129
+ subject.insert(var('q'))
130
+ subject.enter_environment(BackEnd::Environment.new)
131
+ subject.insert(var('x'))
132
+ subject.enter_environment(BackEnd::Environment.new)
133
+ subject.insert(var('y'))
134
+ subject.insert(var('x'))
135
+
136
+ vars = subject.all_variables
137
+ expect(vars.map(&:name)).to eq(%w[q x y x])
138
+ end
139
+ end # context
140
+ end # describe
141
+ end # module
142
+ end # module
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+
5
+ # Load the class under test
6
+ require_relative '../../lib/loxxy/back_end/variable'
7
+
8
+
9
+ module Loxxy
10
+ module BackEnd
11
+ describe Variable do
12
+ let(:sample_name) { 'iAmAVariable' }
13
+ let(:sample_value) { 'here is my value' }
14
+ subject { Variable.new(sample_name, sample_value) }
15
+
16
+ context 'Initialization:' do
17
+ it 'should be initialized with a name and a value, or...' do
18
+ expect { Variable.new(sample_name, sample_value) }.not_to raise_error
19
+ end
20
+
21
+ it 'should be initialized with just a name' do
22
+ expect { Variable.new(sample_name) }.not_to raise_error
23
+ end
24
+
25
+ it 'should know its name' do
26
+ expect(subject.name).to eq(sample_name)
27
+ end
28
+
29
+ it 'should have a frozen name' do
30
+ expect(subject.name).to be_frozen
31
+ end
32
+
33
+ it 'should know its value (if provided)' do
34
+ expect(subject.value).to eq(sample_value)
35
+ end
36
+
37
+ it 'should have a nil value otherwise' do
38
+ instance = Variable.new(sample_name)
39
+ expect(instance.value).to eq(Datatype::Nil.instance)
40
+ end
41
+
42
+ # it 'should know its default internal name' do
43
+ # # By default: internal name == label
44
+ # expect(subject.i_name).to eq(subject.label)
45
+ # end
46
+
47
+ # it 'should have a nil suffix' do
48
+ # expect(subject.suffix).to be_nil
49
+ # end
50
+ end # context
51
+
52
+ context 'Provided service:' do
53
+ let(:sample_suffix) { 'sample-suffix' }
54
+ it 'should have a label equal to its user-defined name' do
55
+ # expect(subject.label).to eq(subject.name)
56
+ end
57
+
58
+ it 'should accept a suffix' do
59
+ # expect { subject.suffix = sample_suffix }.not_to raise_error
60
+ # expect(subject.suffix).to eq(sample_suffix)
61
+ end
62
+
63
+ it 'should calculate its internal name' do
64
+ # # Rule: empty suffix => internal name == label
65
+ # subject.suffix = ''
66
+ # expect(subject.i_name).to eq(subject.label)
67
+
68
+ # # Rule: suffix starting underscore: internal name = label + suffix
69
+ # subject.suffix = '_10'
70
+ # expect(subject.i_name).to eq(subject.label + subject.suffix)
71
+
72
+ # # Rule: ... otherwise: internal name == suffix
73
+ # subject.suffix = sample_suffix
74
+ # expect(subject.i_name).to eq(subject.suffix)
75
+ end
76
+ end # context
77
+ end # describe
78
+ end # module
79
+ end # module
@@ -211,7 +211,7 @@ module Loxxy
211
211
  end
212
212
  end
213
213
 
214
- it 'should supprt expressions between parentheses' do
214
+ it 'should support expressions between parentheses' do
215
215
  [
216
216
  ['3 + 4 * 5;', 23],
217
217
  ['(3 + 4) * 5;', 35],
@@ -250,6 +250,15 @@ module Loxxy
250
250
  end
251
251
  end
252
252
 
253
+ it 'should accept variable declarations' do
254
+ # Variable with initialization value
255
+ var_decl = 'var iAmAVariable = "here is my value";'
256
+ expect { subject.evaluate(var_decl) }.not_to raise_error
257
+
258
+ # Variable without initialization value
259
+ expect { subject.evaluate('var iAmNil;') }.not_to raise_error
260
+ end
261
+
253
262
  it 'should print the hello world message' do
254
263
  expect { subject.evaluate(hello_world) }.not_to raise_error
255
264
  expect(sample_cfg[:ostream].string).to eq('Hello, world!')
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.0.20
4
+ version: 0.0.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-15 00:00:00.000000000 Z
11
+ date: 2021-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -97,7 +97,12 @@ files:
97
97
  - lib/loxxy/ast/lox_noop_expr.rb
98
98
  - lib/loxxy/ast/lox_print_stmt.rb
99
99
  - lib/loxxy/ast/lox_unary_expr.rb
100
+ - lib/loxxy/ast/lox_var_stmt.rb
100
101
  - lib/loxxy/back_end/engine.rb
102
+ - lib/loxxy/back_end/entry.rb
103
+ - lib/loxxy/back_end/environment.rb
104
+ - lib/loxxy/back_end/symbol_table.rb
105
+ - lib/loxxy/back_end/variable.rb
101
106
  - lib/loxxy/datatype/all_datatypes.rb
102
107
  - lib/loxxy/datatype/boolean.rb
103
108
  - lib/loxxy/datatype/builtin_datatype.rb
@@ -115,6 +120,9 @@ files:
115
120
  - lib/loxxy/version.rb
116
121
  - loxxy.gemspec
117
122
  - spec/back_end/engine_spec.rb
123
+ - spec/back_end/environment_spec.rb
124
+ - spec/back_end/symbol_table_spec.rb
125
+ - spec/back_end/variable_spec.rb
118
126
  - spec/datatype/boolean_spec.rb
119
127
  - spec/datatype/lx_string_spec.rb
120
128
  - spec/datatype/nil_spec.rb
@@ -151,6 +159,9 @@ specification_version: 4
151
159
  summary: An implementation of the Lox programming language. WIP
152
160
  test_files:
153
161
  - spec/back_end/engine_spec.rb
162
+ - spec/back_end/environment_spec.rb
163
+ - spec/back_end/symbol_table_spec.rb
164
+ - spec/back_end/variable_spec.rb
154
165
  - spec/datatype/boolean_spec.rb
155
166
  - spec/datatype/lx_string_spec.rb
156
167
  - spec/datatype/nil_spec.rb