loxxy 0.0.20 → 0.0.21

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: 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