loxxy 0.4.09 → 0.4.10
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/.rubocop.yml +32 -304
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +1 -1
- data/lib/loxxy/ast/ast_builder.rb +1 -1
- data/lib/loxxy/back_end/engine.rb +2 -0
- data/lib/loxxy/back_end/environment.rb +1 -1
- data/lib/loxxy/front_end/scanner.rb +9 -9
- data/lib/loxxy/interpreter.rb +3 -3
- data/lib/loxxy/version.rb +1 -1
- data/loxxy.gemspec +8 -7
- data/spec/back_end/engine_spec.rb +21 -19
- data/spec/back_end/environment_spec.rb +20 -19
- data/spec/back_end/symbol_table_spec.rb +67 -67
- data/spec/back_end/variable_spec.rb +14 -13
- data/spec/datatype/boolean_spec.rb +9 -8
- data/spec/datatype/lx_string_spec.rb +16 -15
- data/spec/datatype/nil_spec.rb +5 -5
- data/spec/datatype/number_spec.rb +18 -17
- data/spec/front_end/parser_spec.rb +110 -110
- data/spec/front_end/raw_parser_spec.rb +25 -25
- data/spec/front_end/scanner_spec.rb +77 -76
- data/spec/interpreter_spec.rb +114 -118
- data/spec/loxxy_spec.rb +1 -1
- metadata +33 -27
    
        data/spec/interpreter_spec.rb
    CHANGED
    
    | @@ -1,7 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require_relative 'spec_helper' # Use the RSpec framework
         | 
| 4 | 
            -
            require 'stringio'
         | 
| 5 4 |  | 
| 6 5 | 
             
            # Load the class under test
         | 
| 7 6 | 
             
            require_relative '../lib/loxxy/interpreter'
         | 
| @@ -15,57 +14,58 @@ module Loxxy | |
| 15 14 | 
             
                let(:sample_cfg) do
         | 
| 16 15 | 
             
                  { ostream: StringIO.new }
         | 
| 17 16 | 
             
                end
         | 
| 18 | 
            -
             | 
| 17 | 
            +
             | 
| 18 | 
            +
                subject(:interpreter) { described_class.new(sample_cfg) }
         | 
| 19 19 |  | 
| 20 20 | 
             
                context 'Initialization:' do
         | 
| 21 | 
            -
                  it ' | 
| 22 | 
            -
                    expect {  | 
| 21 | 
            +
                  it 'accepts a option Hash at initialization' do
         | 
| 22 | 
            +
                    expect { described_class.new(sample_cfg) }.not_to raise_error
         | 
| 23 23 | 
             
                  end
         | 
| 24 24 |  | 
| 25 | 
            -
                  it ' | 
| 26 | 
            -
                    expect( | 
| 25 | 
            +
                  it 'knows its config options' do
         | 
| 26 | 
            +
                    expect(interpreter.config).to eq(sample_cfg)
         | 
| 27 27 | 
             
                  end
         | 
| 28 28 | 
             
                end # context
         | 
| 29 29 |  | 
| 30 30 | 
             
                context 'Evaluating arithmetic operations code:' do
         | 
| 31 | 
            -
                  it ' | 
| 32 | 
            -
                    result =  | 
| 33 | 
            -
                    expect(result).to  | 
| 31 | 
            +
                  it 'evaluates an addition of two numbers' do
         | 
| 32 | 
            +
                    result = interpreter.evaluate('123 + 456; // => 579')
         | 
| 33 | 
            +
                    expect(result).to be_a(Loxxy::Datatype::Number)
         | 
| 34 34 | 
             
                    expect(result == 579).to be_true
         | 
| 35 35 | 
             
                  end
         | 
| 36 36 |  | 
| 37 | 
            -
                  it ' | 
| 38 | 
            -
                    result =  | 
| 39 | 
            -
                    expect(result).to  | 
| 37 | 
            +
                  it 'evaluates a subtraction of two numbers' do
         | 
| 38 | 
            +
                    result = interpreter.evaluate('4 - 3; // => 1')
         | 
| 39 | 
            +
                    expect(result).to be_a(Loxxy::Datatype::Number)
         | 
| 40 40 | 
             
                    expect(result == 1).to be_true
         | 
| 41 41 | 
             
                  end
         | 
| 42 42 |  | 
| 43 | 
            -
                  it ' | 
| 44 | 
            -
                    result =  | 
| 45 | 
            -
                    expect(result).to  | 
| 43 | 
            +
                  it 'evaluates a multiplication of two numbers' do
         | 
| 44 | 
            +
                    result = interpreter.evaluate('5 * 3; // => 15')
         | 
| 45 | 
            +
                    expect(result).to be_a(Loxxy::Datatype::Number)
         | 
| 46 46 | 
             
                    expect(result == 15).to be_true
         | 
| 47 47 | 
             
                  end
         | 
| 48 48 |  | 
| 49 | 
            -
                  it ' | 
| 50 | 
            -
                    result =  | 
| 51 | 
            -
                    expect(result).to  | 
| 49 | 
            +
                  it 'evaluates a division of two numbers' do
         | 
| 50 | 
            +
                    result = interpreter.evaluate('8 / 2; // => 4')
         | 
| 51 | 
            +
                    expect(result).to be_a(Loxxy::Datatype::Number)
         | 
| 52 52 | 
             
                    expect(result == 4).to be_true
         | 
| 53 53 | 
             
                  end
         | 
| 54 54 | 
             
                end # context
         | 
| 55 55 |  | 
| 56 56 | 
             
                context 'Evaluating Lox code:' do
         | 
| 57 | 
            -
                  it ' | 
| 58 | 
            -
                    result =  | 
| 59 | 
            -
                    expect(result).to  | 
| 57 | 
            +
                  it 'evaluates core data types' do
         | 
| 58 | 
            +
                    result = interpreter.evaluate('true; // Not false')
         | 
| 59 | 
            +
                    expect(result).to be_a(Loxxy::Datatype::True)
         | 
| 60 60 | 
             
                  end
         | 
| 61 61 |  | 
| 62 | 
            -
                  it ' | 
| 63 | 
            -
                    result =  | 
| 64 | 
            -
                    expect(result).to  | 
| 62 | 
            +
                  it 'evaluates string concatenation' do
         | 
| 63 | 
            +
                    result = interpreter.evaluate('"str" + "ing"; // => "string"')
         | 
| 64 | 
            +
                    expect(result).to be_a(Loxxy::Datatype::LXString)
         | 
| 65 65 | 
             
                    expect(result == 'string').to be_true
         | 
| 66 66 | 
             
                  end
         | 
| 67 67 |  | 
| 68 | 
            -
                  it ' | 
| 68 | 
            +
                  it 'performs the equality tests of two values' do
         | 
| 69 69 | 
             
                    [
         | 
| 70 70 | 
             
                      ['nil == nil;', true],
         | 
| 71 71 | 
             
                      ['true == true;', true],
         | 
| @@ -80,13 +80,13 @@ module Loxxy | |
| 80 80 | 
             
                      ['false == 0;', false],
         | 
| 81 81 | 
             
                      ['0 == "0";', false]
         | 
| 82 82 | 
             
                    ].each do |(source, predicted)|
         | 
| 83 | 
            -
                      lox =  | 
| 83 | 
            +
                      lox = described_class.new
         | 
| 84 84 | 
             
                      result = lox.evaluate(source)
         | 
| 85 85 | 
             
                      expect(result.value == predicted).to be_truthy
         | 
| 86 86 | 
             
                    end
         | 
| 87 87 | 
             
                  end
         | 
| 88 88 |  | 
| 89 | 
            -
                  it ' | 
| 89 | 
            +
                  it 'performs the inequality test of two values' do
         | 
| 90 90 | 
             
                    [
         | 
| 91 91 | 
             
                      ['nil != nil;', false],
         | 
| 92 92 | 
             
                      ['true != true;', false],
         | 
| @@ -101,13 +101,13 @@ module Loxxy | |
| 101 101 | 
             
                      ['false != 0;', true],
         | 
| 102 102 | 
             
                      ['0 != "0";', true]
         | 
| 103 103 | 
             
                    ].each do |(source, predicted)|
         | 
| 104 | 
            -
                      lox =  | 
| 104 | 
            +
                      lox = described_class.new
         | 
| 105 105 | 
             
                      result = lox.evaluate(source)
         | 
| 106 106 | 
             
                      expect(result.value == predicted).to be_truthy
         | 
| 107 107 | 
             
                    end
         | 
| 108 108 | 
             
                  end
         | 
| 109 109 |  | 
| 110 | 
            -
                  it ' | 
| 110 | 
            +
                  it 'evaluates a comparison of two numbers' do
         | 
| 111 111 | 
             
                    [
         | 
| 112 112 | 
             
                      ['1 < 2;', true],
         | 
| 113 113 | 
             
                      ['2 < 2;', false],
         | 
| @@ -130,38 +130,38 @@ module Loxxy | |
| 130 130 | 
             
                      ['0 >= -0;', true],
         | 
| 131 131 | 
             
                      ['-0 >= 0;', true]
         | 
| 132 132 | 
             
                    ].each do |(source, predicted)|
         | 
| 133 | 
            -
                      lox =  | 
| 133 | 
            +
                      lox = described_class.new
         | 
| 134 134 | 
             
                      result = lox.evaluate(source)
         | 
| 135 135 | 
             
                      expect(result.value == predicted).to be_truthy
         | 
| 136 136 | 
             
                    end
         | 
| 137 137 | 
             
                  end
         | 
| 138 138 |  | 
| 139 | 
            -
                  it ' | 
| 139 | 
            +
                  it 'evaluates the change sign of a number' do
         | 
| 140 140 | 
             
                    [
         | 
| 141 141 | 
             
                      ['- 3;', -3],
         | 
| 142 142 | 
             
                      ['- - 3;', 3],
         | 
| 143 143 | 
             
                      ['- - - 3;', -3]
         | 
| 144 144 | 
             
                    ].each do |(source, predicted)|
         | 
| 145 | 
            -
                      lox =  | 
| 145 | 
            +
                      lox = described_class.new
         | 
| 146 146 | 
             
                      result = lox.evaluate(source)
         | 
| 147 147 | 
             
                      expect(result.value == predicted).to be_truthy
         | 
| 148 148 | 
             
                    end
         | 
| 149 149 | 
             
                  end
         | 
| 150 150 |  | 
| 151 | 
            -
                  it ' | 
| 151 | 
            +
                  it 'ignores spaces surrounding minus in subtraction of two numbers' do
         | 
| 152 152 | 
             
                    [
         | 
| 153 153 | 
             
                      ['1 - 1;', 0],
         | 
| 154 154 | 
             
                      ['1 -1;', 0],
         | 
| 155 155 | 
             
                      ['1- 1;', 0],
         | 
| 156 156 | 
             
                      ['1-1;', 0]
         | 
| 157 157 | 
             
                    ].each do |(source, predicted)|
         | 
| 158 | 
            -
                      lox =  | 
| 158 | 
            +
                      lox = described_class.new
         | 
| 159 159 | 
             
                      result = lox.evaluate(source)
         | 
| 160 160 | 
             
                      expect(result.value == predicted).to be_truthy
         | 
| 161 161 | 
             
                    end
         | 
| 162 162 | 
             
                  end
         | 
| 163 163 |  | 
| 164 | 
            -
                  it ' | 
| 164 | 
            +
                  it 'evaluates the negation of an object' do
         | 
| 165 165 | 
             
                    [
         | 
| 166 166 | 
             
                      ['!true;', false],
         | 
| 167 167 | 
             
                      ['!false;', true],
         | 
| @@ -171,13 +171,13 @@ module Loxxy | |
| 171 171 | 
             
                      ['!nil;', true],
         | 
| 172 172 | 
             
                      ['!"";', false]
         | 
| 173 173 | 
             
                    ].each do |(source, predicted)|
         | 
| 174 | 
            -
                      lox =  | 
| 174 | 
            +
                      lox = described_class.new
         | 
| 175 175 | 
             
                      result = lox.evaluate(source)
         | 
| 176 176 | 
             
                      expect(result.value == predicted).to be_truthy
         | 
| 177 177 | 
             
                    end
         | 
| 178 178 | 
             
                  end
         | 
| 179 179 |  | 
| 180 | 
            -
                  it ' | 
| 180 | 
            +
                  it 'evaluates the "conjunction" of two values' do
         | 
| 181 181 | 
             
                    [
         | 
| 182 182 | 
             
                      # Return the first falsey argument
         | 
| 183 183 | 
             
                      ['false and 1;', false],
         | 
| @@ -194,13 +194,13 @@ module Loxxy | |
| 194 194 |  | 
| 195 195 | 
             
                      # TODO test short-circuit at first false argument
         | 
| 196 196 | 
             
                    ].each do |(source, predicted)|
         | 
| 197 | 
            -
                      lox =  | 
| 197 | 
            +
                      lox = described_class.new
         | 
| 198 198 | 
             
                      result = lox.evaluate(source)
         | 
| 199 199 | 
             
                      expect(result.value == predicted).to be_truthy
         | 
| 200 200 | 
             
                    end
         | 
| 201 201 | 
             
                  end
         | 
| 202 202 |  | 
| 203 | 
            -
                  it ' | 
| 203 | 
            +
                  it 'evaluates the "disjunction" of two values' do
         | 
| 204 204 | 
             
                    [
         | 
| 205 205 | 
             
                      # Return the first truthy argument
         | 
| 206 206 | 
             
                      ['1 or true;', 1],
         | 
| @@ -218,25 +218,25 @@ module Loxxy | |
| 218 218 |  | 
| 219 219 | 
             
                      # TODO test short-circuit at first false argument
         | 
| 220 220 | 
             
                    ].each do |(source, predicted)|
         | 
| 221 | 
            -
                      lox =  | 
| 221 | 
            +
                      lox = described_class.new
         | 
| 222 222 | 
             
                      result = lox.evaluate(source)
         | 
| 223 223 | 
             
                      expect(result.value == predicted).to be_truthy
         | 
| 224 224 | 
             
                    end
         | 
| 225 225 | 
             
                  end
         | 
| 226 226 |  | 
| 227 | 
            -
                  it ' | 
| 227 | 
            +
                  it 'supports expressions between parentheses' do
         | 
| 228 228 | 
             
                    [
         | 
| 229 229 | 
             
                      ['3 + 4 * 5;', 23],
         | 
| 230 230 | 
             
                      ['(3 + 4) * 5;', 35],
         | 
| 231 231 | 
             
                      ['(5 - (3 - 1)) + -(1);', 2]
         | 
| 232 232 | 
             
                    ].each do |(source, predicted)|
         | 
| 233 | 
            -
                      lox =  | 
| 233 | 
            +
                      lox = described_class.new
         | 
| 234 234 | 
             
                      result = lox.evaluate(source)
         | 
| 235 235 | 
             
                      expect(result.value == predicted).to be_truthy
         | 
| 236 236 | 
             
                    end
         | 
| 237 237 | 
             
                  end
         | 
| 238 238 |  | 
| 239 | 
            -
                  it ' | 
| 239 | 
            +
                  it 'evaluates an if statement' do
         | 
| 240 240 | 
             
                    [
         | 
| 241 241 | 
             
                      # Evaluate the 'then' expression if the condition is true.
         | 
| 242 242 | 
             
                      ['if (true) print "then-branch";', 'then-branch'],
         | 
| @@ -258,40 +258,40 @@ module Loxxy | |
| 258 258 | 
             
                    ].each do |(source, predicted)|
         | 
| 259 259 | 
             
                      io = StringIO.new
         | 
| 260 260 | 
             
                      cfg = { ostream: io }
         | 
| 261 | 
            -
                      lox =  | 
| 261 | 
            +
                      lox = described_class.new(cfg)
         | 
| 262 262 | 
             
                      lox.evaluate(source)
         | 
| 263 263 | 
             
                      expect(io.string).to eq(predicted)
         | 
| 264 264 | 
             
                    end
         | 
| 265 265 | 
             
                  end
         | 
| 266 266 |  | 
| 267 | 
            -
                  it ' | 
| 267 | 
            +
                  it 'accepts variable declarations' do
         | 
| 268 268 | 
             
                    # Variable with initialization value
         | 
| 269 269 | 
             
                    var_decl = 'var iAmAVariable = "here is my value";'
         | 
| 270 | 
            -
                    expect {  | 
| 270 | 
            +
                    expect { interpreter.evaluate(var_decl) }.not_to raise_error
         | 
| 271 271 |  | 
| 272 272 | 
             
                    # Variable without initialization value
         | 
| 273 | 
            -
                    expect {  | 
| 273 | 
            +
                    expect { interpreter.evaluate('var iAmNil;') }.not_to raise_error
         | 
| 274 274 | 
             
                  end
         | 
| 275 275 |  | 
| 276 | 
            -
                  it ' | 
| 276 | 
            +
                  it 'accepts variable mention' do
         | 
| 277 277 | 
             
                    program = <<-LOX_END
         | 
| 278 278 | 
             
                      var foo = "bar";
         | 
| 279 279 | 
             
                      print foo; // => bar
         | 
| 280 280 | 
             
                    LOX_END
         | 
| 281 | 
            -
                    expect {  | 
| 281 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 282 282 | 
             
                    expect(sample_cfg[:ostream].string).to eq('bar')
         | 
| 283 283 | 
             
                  end
         | 
| 284 284 |  | 
| 285 | 
            -
                  it ' | 
| 285 | 
            +
                  it 'sets uninitialized variables to nil' do
         | 
| 286 286 | 
             
                    program = <<-LOX_END
         | 
| 287 287 | 
             
                    var a;
         | 
| 288 288 | 
             
                    print a; // => nil
         | 
| 289 289 | 
             
            LOX_END
         | 
| 290 | 
            -
                    expect {  | 
| 290 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 291 291 | 
             
                    expect(sample_cfg[:ostream].string).to eq('nil')
         | 
| 292 292 | 
             
                  end
         | 
| 293 293 |  | 
| 294 | 
            -
                  it ' | 
| 294 | 
            +
                  it 'accepts assignments to a global variable' do
         | 
| 295 295 | 
             
                    program = <<-LOX_END
         | 
| 296 296 | 
             
                      var a = "before";
         | 
| 297 297 | 
             
                      print a; // output: before
         | 
| @@ -302,11 +302,11 @@ LOX_END | |
| 302 302 | 
             
                      print a = "arg"; // output: arg
         | 
| 303 303 | 
             
                      print a; // output: arg
         | 
| 304 304 | 
             
                    LOX_END
         | 
| 305 | 
            -
                    expect {  | 
| 305 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 306 306 | 
             
                    expect(sample_cfg[:ostream].string).to eq('beforeafterargarg')
         | 
| 307 307 | 
             
                  end
         | 
| 308 308 |  | 
| 309 | 
            -
                  it ' | 
| 309 | 
            +
                  it 'supports variables local to a block' do
         | 
| 310 310 | 
             
                    program = <<-LOX_END
         | 
| 311 311 | 
             
                    {
         | 
| 312 312 | 
             
                      var a = "first";
         | 
| @@ -317,11 +317,11 @@ LOX_END | |
| 317 317 | 
             
                      print a;
         | 
| 318 318 | 
             
                    }
         | 
| 319 319 | 
             
                    LOX_END
         | 
| 320 | 
            -
                    expect {  | 
| 320 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 321 321 | 
             
                    expect(sample_cfg[:ostream].string).to eq('firstsecond')
         | 
| 322 322 | 
             
                  end
         | 
| 323 323 |  | 
| 324 | 
            -
                  it ' | 
| 324 | 
            +
                  it 'supports the shadowing of variables in a block' do
         | 
| 325 325 | 
             
                    program = <<-LOX_END
         | 
| 326 326 | 
             
                    var a = "outer";
         | 
| 327 327 |  | 
| @@ -332,11 +332,11 @@ LOX_END | |
| 332 332 |  | 
| 333 333 | 
             
                    print a; // output: outer
         | 
| 334 334 | 
             
                    LOX_END
         | 
| 335 | 
            -
                    expect {  | 
| 335 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 336 336 | 
             
                    expect(sample_cfg[:ostream].string).to eq('innerouter')
         | 
| 337 337 | 
             
                  end
         | 
| 338 338 |  | 
| 339 | 
            -
                  it ' | 
| 339 | 
            +
                  it 'implements single statement while loops' do
         | 
| 340 340 | 
             
                    program = <<-LOX_END
         | 
| 341 341 | 
             
                      // Single-expression body.
         | 
| 342 342 | 
             
                      var c = 0;
         | 
| @@ -345,11 +345,11 @@ LOX_END | |
| 345 345 | 
             
                      // output: 2
         | 
| 346 346 | 
             
                      // output: 3
         | 
| 347 347 | 
             
                    LOX_END
         | 
| 348 | 
            -
                    expect {  | 
| 348 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 349 349 | 
             
                    expect(sample_cfg[:ostream].string).to eq('123')
         | 
| 350 350 | 
             
                  end
         | 
| 351 351 |  | 
| 352 | 
            -
                  it ' | 
| 352 | 
            +
                  it 'implements block body while loops' do
         | 
| 353 353 | 
             
                    program = <<-LOX_END
         | 
| 354 354 | 
             
                      // Block body.
         | 
| 355 355 | 
             
                      var a = 0;
         | 
| @@ -361,11 +361,11 @@ LOX_END | |
| 361 361 | 
             
                      // output: 1
         | 
| 362 362 | 
             
                      // output: 2
         | 
| 363 363 | 
             
                    LOX_END
         | 
| 364 | 
            -
                    expect {  | 
| 364 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 365 365 | 
             
                    expect(sample_cfg[:ostream].string).to eq('012')
         | 
| 366 366 | 
             
                  end
         | 
| 367 367 |  | 
| 368 | 
            -
                  it ' | 
| 368 | 
            +
                  it 'implements single statement for loops' do
         | 
| 369 369 | 
             
                    program = <<-LOX_END
         | 
| 370 370 | 
             
                      // Single-expression body.
         | 
| 371 371 | 
             
                      for (var c = 0; c < 3;) print c = c + 1;
         | 
| @@ -373,11 +373,11 @@ LOX_END | |
| 373 373 | 
             
                      // output: 2
         | 
| 374 374 | 
             
                      // output: 3
         | 
| 375 375 | 
             
                    LOX_END
         | 
| 376 | 
            -
                    expect {  | 
| 376 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 377 377 | 
             
                    expect(sample_cfg[:ostream].string).to eq('123')
         | 
| 378 378 | 
             
                  end
         | 
| 379 379 |  | 
| 380 | 
            -
                  it ' | 
| 380 | 
            +
                  it 'implements for loops with block body' do
         | 
| 381 381 | 
             
                    program = <<-LOX_END
         | 
| 382 382 | 
             
                      // Block body.
         | 
| 383 383 | 
             
                      for (var a = 0; a < 3; a = a + 1) {
         | 
| @@ -387,81 +387,77 @@ LOX_END | |
| 387 387 | 
             
                      // output: 1
         | 
| 388 388 | 
             
                      // output: 2
         | 
| 389 389 | 
             
                    LOX_END
         | 
| 390 | 
            -
                    expect {  | 
| 390 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 391 391 | 
             
                    expect(sample_cfg[:ostream].string).to eq('012')
         | 
| 392 392 | 
             
                  end
         | 
| 393 393 |  | 
| 394 | 
            -
                  it ' | 
| 394 | 
            +
                  it 'implements nullary function calls' do
         | 
| 395 395 | 
             
                    program = <<-LOX_END
         | 
| 396 396 | 
             
                      print clock(); // Lox expects the 'clock' predefined native function
         | 
| 397 397 | 
             
                    LOX_END
         | 
| 398 | 
            -
                    expect {  | 
| 398 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 399 399 | 
             
                    tick = sample_cfg[:ostream].string
         | 
| 400 400 | 
             
                    expect(Time.now.to_f - tick.to_f).to be < 0.1
         | 
| 401 401 | 
             
                  end
         | 
| 402 402 |  | 
| 403 | 
            -
                  it ' | 
| 403 | 
            +
                  it 'implements function definition' do
         | 
| 404 404 | 
             
                    program = <<-LOX_END
         | 
| 405 405 | 
             
                      fun printSum(a, b) {
         | 
| 406 406 | 
             
                         print a + b;
         | 
| 407 407 | 
             
                      }
         | 
| 408 408 | 
             
                      printSum(1, 2);
         | 
| 409 409 | 
             
                    LOX_END
         | 
| 410 | 
            -
                    expect {  | 
| 410 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 411 411 | 
             
                    expect(sample_cfg[:ostream].string).to eq('3')
         | 
| 412 412 | 
             
                  end
         | 
| 413 413 |  | 
| 414 | 
            -
                  it ' | 
| 414 | 
            +
                  it 'supports functions with empty body' do
         | 
| 415 415 | 
             
                    program = <<-LOX_END
         | 
| 416 416 | 
             
                      fun f() {}
         | 
| 417 417 | 
             
                      print f();
         | 
| 418 418 | 
             
                    LOX_END
         | 
| 419 | 
            -
                    expect {  | 
| 419 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 420 420 | 
             
                    expect(sample_cfg[:ostream].string).to eq('nil')
         | 
| 421 421 | 
             
                  end
         | 
| 422 422 |  | 
| 423 | 
            -
                  it ' | 
| 423 | 
            +
                  it 'provides print representation of functions' do
         | 
| 424 424 | 
             
                    program = <<-LOX_END
         | 
| 425 425 | 
             
                      fun foo() {}
         | 
| 426 426 | 
             
                      print foo; // output: <fn foo>
         | 
| 427 427 | 
             
                      print clock; // output: <native fn>
         | 
| 428 428 | 
             
                    LOX_END
         | 
| 429 | 
            -
                    expect {  | 
| 429 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 430 430 | 
             
                    expect(sample_cfg[:ostream].string).to eq('<fn foo><native fn>')
         | 
| 431 431 | 
             
                  end
         | 
| 432 432 |  | 
| 433 | 
            -
                  it " | 
| 433 | 
            +
                  it "implements 'getc' function" do
         | 
| 434 434 | 
             
                    input_str = 'Abc'
         | 
| 435 435 | 
             
                    cfg = { istream: StringIO.new(input_str) }
         | 
| 436 | 
            -
                    interpreter =  | 
| 436 | 
            +
                    interpreter = described_class.new(cfg)
         | 
| 437 437 | 
             
                    source = 'getc();'
         | 
| 438 438 | 
             
                    result = interpreter.evaluate(source)
         | 
| 439 439 | 
             
                    expect(result.value).to eq(65) # codepoint for letter 'A'
         | 
| 440 440 | 
             
                  end
         | 
| 441 441 |  | 
| 442 | 
            -
                  it " | 
| 442 | 
            +
                  it "implements 'chr' function" do
         | 
| 443 443 | 
             
                    source = 'chr(65); // => "A"'
         | 
| 444 | 
            -
                    result =  | 
| 444 | 
            +
                    result = interpreter.evaluate(source)
         | 
| 445 445 | 
             
                    expect(result.value).to eq('A')
         | 
| 446 446 | 
             
                  end
         | 
| 447 447 |  | 
| 448 448 | 
             
                  # This test is disabled since it causes RSpec to stop immediately
         | 
| 449 | 
            -
                  # it " | 
| 449 | 
            +
                  # it "implements 'exit' function" do
         | 
| 450 450 | 
             
                  #   source = 'exit(100); // Process halts with exit code 100'
         | 
| 451 | 
            -
                  #   expect {  | 
| 451 | 
            +
                  #   expect { interpreter.evaluate(source) }.to raise(SystemExit)
         | 
| 452 452 | 
             
                  # end
         | 
| 453 453 |  | 
| 454 | 
            -
                  it " | 
| 454 | 
            +
                  it "implements 'print_error' function" do
         | 
| 455 455 | 
             
                    source = 'print_error("Some error"); // => Some error on stderr'
         | 
| 456 | 
            -
                     | 
| 457 | 
            -
                    $stderr = StringIO.new
         | 
| 458 | 
            -
                    expect { subject.evaluate(source) }.not_to raise_error
         | 
| 459 | 
            -
                    expect($stderr.string).to eq('Some error')
         | 
| 460 | 
            -
                    $stderr = stderr_backup
         | 
| 456 | 
            +
                    expect { interpreter.evaluate(source) }.to output('Some error').to_stderr
         | 
| 461 457 | 
             
                  end
         | 
| 462 458 |  | 
| 463 459 | 
             
                  # rubocop: disable Style/StringConcatenation
         | 
| 464 | 
            -
                  it ' | 
| 460 | 
            +
                  it 'returns in absence of explicit return statement' do
         | 
| 465 461 | 
             
                    program = <<-LOX_END
         | 
| 466 462 | 
             
                      fun foo() {
         | 
| 467 463 | 
             
                        print "foo";
         | 
| @@ -469,12 +465,12 @@ LOX_END | |
| 469 465 |  | 
| 470 466 | 
             
                      print foo();
         | 
| 471 467 | 
             
                    LOX_END
         | 
| 472 | 
            -
                    expect {  | 
| 468 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 473 469 | 
             
                    expect(sample_cfg[:ostream].string).to eq('foo' + 'nil')
         | 
| 474 470 | 
             
                  end
         | 
| 475 471 | 
             
                  # rubocop: enable Style/StringConcatenation
         | 
| 476 472 |  | 
| 477 | 
            -
                  it ' | 
| 473 | 
            +
                  it 'supports return statements' do
         | 
| 478 474 | 
             
                    program = <<-LOX_END
         | 
| 479 475 | 
             
                      fun max(a, b) {
         | 
| 480 476 | 
             
                        if (a > b) return a;
         | 
| @@ -484,23 +480,23 @@ LOX_END | |
| 484 480 |  | 
| 485 481 | 
             
                      max(3, 2);
         | 
| 486 482 | 
             
                    LOX_END
         | 
| 487 | 
            -
                    result =  | 
| 483 | 
            +
                    result = interpreter.evaluate(program)
         | 
| 488 484 | 
             
                    expect(result).to eq(3)
         | 
| 489 485 | 
             
                  end
         | 
| 490 486 |  | 
| 491 | 
            -
                  it ' | 
| 487 | 
            +
                  it 'supports return within statements inside a function' do
         | 
| 492 488 | 
             
                    program = <<-LOX_END
         | 
| 493 489 | 
             
                      fun foo() {
         | 
| 494 490 | 
             
                        for (;;) return "done";
         | 
| 495 491 | 
             
                      }
         | 
| 496 492 | 
             
                      print foo(); // output: done
         | 
| 497 493 | 
             
                    LOX_END
         | 
| 498 | 
            -
                    expect {  | 
| 494 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 499 495 | 
             
                    expect(sample_cfg[:ostream].string).to eq('done')
         | 
| 500 496 | 
             
                  end
         | 
| 501 497 |  | 
| 502 498 | 
             
                  # rubocop: disable Style/StringConcatenation
         | 
| 503 | 
            -
                  it ' | 
| 499 | 
            +
                  it 'supports local functions and closures' do
         | 
| 504 500 | 
             
                    program = <<-LOX_END
         | 
| 505 501 | 
             
                      fun makeCounter() {
         | 
| 506 502 | 
             
                        var i = 0;
         | 
| @@ -516,17 +512,17 @@ LOX_END | |
| 516 512 | 
             
                      counter(); // "1".
         | 
| 517 513 | 
             
                      counter(); // "2".
         | 
| 518 514 | 
             
                    LOX_END
         | 
| 519 | 
            -
                    expect {  | 
| 515 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 520 516 | 
             
                    expect(sample_cfg[:ostream].string).to eq('1' + '2')
         | 
| 521 517 | 
             
                  end
         | 
| 522 518 | 
             
                  # rubocop: enable Style/StringConcatenation
         | 
| 523 519 |  | 
| 524 | 
            -
                  it ' | 
| 520 | 
            +
                  it 'prints the hello world message' do
         | 
| 525 521 | 
             
                    program = <<-LOX_END
         | 
| 526 522 | 
             
                      var greeting = "Hello"; // Declaring a variable
         | 
| 527 523 | 
             
                      print greeting + ", " + "world!"; // ... Playing with concatenation
         | 
| 528 524 | 
             
                    LOX_END
         | 
| 529 | 
            -
                    expect {  | 
| 525 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 530 526 | 
             
                    expect(sample_cfg[:ostream].string).to eq('Hello, world!')
         | 
| 531 527 | 
             
                  end
         | 
| 532 528 | 
             
                end # context
         | 
| @@ -548,7 +544,7 @@ LOX_END | |
| 548 544 | 
             
                    snippet
         | 
| 549 545 | 
             
                  end
         | 
| 550 546 |  | 
| 551 | 
            -
                  it ' | 
| 547 | 
            +
                  it 'supports field assignment expression' do
         | 
| 552 548 | 
             
                    program = <<-LOX_END
         | 
| 553 549 | 
             
                      class Foo {}
         | 
| 554 550 |  | 
| @@ -556,43 +552,43 @@ LOX_END | |
| 556 552 |  | 
| 557 553 | 
             
                      print foo.bar = "bar value"; // expect: bar value
         | 
| 558 554 | 
             
                    LOX_END
         | 
| 559 | 
            -
                    expect {  | 
| 555 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 560 556 | 
             
                    expect(sample_cfg[:ostream].string).to eq('bar value')
         | 
| 561 557 | 
             
                  end
         | 
| 562 558 |  | 
| 563 | 
            -
                  it ' | 
| 559 | 
            +
                  it 'supports class declaration' do
         | 
| 564 560 | 
             
                    program = <<-LOX_END
         | 
| 565 561 | 
             
                      #{duck_class}
         | 
| 566 562 |  | 
| 567 563 | 
             
                      print Duck; // Class names can appear in statements
         | 
| 568 564 | 
             
                    LOX_END
         | 
| 569 | 
            -
                    expect {  | 
| 565 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 570 566 | 
             
                    expect(sample_cfg[:ostream].string).to eq('Duck')
         | 
| 571 567 | 
             
                  end
         | 
| 572 568 |  | 
| 573 | 
            -
                  it ' | 
| 569 | 
            +
                  it 'supports default instance creation' do
         | 
| 574 570 | 
             
                    program = <<-LOX_END
         | 
| 575 571 | 
             
                      #{duck_class}
         | 
| 576 572 |  | 
| 577 573 | 
             
                      var daffy = Duck(); // Default constructor
         | 
| 578 574 | 
             
                      print daffy;
         | 
| 579 575 | 
             
                    LOX_END
         | 
| 580 | 
            -
                    expect {  | 
| 576 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 581 577 | 
             
                    expect(sample_cfg[:ostream].string).to eq('Duck instance')
         | 
| 582 578 | 
             
                  end
         | 
| 583 579 |  | 
| 584 | 
            -
                  it ' | 
| 580 | 
            +
                  it 'supports calls to method' do
         | 
| 585 581 | 
             
                    program = <<-LOX_END
         | 
| 586 582 | 
             
                      #{duck_class}
         | 
| 587 583 |  | 
| 588 584 | 
             
                      var daffy = Duck(); // Default constructor
         | 
| 589 585 | 
             
                      daffy.quack();
         | 
| 590 586 | 
             
                    LOX_END
         | 
| 591 | 
            -
                    expect {  | 
| 587 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 592 588 | 
             
                    expect(sample_cfg[:ostream].string).to eq('quack')
         | 
| 593 589 | 
             
                  end
         | 
| 594 590 |  | 
| 595 | 
            -
                  it " | 
| 591 | 
            +
                  it "supports the 'this' keyword" do
         | 
| 596 592 | 
             
                    program = <<-LOX_END
         | 
| 597 593 | 
             
                      class Egotist {
         | 
| 598 594 | 
             
                        speak() {
         | 
| @@ -603,11 +599,11 @@ LOX_END | |
| 603 599 | 
             
                      var method = Egotist().speak;
         | 
| 604 600 | 
             
                      method(); // Output: Egotist instance
         | 
| 605 601 | 
             
                    LOX_END
         | 
| 606 | 
            -
                    expect {  | 
| 602 | 
            +
                    expect { interpreter.evaluate(program) }.not_to raise_error
         | 
| 607 603 | 
             
                    expect(sample_cfg[:ostream].string).to eq('Egotist instance')
         | 
| 608 604 | 
             
                  end
         | 
| 609 605 |  | 
| 610 | 
            -
                  it ' | 
| 606 | 
            +
                  it 'supports a closure nested in a method' do
         | 
| 611 607 | 
             
                    lox_snippet = <<-LOX_END
         | 
| 612 608 | 
             
                    class Foo {
         | 
| 613 609 | 
             
                      getClosure() {
         | 
| @@ -635,21 +631,21 @@ LOX_END | |
| 635 631 | 
             
                    # Environment
         | 
| 636 632 | 
             
                    #   defns
         | 
| 637 633 | 
             
                    #   +- ['closure'] => Backend::LoxFunction
         | 
| 638 | 
            -
                    result =  | 
| 639 | 
            -
                    expect(result).to  | 
| 634 | 
            +
                    result = interpreter.evaluate(lox_snippet)
         | 
| 635 | 
            +
                    expect(result).to be_a(BackEnd::LoxFunction)
         | 
| 640 636 | 
             
                    expect(result.name).to eq('closure')
         | 
| 641 637 | 
             
                    closure = result.closure
         | 
| 642 | 
            -
                    expect(closure).to  | 
| 638 | 
            +
                    expect(closure).to be_a(Loxxy::BackEnd::Environment)
         | 
| 643 639 | 
             
                    expect(closure.defns['closure'].value).to eq(result)
         | 
| 644 | 
            -
                    expect(closure.enclosing).to  | 
| 645 | 
            -
                    expect(closure.enclosing.defns['this'].value).to  | 
| 640 | 
            +
                    expect(closure.enclosing).to be_a(Loxxy::BackEnd::Environment)
         | 
| 641 | 
            +
                    expect(closure.enclosing.defns['this'].value).to be_a(Loxxy::BackEnd::LoxInstance)
         | 
| 646 642 | 
             
                    global_env = closure.enclosing.enclosing
         | 
| 647 | 
            -
                    expect(global_env).to  | 
| 648 | 
            -
                    expect(global_env.defns['clock'].value).to  | 
| 649 | 
            -
                    expect(global_env.defns['Foo'].value).to  | 
| 643 | 
            +
                    expect(global_env).to be_a(Loxxy::BackEnd::Environment)
         | 
| 644 | 
            +
                    expect(global_env.defns['clock'].value).to be_a(BackEnd::Engine::NativeFunction)
         | 
| 645 | 
            +
                    expect(global_env.defns['Foo'].value).to be_a(BackEnd::LoxClass)
         | 
| 650 646 | 
             
                  end
         | 
| 651 647 |  | 
| 652 | 
            -
                  it ' | 
| 648 | 
            +
                  it 'supports custom initializer' do
         | 
| 653 649 | 
             
                    lox_snippet = <<-LOX_END
         | 
| 654 650 | 
             
                      // From section 3.9.5
         | 
| 655 651 | 
             
                      class Breakfast {
         | 
| @@ -668,12 +664,12 @@ LOX_END | |
| 668 664 | 
             
                      baconAndToast.serve("Dear Reader");
         | 
| 669 665 | 
             
                      // Output: "Enjoy your bacon and toast, Dear Reader."
         | 
| 670 666 | 
             
                    LOX_END
         | 
| 671 | 
            -
                    expect {  | 
| 667 | 
            +
                    expect { interpreter.evaluate(lox_snippet) }.not_to raise_error
         | 
| 672 668 | 
             
                    predicted = 'Enjoy your bacon and toast, Dear Reader.'
         | 
| 673 669 | 
             
                    expect(sample_cfg[:ostream].string).to eq(predicted)
         | 
| 674 670 | 
             
                  end
         | 
| 675 671 |  | 
| 676 | 
            -
                  it ' | 
| 672 | 
            +
                  it 'supports class inheritance and super keyword' do
         | 
| 677 673 | 
             
                    lox_snippet = <<-LOX_END
         | 
| 678 674 | 
             
                      class A {
         | 
| 679 675 | 
             
                        method() {
         | 
| @@ -695,7 +691,7 @@ LOX_END | |
| 695 691 |  | 
| 696 692 | 
             
                      C().test();
         | 
| 697 693 | 
             
                    LOX_END
         | 
| 698 | 
            -
                    expect {  | 
| 694 | 
            +
                    expect { interpreter.evaluate(lox_snippet) }.not_to raise_error
         | 
| 699 695 | 
             
                    expect(sample_cfg[:ostream].string).to eq('A method')
         | 
| 700 696 | 
             
                  end
         | 
| 701 697 | 
             
                end # context
         |