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
| @@ -23,26 +23,27 @@ module Loxxy | |
| 23 23 | 
             
                  end
         | 
| 24 24 |  | 
| 25 25 | 
             
                  let(:sample_text) { 'print "Hello, world";' }
         | 
| 26 | 
            -
             | 
| 26 | 
            +
             | 
| 27 | 
            +
                  subject(:scanner) { described_class.new }
         | 
| 27 28 |  | 
| 28 29 | 
             
                  context 'Initialization:' do
         | 
| 29 30 | 
             
                    it 'could be initialized with a text to tokenize or...' do
         | 
| 30 | 
            -
                      expect {  | 
| 31 | 
            +
                      expect { described_class.new(sample_text) }.not_to raise_error
         | 
| 31 32 | 
             
                    end
         | 
| 32 33 |  | 
| 33 34 | 
             
                    it 'could be initialized without argument...' do
         | 
| 34 | 
            -
                      expect {  | 
| 35 | 
            +
                      expect { described_class.new }.not_to raise_error
         | 
| 35 36 | 
             
                    end
         | 
| 36 37 |  | 
| 37 | 
            -
                    it ' | 
| 38 | 
            -
                      expect( | 
| 38 | 
            +
                    it 'has its scanner initialized' do
         | 
| 39 | 
            +
                      expect(scanner.scanner).to be_a(StringScanner)
         | 
| 39 40 | 
             
                    end
         | 
| 40 41 | 
             
                  end # context
         | 
| 41 42 |  | 
| 42 43 | 
             
                  context 'Input tokenization:' do
         | 
| 43 | 
            -
                    it ' | 
| 44 | 
            +
                    it 'recognizes single special character token' do
         | 
| 44 45 | 
             
                      input = '(){},.-+;*/'
         | 
| 45 | 
            -
                       | 
| 46 | 
            +
                      scanner.start_with(input)
         | 
| 46 47 | 
             
                      expectations = [
         | 
| 47 48 | 
             
                        # [token lexeme]
         | 
| 48 49 | 
             
                        %w[LEFT_PAREN (],
         | 
| @@ -57,12 +58,12 @@ module Loxxy | |
| 57 58 | 
             
                        %w[STAR *],
         | 
| 58 59 | 
             
                        %w[SLASH /]
         | 
| 59 60 | 
             
                      ]
         | 
| 60 | 
            -
                      match_expectations( | 
| 61 | 
            +
                      match_expectations(scanner, expectations)
         | 
| 61 62 | 
             
                    end
         | 
| 62 63 |  | 
| 63 | 
            -
                    it ' | 
| 64 | 
            +
                    it 'recognizes one or two special character tokens' do
         | 
| 64 65 | 
             
                      input = '! != = == > >= < <='
         | 
| 65 | 
            -
                       | 
| 66 | 
            +
                      scanner.start_with(input)
         | 
| 66 67 | 
             
                      expectations = [
         | 
| 67 68 | 
             
                        # [token lexeme]
         | 
| 68 69 | 
             
                        %w[BANG !],
         | 
| @@ -74,15 +75,15 @@ module Loxxy | |
| 74 75 | 
             
                        %w[LESS <],
         | 
| 75 76 | 
             
                        %w[LESS_EQUAL <=]
         | 
| 76 77 | 
             
                      ]
         | 
| 77 | 
            -
                      match_expectations( | 
| 78 | 
            +
                      match_expectations(scanner, expectations)
         | 
| 78 79 | 
             
                    end
         | 
| 79 80 |  | 
| 80 | 
            -
                    it ' | 
| 81 | 
            +
                    it 'recognizes non-datatype keywords' do
         | 
| 81 82 | 
             
                      keywords = <<-LOX_END
         | 
| 82 83 | 
             
                        and class else fun for if or
         | 
| 83 84 | 
             
                        print return super this var while
         | 
| 84 85 | 
             
            LOX_END
         | 
| 85 | 
            -
                       | 
| 86 | 
            +
                      scanner.start_with(keywords)
         | 
| 86 87 | 
             
                      expectations = [
         | 
| 87 88 | 
             
                        # [token lexeme]
         | 
| 88 89 | 
             
                        %w[AND and],
         | 
| @@ -99,30 +100,30 @@ LOX_END | |
| 99 100 | 
             
                        %w[VAR var],
         | 
| 100 101 | 
             
                        %w[WHILE while]
         | 
| 101 102 | 
             
                      ]
         | 
| 102 | 
            -
                      match_expectations( | 
| 103 | 
            +
                      match_expectations(scanner, expectations)
         | 
| 103 104 | 
             
                    end
         | 
| 104 105 |  | 
| 105 | 
            -
                    it ' | 
| 106 | 
            -
                       | 
| 107 | 
            -
                      token_false =  | 
| 108 | 
            -
                      expect(token_false).to  | 
| 106 | 
            +
                    it 'recognizes a false boolean token' do
         | 
| 107 | 
            +
                      scanner.start_with('false')
         | 
| 108 | 
            +
                      token_false = scanner.tokens[0]
         | 
| 109 | 
            +
                      expect(token_false).to be_a(Rley::Lexical::Literal)
         | 
| 109 110 | 
             
                      expect(token_false.terminal).to eq('FALSE')
         | 
| 110 111 | 
             
                      expect(token_false.lexeme).to eq('false')
         | 
| 111 | 
            -
                      expect(token_false.value).to  | 
| 112 | 
            +
                      expect(token_false.value).to be_a(Datatype::False)
         | 
| 112 113 | 
             
                      expect(token_false.value.value).to be_falsy
         | 
| 113 114 | 
             
                    end
         | 
| 114 115 |  | 
| 115 | 
            -
                    it ' | 
| 116 | 
            -
                       | 
| 117 | 
            -
                      token_true =  | 
| 118 | 
            -
                      expect(token_true).to  | 
| 116 | 
            +
                    it 'recognizes a true boolean token' do
         | 
| 117 | 
            +
                      scanner.start_with('true')
         | 
| 118 | 
            +
                      token_true = scanner.tokens[0]
         | 
| 119 | 
            +
                      expect(token_true).to be_a(Rley::Lexical::Literal)
         | 
| 119 120 | 
             
                      expect(token_true.terminal).to eq('TRUE')
         | 
| 120 121 | 
             
                      expect(token_true.lexeme).to eq('true')
         | 
| 121 | 
            -
                      expect(token_true.value).to  | 
| 122 | 
            +
                      expect(token_true.value).to be_a(Datatype::True)
         | 
| 122 123 | 
             
                      expect(token_true.value.value).to be_truthy
         | 
| 123 124 | 
             
                    end
         | 
| 124 125 |  | 
| 125 | 
            -
                    it ' | 
| 126 | 
            +
                    it 'recognizes number values' do
         | 
| 126 127 | 
             
                      input = <<-LOX_END
         | 
| 127 128 | 
             
                        123     987654
         | 
| 128 129 | 
             
                        0       123.456
         | 
| @@ -135,18 +136,18 @@ LOX_END | |
| 135 136 | 
             
                        ['123.456', 123.456]
         | 
| 136 137 | 
             
                      ]
         | 
| 137 138 |  | 
| 138 | 
            -
                       | 
| 139 | 
            -
                       | 
| 140 | 
            -
                        expect(tok).to  | 
| 139 | 
            +
                      scanner.start_with(input)
         | 
| 140 | 
            +
                      scanner.tokens[0..-2].each_with_index do |tok, i|
         | 
| 141 | 
            +
                        expect(tok).to be_a(Rley::Lexical::Literal)
         | 
| 141 142 | 
             
                        expect(tok.terminal).to eq('NUMBER')
         | 
| 142 143 | 
             
                        (lexeme, val) = expectations[i]
         | 
| 143 144 | 
             
                        expect(tok.lexeme).to eq(lexeme)
         | 
| 144 | 
            -
                        expect(tok.value).to  | 
| 145 | 
            +
                        expect(tok.value).to be_a(Datatype::Number)
         | 
| 145 146 | 
             
                        expect(tok.value.value).to eq(val)
         | 
| 146 147 | 
             
                      end
         | 
| 147 148 | 
             
                    end
         | 
| 148 149 |  | 
| 149 | 
            -
                    it ' | 
| 150 | 
            +
                    it 'recognizes negative number values' do
         | 
| 150 151 | 
             
                      input = <<-LOX_END
         | 
| 151 152 | 
             
                        -0
         | 
| 152 153 | 
             
                        -0.001
         | 
| @@ -157,8 +158,8 @@ LOX_END | |
| 157 158 | 
             
                        ['-', '0.001']
         | 
| 158 159 | 
             
                      ].flatten
         | 
| 159 160 |  | 
| 160 | 
            -
                       | 
| 161 | 
            -
                      tokens =  | 
| 161 | 
            +
                      scanner.start_with(input)
         | 
| 162 | 
            +
                      tokens = scanner.tokens
         | 
| 162 163 | 
             
                      tokens.pop
         | 
| 163 164 | 
             
                      i = 0
         | 
| 164 165 | 
             
                      tokens.each_slice(2) do |(sign, lit)|
         | 
| @@ -170,24 +171,24 @@ LOX_END | |
| 170 171 | 
             
                      end
         | 
| 171 172 | 
             
                    end
         | 
| 172 173 |  | 
| 173 | 
            -
                    it ' | 
| 174 | 
            +
                    it 'recognizes leading and trailing dots as distinct tokens' do
         | 
| 174 175 | 
             
                      input = '.456 123.'
         | 
| 175 176 |  | 
| 176 | 
            -
                       | 
| 177 | 
            -
                      tokens =  | 
| 178 | 
            -
                      expect(tokens[0]).to  | 
| 177 | 
            +
                      scanner.start_with(input)
         | 
| 178 | 
            +
                      tokens = scanner.tokens[0..-2]
         | 
| 179 | 
            +
                      expect(tokens[0]).to be_a(Rley::Lexical::Token)
         | 
| 179 180 | 
             
                      expect(tokens[0].terminal).to eq('DOT')
         | 
| 180 | 
            -
                      expect(tokens[1]).to  | 
| 181 | 
            +
                      expect(tokens[1]).to be_a(Rley::Lexical::Literal)
         | 
| 181 182 | 
             
                      expect(tokens[1].terminal).to eq('NUMBER')
         | 
| 182 183 | 
             
                      expect(tokens[1].value.value).to eq(456)
         | 
| 183 | 
            -
                      expect(tokens[2]).to  | 
| 184 | 
            +
                      expect(tokens[2]).to be_a(Rley::Lexical::Literal)
         | 
| 184 185 | 
             
                      expect(tokens[2].terminal).to eq('NUMBER')
         | 
| 185 186 | 
             
                      expect(tokens[2].value.value).to eq(123)
         | 
| 186 | 
            -
                      expect(tokens[3]).to  | 
| 187 | 
            +
                      expect(tokens[3]).to be_a(Rley::Lexical::Token)
         | 
| 187 188 | 
             
                      expect(tokens[3].terminal).to eq('DOT')
         | 
| 188 189 | 
             
                    end
         | 
| 189 190 |  | 
| 190 | 
            -
                    it ' | 
| 191 | 
            +
                    it 'recognizes string values' do
         | 
| 191 192 | 
             
                      input = <<-LOX_END
         | 
| 192 193 | 
             
                      ""
         | 
| 193 194 | 
             
                      "string"
         | 
| @@ -200,85 +201,85 @@ LOX_END | |
| 200 201 | 
             
                        '123'
         | 
| 201 202 | 
             
                      ]
         | 
| 202 203 |  | 
| 203 | 
            -
                       | 
| 204 | 
            -
                       | 
| 205 | 
            -
                        expect(str).to  | 
| 204 | 
            +
                      scanner.start_with(input)
         | 
| 205 | 
            +
                      scanner.tokens[0..-2].each_with_index do |str, i|
         | 
| 206 | 
            +
                        expect(str).to be_a(Rley::Lexical::Literal)
         | 
| 206 207 | 
             
                        expect(str.terminal).to eq('STRING')
         | 
| 207 208 | 
             
                        val = expectations[i]
         | 
| 208 | 
            -
                        expect(str.value).to  | 
| 209 | 
            +
                        expect(str.value).to be_a(Datatype::LXString)
         | 
| 209 210 | 
             
                        expect(str.value.value).to eq(val)
         | 
| 210 211 | 
             
                      end
         | 
| 211 212 | 
             
                    end
         | 
| 212 213 |  | 
| 213 | 
            -
                    it ' | 
| 214 | 
            +
                    it 'recognizes escaped quotes' do
         | 
| 214 215 | 
             
                      embedded_quotes = %q{"she said: \"Hello\""}
         | 
| 215 | 
            -
                       | 
| 216 | 
            -
                      result =  | 
| 216 | 
            +
                      scanner.start_with(embedded_quotes)
         | 
| 217 | 
            +
                      result = scanner.tokens[0]
         | 
| 217 218 | 
             
                      expect(result.value).to eq('she said: "Hello"')
         | 
| 218 219 | 
             
                    end
         | 
| 219 220 |  | 
| 220 | 
            -
                    it ' | 
| 221 | 
            +
                    it 'recognizes escaped backslash' do
         | 
| 221 222 | 
             
                      embedded_backslash = '"backslash>\\\\"'
         | 
| 222 | 
            -
                       | 
| 223 | 
            -
                      result =  | 
| 223 | 
            +
                      scanner.start_with(embedded_backslash)
         | 
| 224 | 
            +
                      result = scanner.tokens[0]
         | 
| 224 225 | 
             
                      expect(result.value).to eq('backslash>\\')
         | 
| 225 226 | 
             
                    end
         | 
| 226 227 |  | 
| 227 228 | 
             
                    # rubocop: disable Style/StringConcatenation
         | 
| 228 | 
            -
                    it ' | 
| 229 | 
            +
                    it 'recognizes newline escape sequence' do
         | 
| 229 230 | 
             
                      embedded_newline = '"line1\\nline2"'
         | 
| 230 | 
            -
                       | 
| 231 | 
            -
                      result =  | 
| 231 | 
            +
                      scanner.start_with(embedded_newline)
         | 
| 232 | 
            +
                      result = scanner.tokens[0]
         | 
| 232 233 | 
             
                      expect(result.value).to eq('line1' + "\n" + 'line2')
         | 
| 233 234 | 
             
                    end
         | 
| 234 235 | 
             
                    # rubocop: enable Style/StringConcatenation
         | 
| 235 236 |  | 
| 236 | 
            -
                    it ' | 
| 237 | 
            -
                       | 
| 238 | 
            -
                      token_nil =  | 
| 239 | 
            -
                      expect(token_nil).to  | 
| 237 | 
            +
                    it 'recognizes a nil token' do
         | 
| 238 | 
            +
                      scanner.start_with('nil')
         | 
| 239 | 
            +
                      token_nil = scanner.tokens[0]
         | 
| 240 | 
            +
                      expect(token_nil).to be_a(Rley::Lexical::Literal)
         | 
| 240 241 | 
             
                      expect(token_nil.terminal).to eq('NIL')
         | 
| 241 242 | 
             
                      expect(token_nil.lexeme).to eq('nil')
         | 
| 242 | 
            -
                      expect(token_nil.value).to  | 
| 243 | 
            +
                      expect(token_nil.value).to be_a(Datatype::Nil)
         | 
| 243 244 | 
             
                    end
         | 
| 244 245 |  | 
| 245 | 
            -
                    it ' | 
| 246 | 
            -
                       | 
| 247 | 
            -
                      similar =  | 
| 246 | 
            +
                    it 'differentiates nil from variable spelled same' do
         | 
| 247 | 
            +
                      scanner.start_with('Nil')
         | 
| 248 | 
            +
                      similar = scanner.tokens[0]
         | 
| 248 249 | 
             
                      expect(similar.terminal).to eq('IDENTIFIER')
         | 
| 249 250 | 
             
                      expect(similar.lexeme).to eq('Nil')
         | 
| 250 251 | 
             
                    end
         | 
| 251 252 | 
             
                  end # context
         | 
| 252 253 |  | 
| 253 254 | 
             
                  context 'Handling comments:' do
         | 
| 254 | 
            -
                    it ' | 
| 255 | 
            -
                       | 
| 255 | 
            +
                    it 'copes with one line comment only' do
         | 
| 256 | 
            +
                      scanner.start_with('// comment')
         | 
| 256 257 |  | 
| 257 258 | 
             
                      # No token found, except eof marker
         | 
| 258 | 
            -
                      eof_token =  | 
| 259 | 
            +
                      eof_token = scanner.tokens[0]
         | 
| 259 260 | 
             
                      expect(eof_token.terminal).to eq('EOF')
         | 
| 260 261 | 
             
                    end
         | 
| 261 262 |  | 
| 262 263 | 
             
                    # rubocop: disable Lint/PercentStringArray
         | 
| 263 | 
            -
                    it ' | 
| 264 | 
            +
                    it 'skips end of line comments' do
         | 
| 264 265 | 
             
                      input = <<-LOX_END
         | 
| 265 266 | 
             
                        // first comment
         | 
| 266 267 | 
             
                        print "ok"; // second comment
         | 
| 267 268 | 
             
                        // third comment
         | 
| 268 269 | 
             
            LOX_END
         | 
| 269 | 
            -
                       | 
| 270 | 
            +
                      scanner.start_with(input)
         | 
| 270 271 | 
             
                      expectations = [
         | 
| 271 272 | 
             
                        # [token lexeme]
         | 
| 272 273 | 
             
                        %w[PRINT print],
         | 
| 273 274 | 
             
                        %w[STRING "ok"],
         | 
| 274 275 | 
             
                        %w[SEMICOLON ;]
         | 
| 275 276 | 
             
                      ]
         | 
| 276 | 
            -
                      match_expectations( | 
| 277 | 
            +
                      match_expectations(scanner, expectations)
         | 
| 277 278 | 
             
                    end
         | 
| 278 279 | 
             
                    # rubocop: enable Lint/PercentStringArray
         | 
| 279 280 |  | 
| 280 | 
            -
                    it ' | 
| 281 | 
            -
                       | 
| 281 | 
            +
                    it 'copes with single slash (divide) expression' do
         | 
| 282 | 
            +
                      scanner.start_with('8 / 2')
         | 
| 282 283 |  | 
| 283 284 | 
             
                      expectations = [
         | 
| 284 285 | 
             
                        # [token lexeme]
         | 
| @@ -286,21 +287,21 @@ LOX_END | |
| 286 287 | 
             
                        %w[SLASH /],
         | 
| 287 288 | 
             
                        %w[NUMBER 2]
         | 
| 288 289 | 
             
                      ]
         | 
| 289 | 
            -
                      match_expectations( | 
| 290 | 
            +
                      match_expectations(scanner, expectations)
         | 
| 290 291 | 
             
                    end
         | 
| 291 292 |  | 
| 292 | 
            -
                    it ' | 
| 293 | 
            -
                       | 
| 293 | 
            +
                    it 'complains if it finds an unterminated string' do
         | 
| 294 | 
            +
                      scanner.start_with('var a = "Unfinished;')
         | 
| 294 295 | 
             
                      err = Loxxy::ScanError
         | 
| 295 296 | 
             
                      err_msg = 'Error: [line 1:9]: Unterminated string.'
         | 
| 296 | 
            -
                      expect {  | 
| 297 | 
            +
                      expect { scanner.tokens }.to raise_error(err, err_msg)
         | 
| 297 298 | 
             
                    end
         | 
| 298 299 |  | 
| 299 | 
            -
                    it ' | 
| 300 | 
            -
                       | 
| 300 | 
            +
                    it 'complains if it finds an unexpected character' do
         | 
| 301 | 
            +
                      scanner.start_with('var a = ?1?;')
         | 
| 301 302 | 
             
                      err = Loxxy::ScanError
         | 
| 302 303 | 
             
                      err_msg = 'Error: [line 1:9]: Unexpected character.'
         | 
| 303 | 
            -
                      expect {  | 
| 304 | 
            +
                      expect { scanner.tokens }.to raise_error(err, err_msg)
         | 
| 304 305 | 
             
                    end
         | 
| 305 306 | 
             
                  end # context
         | 
| 306 307 | 
             
                end # describe
         |