Browse Source

Add hashes

master
Dylan Baker 5 years ago
parent
commit
673f904132
9 changed files with 164 additions and 1 deletions
  1. 3
    0
      lib/ahem/ast.rb
  2. 15
    0
      lib/ahem/ast/atom.rb
  3. 21
    0
      lib/ahem/ast/hash.rb
  4. 37
    0
      lib/ahem/ast/index.rb
  5. 8
    1
      lib/ahem/lexer.rb
  6. 33
    0
      lib/ahem/parser.rb
  7. 2
    0
      lib/ahem/token_kinds.rb
  8. 13
    0
      spec/lexer_spec.rb
  9. 32
    0
      spec/parser_spec.rb

+ 3
- 0
lib/ahem/ast.rb View File

@@ -1,4 +1,5 @@
1 1
 module AST
2
+  require 'ahem/ast/atom'
2 3
   require 'ahem/ast/array'
3 4
   require 'ahem/ast/binary'
4 5
   require 'ahem/ast/block'
@@ -8,7 +9,9 @@ module AST
8 9
   require 'ahem/ast/conditional'
9 10
   require 'ahem/ast/function_call'
10 11
   require 'ahem/ast/function_definition'
12
+  require 'ahem/ast/hash'
11 13
   require 'ahem/ast/identifier'
14
+  require 'ahem/ast/index'
12 15
   require 'ahem/ast/member'
13 16
   require 'ahem/ast/null'
14 17
   require 'ahem/ast/number'

+ 15
- 0
lib/ahem/ast/atom.rb View File

@@ -0,0 +1,15 @@
1
+class AST::Atom
2
+  attr_reader :value
3
+
4
+  def initialize(value)
5
+    @value = value
6
+  end
7
+
8
+  def ==(other)
9
+    other.is_a?(AST::Atom) && other.value == @value
10
+  end
11
+
12
+  def execute(env)
13
+    @value
14
+  end
15
+end

+ 21
- 0
lib/ahem/ast/hash.rb View File

@@ -0,0 +1,21 @@
1
+class AST::Hash
2
+  attr_reader :data
3
+
4
+  def initialize(data)
5
+    @data = data
6
+  end
7
+
8
+  def ==(other)
9
+    other.is_a?(AST::Hash) && other.data == @data
10
+  end
11
+
12
+  def execute(env)
13
+    result = Hash.new
14
+
15
+    @data.each do |k, v|
16
+      result[k] = v.execute(env)
17
+    end
18
+
19
+    result
20
+  end
21
+end

+ 37
- 0
lib/ahem/ast/index.rb View File

@@ -0,0 +1,37 @@
1
+class AST::Index
2
+  attr_reader :object, :key
3
+
4
+  def initialize(object, key)
5
+    @object = object
6
+    @key = key
7
+  end
8
+
9
+  def ==(other)
10
+    other.is_a?(AST::Index) && other.object == @object && other.key == @key
11
+  end
12
+
13
+  def execute(env)
14
+    object = @object.execute(env)
15
+    key = @key.execute(env)
16
+
17
+    if object.is_a?(Array)
18
+      if key.is_a?(Float)
19
+        if key.to_i == key && object.size > key
20
+          object[key]
21
+        elsif key.to_i != key
22
+          throw "Array index requires integer key"
23
+        elsif object.size <= key
24
+          throw "Array index out of bounds"
25
+        end
26
+      else
27
+        throw "Array index requires integer key"
28
+      end
29
+    elsif object.is_a?(Hash)
30
+      if object.has_key?(key)
31
+        object[key]
32
+      else
33
+        throw "Key #{key} does not exist on object"
34
+      end
35
+    end
36
+  end
37
+end

+ 8
- 1
lib/ahem/lexer.rb View File

@@ -36,6 +36,10 @@ class Lexer
36 36
       string = source.match(/^"([^"]*)"/)[1]
37 37
       @position += (string.size + 2)
38 38
       Token.new(TokenKinds::STRING, string)
39
+    elsif source.match(/^\:([a-z][a-zA-Z0-9_]*)/)
40
+      atom = source.match(/^\:([a-z][a-zA-Z0-9_]*)/)[1]
41
+      @position += atom.size + 1
42
+      Token.new(TokenKinds::ATOM, atom.to_sym)
39 43
     elsif source.match(/^\+/)
40 44
       @position += 1
41 45
       Token.new(TokenKinds::OPERATOR, :+)
@@ -75,9 +79,12 @@ class Lexer
75 79
     elsif source.match(/^\./)
76 80
       @position += 1
77 81
       Token.new(TokenKinds::DOT)
82
+    elsif source.match(/^=>/)
83
+      @position += 2
84
+      Token.new(TokenKinds::ROCKET)
78 85
     elsif source.match(/^==/)
79 86
       @position += 2
80
-      Token.new(TokenKinds::OPERATOR, :==)      
87
+      Token.new(TokenKinds::OPERATOR, :==)
81 88
     elsif source.match(/^\<=/)
82 89
       @position += 2
83 90
       Token.new(TokenKinds::OPERATOR, :<=)

+ 33
- 0
lib/ahem/parser.rb View File

@@ -217,10 +217,15 @@ class Parser
217 217
     when TokenKinds::STRING
218 218
       advance
219 219
       AST::String.new(token.value)
220
+    when TokenKinds::ATOM
221
+      advance
222
+      AST::Atom.new(token.value)
220 223
     when TokenKinds::IDENTIFIER
221 224
       identifier
222 225
     when TokenKinds::LBRACKET
223 226
       array
227
+    when TokenKinds::LBRACE
228
+      hash
224 229
     else
225 230
       throw "Unexpected token #{token.type}"
226 231
     end
@@ -228,6 +233,11 @@ class Parser
228 233
     if @current_token.type == TokenKinds::LPAREN
229 234
       args = arguments
230 235
       AST::FunctionCall.new(expr, args)
236
+    elsif @current_token.type == TokenKinds::LBRACKET
237
+      eat(TokenKinds::LBRACKET)
238
+      key = expression
239
+      eat(TokenKinds::RBRACKET)
240
+      AST::Index.new(expr, key)
231 241
     else
232 242
       expr
233 243
     end
@@ -257,6 +267,29 @@ class Parser
257 267
     AST::Array.new(elements)
258 268
   end
259 269
 
270
+  def hash
271
+    h = Hash.new
272
+    eat(TokenKinds::LBRACE)
273
+
274
+    until @current_token.type == TokenKinds::RBRACE
275
+      key = eat(TokenKinds::ATOM).value
276
+      eat(TokenKinds::ROCKET)
277
+      value = expression
278
+
279
+      h[key] = value
280
+
281
+      if @current_token.type == TokenKinds::COMMA
282
+        eat(TokenKinds::COMMA)
283
+      else
284
+        break
285
+      end
286
+    end
287
+
288
+    eat(TokenKinds::RBRACE)
289
+
290
+    AST::Hash.new(h)
291
+  end
292
+
260 293
   def arguments
261 294
     args = Array.new
262 295
     eat(TokenKinds::LPAREN)

+ 2
- 0
lib/ahem/token_kinds.rb View File

@@ -1,4 +1,5 @@
1 1
 module TokenKinds
2
+  ATOM = :atom
2 3
   BOOLEAN = :boolean
3 4
   CLASS = :class
4 5
   CLASS_NAME = :class_name
@@ -22,6 +23,7 @@ module TokenKinds
22 23
   PUBLIC = :public
23 24
   RBRACE = :rbrace
24 25
   RBRACKET = :rbracket
26
+  ROCKET = :rocket
25 27
   RPAREN = :rparen
26 28
   SEMICOLON = :semicolon
27 29
   STRING = :string

+ 13
- 0
spec/lexer_spec.rb View File

@@ -123,4 +123,17 @@ RSpec.describe Lexer do
123 123
       ]
124 124
     )
125 125
   end
126
+
127
+  it 'lexes hash' do
128
+    expect(Lexer.new('{ :a => 1 }').scan_all).to eq(
129
+      [
130
+        Token.new(TokenKinds::LBRACE),
131
+        Token.new(TokenKinds::ATOM, :a),
132
+        Token.new(TokenKinds::ROCKET),
133
+        Token.new(TokenKinds::NUMBER, 1.0),
134
+        Token.new(TokenKinds::RBRACE),
135
+        Token.new(TokenKinds::EOF)
136
+      ]
137
+    )
138
+  end
126 139
 end

+ 32
- 0
spec/parser_spec.rb View File

@@ -323,4 +323,36 @@ RSpec.describe Parser do
323 323
       ]
324 324
     )
325 325
   end
326
+
327
+  it 'parses hashes' do
328
+    expect(parse('{ :a => 1, :b => 2 };')).to eq(
329
+      [
330
+        AST::Hash.new(
331
+          {
332
+            :a => AST::Number.new(1.0),
333
+            :b => AST::Number.new(2.0),
334
+          }
335
+        )
336
+      ]
337
+    )
338
+  end
339
+
340
+  it 'parses bracket indexing' do
341
+    expect(parse('array[0];')).to eq(
342
+      [
343
+        AST::Index.new(
344
+          AST::Identifier.new('array'),
345
+          AST::Number.new(0),
346
+        )
347
+      ]
348
+    )
349
+  end
350
+
351
+  it 'parses atoms' do
352
+    expect(parse(':atom;')).to eq(
353
+      [
354
+        AST::Atom.new(:atom)
355
+      ]
356
+    )
357
+  end
326 358
 end

Loading…
Cancel
Save