loxxy 0.0.19 → 0.0.24
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 +4 -4
- data/CHANGELOG.md +91 -30
- data/README.md +64 -5
- data/lib/loxxy/ast/all_lox_nodes.rb +5 -0
- data/lib/loxxy/ast/ast_builder.rb +44 -2
- data/lib/loxxy/ast/ast_visitor.rb +47 -1
- data/lib/loxxy/ast/lox_assign_expr.rb +27 -0
- data/lib/loxxy/ast/lox_if_stmt.rb +35 -0
- data/lib/loxxy/ast/lox_seq_decl.rb +17 -0
- data/lib/loxxy/ast/lox_var_stmt.rb +28 -0
- data/lib/loxxy/ast/lox_variable_expr.rb +26 -0
- data/lib/loxxy/back_end/engine.rb +49 -1
- data/lib/loxxy/back_end/entry.rb +41 -0
- data/lib/loxxy/back_end/environment.rb +66 -0
- data/lib/loxxy/back_end/symbol_table.rb +135 -0
- data/lib/loxxy/back_end/variable.rb +30 -0
- data/lib/loxxy/datatype/builtin_datatype.rb +6 -0
- data/lib/loxxy/front_end/grammar.rb +9 -9
- data/lib/loxxy/interpreter.rb +1 -1
- data/lib/loxxy/version.rb +1 -1
- data/spec/back_end/engine_spec.rb +9 -0
- data/spec/back_end/environment_spec.rb +74 -0
- data/spec/back_end/symbol_table_spec.rb +142 -0
- data/spec/back_end/variable_spec.rb +79 -0
- data/spec/front_end/parser_spec.rb +21 -19
- data/spec/interpreter_spec.rb +78 -2
- metadata +17 -2
| @@ -0,0 +1,30 @@ | |
| 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_reader :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 | 
            +
                    assign(aValue)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  # @param aValue [Datatype::BuiltinDatatype] the assigned value
         | 
| 25 | 
            +
                  def assign(aValue)
         | 
| 26 | 
            +
                    @value = aValue
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end # class
         | 
| 29 | 
            +
              end # module
         | 
| 30 | 
            +
            end # module
         | 
| @@ -62,6 +62,12 @@ module Loxxy | |
| 62 62 | 
             
                    value.to_s # Default implementation...
         | 
| 63 63 | 
             
                  end
         | 
| 64 64 |  | 
| 65 | 
            +
                  # Part of the 'visitee' role in Visitor design pattern.
         | 
| 66 | 
            +
                  # @param visitor [Ast::ASTVisitor] the visitor
         | 
| 67 | 
            +
                  def accept(visitor)
         | 
| 68 | 
            +
                    visitor.visit_builtin(self)
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
             | 
| 65 71 | 
             
                  protected
         | 
| 66 72 |  | 
| 67 73 | 
             
                  def validated_value(aValue)
         | 
| @@ -30,8 +30,8 @@ module Loxxy | |
| 30 30 | 
             
                  rule('program' => 'declaration_plus EOF').as 'lox_program'
         | 
| 31 31 |  | 
| 32 32 | 
             
                  # Declarations: bind an identifier to something
         | 
| 33 | 
            -
                  rule('declaration_plus' => 'declaration_plus declaration')
         | 
| 34 | 
            -
                  rule('declaration_plus' => 'declaration')
         | 
| 33 | 
            +
                  rule('declaration_plus' => 'declaration_plus declaration').as 'declaration_plus_more'
         | 
| 34 | 
            +
                  rule('declaration_plus' => 'declaration').as 'declaration_plus_end'
         | 
| 35 35 | 
             
                  rule('declaration' => 'classDecl')
         | 
| 36 36 | 
             
                  rule('declaration' => 'funDecl')
         | 
| 37 37 | 
             
                  rule('declaration' => 'varDecl')
         | 
| @@ -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')
         | 
| @@ -68,9 +68,9 @@ module Loxxy | |
| 68 68 | 
             
                  rule('forTest' => 'expression_opt SEMICOLON')
         | 
| 69 69 | 
             
                  rule('forUpdate' => 'expression_opt')
         | 
| 70 70 |  | 
| 71 | 
            -
                  rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
         | 
| 72 | 
            -
                  rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN')
         | 
| 73 | 
            -
                  rule('elsePart_opt' => 'ELSE statement')
         | 
| 71 | 
            +
                  rule('ifStmt' => 'IF ifCondition statement elsePart_opt').as 'if_stmt'
         | 
| 72 | 
            +
                  rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN').as 'keep_symbol2'
         | 
| 73 | 
            +
                  rule('elsePart_opt' => 'ELSE statement').as 'keep_symbol2'
         | 
| 74 74 | 
             
                  rule('elsePart_opt' => [])
         | 
| 75 75 |  | 
| 76 76 | 
             
                  rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
         | 
| @@ -83,7 +83,7 @@ module Loxxy | |
| 83 83 | 
             
                  rule('expression_opt' => 'expression')
         | 
| 84 84 | 
             
                  rule('expression_opt' => [])
         | 
| 85 85 | 
             
                  rule('expression' => 'assignment')
         | 
| 86 | 
            -
                  rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
         | 
| 86 | 
            +
                  rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment').as 'assign_expr'
         | 
| 87 87 | 
             
                  rule('assignment' => 'logic_or')
         | 
| 88 88 | 
             
                  rule('owner_opt' => 'call DOT')
         | 
| 89 89 | 
             
                  rule('owner_opt' => [])
         | 
| @@ -137,7 +137,7 @@ module Loxxy | |
| 137 137 | 
             
                  rule('primary' => 'THIS')
         | 
| 138 138 | 
             
                  rule('primary' => 'NUMBER').as 'literal_expr'
         | 
| 139 139 | 
             
                  rule('primary' => 'STRING').as 'literal_expr'
         | 
| 140 | 
            -
                  rule('primary' => 'IDENTIFIER')
         | 
| 140 | 
            +
                  rule('primary' => 'IDENTIFIER').as 'variable_expr'
         | 
| 141 141 | 
             
                  rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN').as 'grouping_expr'
         | 
| 142 142 | 
             
                  rule('primary' => 'SUPER DOT IDENTIFIER')
         | 
| 143 143 |  | 
    
        data/lib/loxxy/interpreter.rb
    CHANGED
    
    | @@ -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 &  | 
| 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)
         | 
    
        data/lib/loxxy/version.rb
    CHANGED
    
    
| @@ -31,8 +31,17 @@ 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 | 
            +
             | 
| 38 | 
            +
                    it "should react to 'after_var_stmt' event" do
         | 
| 39 | 
            +
                      expect { subject.after_var_stmt(var_decl) }.not_to raise_error
         | 
| 40 | 
            +
                      current_env = subject.symbol_table.current_env
         | 
| 41 | 
            +
                      expect(current_env.defns['greeting']).to be_kind_of(Variable)
         | 
| 42 | 
            +
                      expect(current_env.defns['greeting'].value).to eq(greeting)
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
             | 
| 36 45 | 
             
                    it "should react to 'before_literal_expr' event" do
         | 
| 37 46 | 
             
                      expect { subject.before_literal_expr(lit_expr) }.not_to raise_error
         | 
| 38 47 | 
             
                      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 | 
            +
                      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
         | 
| @@ -64,7 +64,8 @@ module Loxxy | |
| 64 64 | 
             
                    it 'should parse a false literal' do
         | 
| 65 65 | 
             
                      input = 'false;'
         | 
| 66 66 | 
             
                      ptree = subject.parse(input)
         | 
| 67 | 
            -
                       | 
| 67 | 
            +
                      expect(ptree.root).to be_kind_of(Ast::LoxSeqDecl)
         | 
| 68 | 
            +
                      leaf = ptree.root.subnodes[0]
         | 
| 68 69 | 
             
                      expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
         | 
| 69 70 | 
             
                      expect(leaf.literal).to be_equal(Datatype::False.instance)
         | 
| 70 71 | 
             
                    end
         | 
| @@ -72,7 +73,8 @@ module Loxxy | |
| 72 73 | 
             
                    it 'should parse a true literal' do
         | 
| 73 74 | 
             
                      input = 'true;'
         | 
| 74 75 | 
             
                      ptree = subject.parse(input)
         | 
| 75 | 
            -
                       | 
| 76 | 
            +
                      expect(ptree.root).to be_kind_of(Ast::LoxSeqDecl)
         | 
| 77 | 
            +
                      leaf = ptree.root.subnodes[0]
         | 
| 76 78 | 
             
                      expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
         | 
| 77 79 | 
             
                      expect(leaf.literal).to be_equal(Datatype::True.instance)
         | 
| 78 80 | 
             
                    end
         | 
| @@ -81,7 +83,7 @@ module Loxxy | |
| 81 83 | 
             
                      inputs = %w[1234; 12.34;]
         | 
| 82 84 | 
             
                      inputs.each do |source|
         | 
| 83 85 | 
             
                        ptree = subject.parse(source)
         | 
| 84 | 
            -
                        leaf = ptree.root
         | 
| 86 | 
            +
                        leaf = ptree.root.subnodes[0]
         | 
| 85 87 | 
             
                        expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
         | 
| 86 88 | 
             
                        expect(leaf.literal).to be_kind_of(Datatype::Number)
         | 
| 87 89 | 
             
                        expect(leaf.literal.value).to eq(source.to_f)
         | 
| @@ -96,7 +98,7 @@ module Loxxy | |
| 96 98 | 
             
                      ]
         | 
| 97 99 | 
             
                      inputs.each do |source|
         | 
| 98 100 | 
             
                        ptree = subject.parse(source)
         | 
| 99 | 
            -
                        leaf = ptree.root
         | 
| 101 | 
            +
                        leaf = ptree.root.subnodes[0]
         | 
| 100 102 | 
             
                        expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
         | 
| 101 103 | 
             
                        expect(leaf.literal).to be_kind_of(Datatype::LXString)
         | 
| 102 104 | 
             
                        expect(leaf.literal.value).to eq(source.gsub(/(^")|(";$)/, ''))
         | 
| @@ -106,7 +108,7 @@ module Loxxy | |
| 106 108 | 
             
                    it 'should parse a nil literal' do
         | 
| 107 109 | 
             
                      input = 'nil;'
         | 
| 108 110 | 
             
                      ptree = subject.parse(input)
         | 
| 109 | 
            -
                      leaf = ptree.root
         | 
| 111 | 
            +
                      leaf = ptree.root.subnodes[0]
         | 
| 110 112 | 
             
                      expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
         | 
| 111 113 | 
             
                      expect(leaf.literal).to be_equal(Datatype::Nil.instance)
         | 
| 112 114 | 
             
                    end
         | 
| @@ -119,7 +121,7 @@ module Loxxy | |
| 119 121 | 
             
                        print "Hello, world!";
         | 
| 120 122 | 
             
            LOX_END
         | 
| 121 123 | 
             
                      ptree = subject.parse(program)
         | 
| 122 | 
            -
                      prnt_stmt = ptree.root
         | 
| 124 | 
            +
                      prnt_stmt = ptree.root.subnodes[0]
         | 
| 123 125 | 
             
                      expect(prnt_stmt).to be_kind_of(Ast::LoxPrintStmt)
         | 
| 124 126 | 
             
                      expect(prnt_stmt.subnodes[0]).to be_kind_of(Ast::LoxLiteralExpr)
         | 
| 125 127 | 
             
                      expect(prnt_stmt.subnodes[0].literal).to be_kind_of(Loxxy::Datatype::LXString)
         | 
| @@ -131,7 +133,7 @@ LOX_END | |
| 131 133 | 
             
                    it 'should parse the addition of two number literals' do
         | 
| 132 134 | 
             
                      input = '123 + 456;'
         | 
| 133 135 | 
             
                      ptree = subject.parse(input)
         | 
| 134 | 
            -
                      expr = ptree.root
         | 
| 136 | 
            +
                      expr = ptree.root.subnodes[0]
         | 
| 135 137 | 
             
                      expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| 136 138 | 
             
                      expect(expr.operator).to eq(:+)
         | 
| 137 139 | 
             
                      expect(expr.operands[0].literal.value).to eq(123)
         | 
| @@ -141,7 +143,7 @@ LOX_END | |
| 141 143 | 
             
                    it 'should parse the subtraction of two number literals' do
         | 
| 142 144 | 
             
                      input = '4 - 3;'
         | 
| 143 145 | 
             
                      ptree = subject.parse(input)
         | 
| 144 | 
            -
                      expr = ptree.root
         | 
| 146 | 
            +
                      expr = ptree.root.subnodes[0]
         | 
| 145 147 | 
             
                      expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| 146 148 | 
             
                      expect(expr.operator).to eq(:-)
         | 
| 147 149 | 
             
                      expect(expr.operands[0].literal.value).to eq(4)
         | 
| @@ -151,7 +153,7 @@ LOX_END | |
| 151 153 | 
             
                    it 'should parse multiple additive operations' do
         | 
| 152 154 | 
             
                      input = '5 + 2 - 3;'
         | 
| 153 155 | 
             
                      ptree = subject.parse(input)
         | 
| 154 | 
            -
                      expr = ptree.root
         | 
| 156 | 
            +
                      expr = ptree.root.subnodes[0]
         | 
| 155 157 | 
             
                      expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| 156 158 | 
             
                      expect(expr.operator).to eq(:-)
         | 
| 157 159 | 
             
                      expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| @@ -164,7 +166,7 @@ LOX_END | |
| 164 166 | 
             
                    it 'should parse the division of two number literals' do
         | 
| 165 167 | 
             
                      input = '8 / 2;'
         | 
| 166 168 | 
             
                      ptree = subject.parse(input)
         | 
| 167 | 
            -
                      expr = ptree.root
         | 
| 169 | 
            +
                      expr = ptree.root.subnodes[0]
         | 
| 168 170 | 
             
                      expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| 169 171 | 
             
                      expect(expr.operator).to eq(:/)
         | 
| 170 172 | 
             
                      expect(expr.operands[0].literal.value).to eq(8)
         | 
| @@ -174,7 +176,7 @@ LOX_END | |
| 174 176 | 
             
                    it 'should parse the product of two number literals' do
         | 
| 175 177 | 
             
                      input = '12.34 * 0.3;'
         | 
| 176 178 | 
             
                      ptree = subject.parse(input)
         | 
| 177 | 
            -
                      expr = ptree.root
         | 
| 179 | 
            +
                      expr = ptree.root.subnodes[0]
         | 
| 178 180 | 
             
                      expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| 179 181 | 
             
                      expect(expr.operator).to eq(:*)
         | 
| 180 182 | 
             
                      expect(expr.operands[0].literal.value).to eq(12.34)
         | 
| @@ -184,7 +186,7 @@ LOX_END | |
| 184 186 | 
             
                    it 'should parse multiple multiplicative operations' do
         | 
| 185 187 | 
             
                      input = '5 * 2 / 3;'
         | 
| 186 188 | 
             
                      ptree = subject.parse(input)
         | 
| 187 | 
            -
                      expr = ptree.root
         | 
| 189 | 
            +
                      expr = ptree.root.subnodes[0]
         | 
| 188 190 | 
             
                      expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| 189 191 | 
             
                      expect(expr.operator).to eq(:/)
         | 
| 190 192 | 
             
                      expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| @@ -197,7 +199,7 @@ LOX_END | |
| 197 199 | 
             
                    it 'should parse combination of terms and factors' do
         | 
| 198 200 | 
             
                      input = '5 + 2 / 3;'
         | 
| 199 201 | 
             
                      ptree = subject.parse(input)
         | 
| 200 | 
            -
                      expr = ptree.root
         | 
| 202 | 
            +
                      expr = ptree.root.subnodes[0]
         | 
| 201 203 | 
             
                      expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| 202 204 | 
             
                      expect(expr.operator).to eq(:+)
         | 
| 203 205 | 
             
                      expect(expr.operands[0].literal.value).to eq(5)
         | 
| @@ -212,7 +214,7 @@ LOX_END | |
| 212 214 | 
             
                    it 'should parse the concatenation of two string literals' do
         | 
| 213 215 | 
             
                      input = '"Lo" + "ve";'
         | 
| 214 216 | 
             
                      ptree = subject.parse(input)
         | 
| 215 | 
            -
                      expr = ptree.root
         | 
| 217 | 
            +
                      expr = ptree.root.subnodes[0]
         | 
| 216 218 | 
             
                      expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| 217 219 | 
             
                      expect(expr.operator).to eq(:+)
         | 
| 218 220 | 
             
                      expect(expr.operands[0].literal.value).to eq('Lo')
         | 
| @@ -225,7 +227,7 @@ LOX_END | |
| 225 227 | 
             
                    %w[> >= < <=].each do |predicate|
         | 
| 226 228 | 
             
                      input = "3 #{predicate} 2;"
         | 
| 227 229 | 
             
                      ptree = subject.parse(input)
         | 
| 228 | 
            -
                      expr = ptree.root
         | 
| 230 | 
            +
                      expr = ptree.root.subnodes[0]
         | 
| 229 231 | 
             
                      expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| 230 232 | 
             
                      expect(expr.operator).to eq(predicate.to_sym)
         | 
| 231 233 | 
             
                      expect(expr.operands[0].literal.value).to eq(3)
         | 
| @@ -239,7 +241,7 @@ LOX_END | |
| 239 241 | 
             
                      %w[!= ==].each do |predicate|
         | 
| 240 242 | 
             
                        input = "3 #{predicate} 2;"
         | 
| 241 243 | 
             
                        ptree = subject.parse(input)
         | 
| 242 | 
            -
                        expr = ptree.root
         | 
| 244 | 
            +
                        expr = ptree.root.subnodes[0]
         | 
| 243 245 | 
             
                        expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| 244 246 | 
             
                        expect(expr.operator).to eq(predicate.to_sym)
         | 
| 245 247 | 
             
                        expect(expr.operands[0].literal.value).to eq(3)
         | 
| @@ -250,7 +252,7 @@ LOX_END | |
| 250 252 | 
             
                    it 'should parse combination of equality expressions' do
         | 
| 251 253 | 
             
                      input = '5 != 2 == false; // A bit contrived example'
         | 
| 252 254 | 
             
                      ptree = subject.parse(input)
         | 
| 253 | 
            -
                      expr = ptree.root
         | 
| 255 | 
            +
                      expr = ptree.root.subnodes[0]
         | 
| 254 256 | 
             
                      expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| 255 257 | 
             
                      expect(expr.operator).to eq(:==)
         | 
| 256 258 | 
             
                      expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| @@ -266,7 +268,7 @@ LOX_END | |
| 266 268 | 
             
                      %w[or and].each do |connector|
         | 
| 267 269 | 
             
                        input = "5 > 2 #{connector} 3 <= 4;"
         | 
| 268 270 | 
             
                        ptree = subject.parse(input)
         | 
| 269 | 
            -
                        expr = ptree.root
         | 
| 271 | 
            +
                        expr = ptree.root.subnodes[0]
         | 
| 270 272 | 
             
                        expect(expr).to be_kind_of(Ast::LoxLogicalExpr)
         | 
| 271 273 | 
             
                        expect(expr.operator).to eq(connector.to_sym)
         | 
| 272 274 | 
             
                        expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
         | 
| @@ -283,7 +285,7 @@ LOX_END | |
| 283 285 | 
             
                    it 'should parse a combinations of logical expressions' do
         | 
| 284 286 | 
             
                      input = '4 > 3 and 1 < 2 or 4 >= 5;'
         | 
| 285 287 | 
             
                      ptree = subject.parse(input)
         | 
| 286 | 
            -
                      expr = ptree.root
         | 
| 288 | 
            +
                      expr = ptree.root.subnodes[0]
         | 
| 287 289 | 
             
                      expect(expr).to be_kind_of(Ast::LoxLogicalExpr)
         | 
| 288 290 | 
             
                      expect(expr.operator).to eq(:or) # or has lower precedence than and
         | 
| 289 291 | 
             
                      expect(expr.operands[0]).to be_kind_of(Ast::LoxLogicalExpr)
         |