Browse Source

Interpreter

master
Dylan Baker 4 years ago
parent
commit
7553f1eee9

+ 3
- 0
lib/ahem.rb View File

@@ -1,5 +1,8 @@
1 1
 require 'ahem/ast'
2 2
 require 'ahem/cli'
3
+require 'ahem/core'
4
+require 'ahem/environment'
5
+require 'ahem/interpreter'
3 6
 require 'ahem/lexer'
4 7
 require 'ahem/parser'
5 8
 require 'ahem/token'

+ 4
- 0
lib/ahem/ast/array.rb View File

@@ -8,4 +8,8 @@ class AST::Array
8 8
   def ==(other)
9 9
     other.is_a?(AST::Array) && other.elements == @elements
10 10
   end
11
+
12
+  def execute(env)
13
+    @elements.map { |el| el.execute(env) }
14
+  end
11 15
 end

+ 27
- 0
lib/ahem/ast/binary.rb View File

@@ -11,4 +11,31 @@ class AST::Binary
11 11
     other.operation == @operation && other.left == @left &&
12 12
       other.right == @right
13 13
   end
14
+
15
+  def execute(env)
16
+    case @operation
17
+    when AST::Operators::ADD
18
+      @left.execute(env) + @right.execute(env)
19
+    when AST::Operators::SUBTRACT
20
+      @left.execute(env) - @right.execute(env)
21
+    when AST::Operators::MULTIPLY
22
+      @left.execute(env) * @right.execute(env)
23
+    when AST::Operators::DIVIDE
24
+      @left.execute(env) / @right.execute(env)
25
+    when AST::Operators::LESS_THAN
26
+      @left.execute(env) < @right.execute(env)
27
+    when AST::Operators::LESS_THAN_OR_EQUAL
28
+      @left.execute(env) <= @right.execute(env)
29
+    when AST::Operators::GREATER_THAN
30
+      @left.execute(env) > @right.execute(env)
31
+    when AST::Operators::GREATER_THAN_OR_EQUAL
32
+      @left.execute(env) >= @right.execute(env)
33
+    when AST::Operators::DOUBLE_EQUALS
34
+      @left.execute(env) == @right.execute(env)
35
+    when AST::Operators::OR
36
+      @left.execute(env) || @right.execute(env)
37
+    when AST::Operators::AND
38
+      @left.execute(env) && @right.execute(env)
39
+    end
40
+  end
14 41
 end

+ 4
- 0
lib/ahem/ast/boolean.rb View File

@@ -8,4 +8,8 @@ class AST::Boolean
8 8
   def ==(other)
9 9
     other.is_a?(AST::Boolean) && other.value == @value
10 10
   end
11
+
12
+  def execute(env)
13
+    @value
14
+  end
11 15
 end

+ 13
- 0
lib/ahem/ast/conditional.rb View File

@@ -8,4 +8,17 @@ class AST::Conditional
8 8
   def ==(other)
9 9
     other.is_a?(AST::Conditional) && other.branches == @branches
10 10
   end
11
+
12
+  def execute(env)
13
+    @branches.each do |branch|
14
+      if branch.condition.execute(env)
15
+        value = nil
16
+        branch.block.statements.each do |statement|
17
+          value = statement.execute(env)
18
+        end
19
+
20
+        return value
21
+      end
22
+    end
23
+  end
11 24
 end

+ 25
- 0
lib/ahem/ast/function_call.rb View File

@@ -10,4 +10,29 @@ class AST::FunctionCall
10 10
     other.is_a?(AST::FunctionCall) && other.function == @function &&
11 11
       other.arguments == @arguments
12 12
   end
13
+
14
+  def execute(env)
15
+    function = env.get(@function.name)
16
+
17
+    if function.is_a?(AST::FunctionDefinition)
18
+      if @arguments.size != function.parameters.size
19
+        throw "Arity mismatch in call to function #{function.name}"
20
+      end
21
+
22
+      function.parameters.each_with_index do |param, i|
23
+        env.set(param.name, @arguments[i].execute(env))
24
+      end
25
+
26
+      value = nil
27
+
28
+      function.body.statements.each do |statement|
29
+        value = statement.execute(env)
30
+      end
31
+
32
+      value
33
+    elsif function.is_a?(Proc)
34
+      arguments = @arguments.map { |arg| arg.execute(env) }
35
+      function.call(*arguments)
36
+    end
37
+  end
13 38
 end

+ 4
- 0
lib/ahem/ast/function_definition.rb View File

@@ -12,4 +12,8 @@ class AST::FunctionDefinition
12 12
       other.parameters == @parameters &&
13 13
       other.body == @body
14 14
   end
15
+
16
+  def execute(env)
17
+    env.set(@name.name, self)
18
+  end
15 19
 end

+ 10
- 0
lib/ahem/ast/identifier.rb View File

@@ -8,4 +8,14 @@ class AST::Identifier
8 8
   def ==(other)
9 9
     other.is_a?(AST::Identifier) && other.name == @name
10 10
   end
11
+
12
+  def execute(env)
13
+    value = env.get(@name)
14
+
15
+    if value.respond_to?(:execute)
16
+      value.execute(env)
17
+    else
18
+      value
19
+    end
20
+  end
11 21
 end

+ 4
- 0
lib/ahem/ast/null.rb View File

@@ -2,4 +2,8 @@ class AST::Null
2 2
   def ==(other)
3 3
     other.is_a?(AST::Null)
4 4
   end
5
+
6
+  def execute(env)
7
+    nil
8
+  end
5 9
 end

+ 4
- 0
lib/ahem/ast/number.rb View File

@@ -8,4 +8,8 @@ class AST::Number
8 8
   def ==(other)
9 9
     other.is_a?(AST::Number) && other.value == @value
10 10
   end
11
+
12
+  def execute(env)
13
+    @value
14
+  end
11 15
 end

+ 1
- 0
lib/ahem/ast/operators.rb View File

@@ -7,6 +7,7 @@ module AST::Operators
7 7
   GREATER_THAN = :>
8 8
   LESS_THAN_OR_EQUAL = :<=
9 9
   GREATER_THAN_OR_EQUAL = :>=
10
+  DOUBLE_EQUALS = :==
10 11
   OR = :or
11 12
   AND = :and
12 13
 end

+ 4
- 0
lib/ahem/ast/string.rb View File

@@ -8,4 +8,8 @@ class AST::String
8 8
   def ==(other)
9 9
     other.is_a?(AST::String) && other.value == @value
10 10
   end
11
+
12
+  def execute(env)
13
+    @value
14
+  end
11 15
 end

+ 4
- 0
lib/ahem/ast/variable_declaration.rb View File

@@ -10,4 +10,8 @@ class AST::VariableDeclaration
10 10
     other.is_a?(AST::VariableDeclaration) && other.name == @name &&
11 11
       other.value == @value
12 12
   end
13
+
14
+  def execute(env)
15
+    env.set(@name.name, @value)
16
+  end
13 17
 end

+ 1
- 3
lib/ahem/cli.rb View File

@@ -1,9 +1,7 @@
1
-require 'pp'
2
-
3 1
 class CLI
4 2
   def self.run
5 3
     file = ARGV.first
6 4
     source = File.read(file).strip
7
-    Parser.new(Lexer.new(source)).parse.each { |node| pp node }
5
+    Interpreter.new(Parser.new(Lexer.new(source)).parse).interpret
8 6
   end
9 7
 end

+ 11
- 0
lib/ahem/core.rb View File

@@ -0,0 +1,11 @@
1
+CORE = {
2
+  :print => -> *args do
3
+    args.each do |arg|
4
+      if arg.is_a?(Array)
5
+        puts "[#{arg.join(', ')}]"
6
+      else
7
+        puts arg
8
+      end
9
+    end
10
+  end,
11
+}

+ 29
- 0
lib/ahem/environment.rb View File

@@ -0,0 +1,29 @@
1
+class Environment
2
+  attr_reader :data
3
+
4
+  def initialize(parent = nil)
5
+    @data = Hash.new
6
+
7
+    CORE.each do |k, v|
8
+      @data[k.to_s] = v
9
+    end
10
+
11
+    unless parent.nil?
12
+      parent.data.each do |k, v|
13
+        @data[k] = v
14
+      end
15
+    end
16
+  end
17
+
18
+  def get(name)
19
+    if @data.has_key?(name)
20
+      @data[name]
21
+    else
22
+      throw "Undefined variable #{name}"
23
+    end
24
+  end
25
+
26
+  def set(name, value)
27
+    @data[name] = value
28
+  end
29
+end

+ 12
- 0
lib/ahem/interpreter.rb View File

@@ -0,0 +1,12 @@
1
+class Interpreter
2
+  def initialize(tree, env = Environment.new)
3
+    @tree = tree
4
+    @env = env
5
+  end
6
+
7
+  def interpret
8
+    @tree.each do |node|
9
+      node.execute(@env)
10
+    end
11
+  end
12
+end

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

@@ -75,6 +75,9 @@ class Lexer
75 75
     elsif source.match(/^\./)
76 76
       @position += 1
77 77
       Token.new(TokenKinds::DOT)
78
+    elsif source.match(/^==/)
79
+      @position += 2
80
+      Token.new(TokenKinds::OPERATOR, :==)      
78 81
     elsif source.match(/^\<=/)
79 82
       @position += 2
80 83
       Token.new(TokenKinds::OPERATOR, :<=)

+ 9
- 8
lib/ahem/parser.rb View File

@@ -142,13 +142,7 @@ class Parser
142 142
   end
143 143
 
144 144
   def expression
145
-    expr = binary
146
-    if @current_token.type == TokenKinds::LPAREN
147
-      args = arguments
148
-      AST::FunctionCall.new(expr, args)
149
-    else
150
-      expr
151
-    end
145
+    binary
152 146
   end
153 147
 
154 148
   private
@@ -210,7 +204,7 @@ class Parser
210 204
 
211 205
   def primary
212 206
     token = @current_token
213
-    case token.type
207
+    expr = case token.type
214 208
     when TokenKinds::NULL
215 209
       advance
216 210
       AST::Null.new
@@ -230,6 +224,13 @@ class Parser
230 224
     else
231 225
       throw "Unexpected token #{token.type}"
232 226
     end
227
+
228
+    if @current_token.type == TokenKinds::LPAREN
229
+      args = arguments
230
+      AST::FunctionCall.new(expr, args)
231
+    else
232
+      expr
233
+    end
233 234
   end
234 235
 
235 236
   def identifier

+ 2
- 1
spec/lexer_spec.rb View File

@@ -32,7 +32,7 @@ RSpec.describe Lexer do
32 32
   end
33 33
 
34 34
   it 'lexes operators' do
35
-    expect(Lexer.new('+-*/<<=>>= and or').scan_all).to eq(
35
+    expect(Lexer.new('+-*/<<=>>=== and or').scan_all).to eq(
36 36
       [
37 37
         Token.new(TokenKinds::OPERATOR, :+),
38 38
         Token.new(TokenKinds::OPERATOR, :-),
@@ -42,6 +42,7 @@ RSpec.describe Lexer do
42 42
         Token.new(TokenKinds::OPERATOR, :<=),
43 43
         Token.new(TokenKinds::OPERATOR, :>),
44 44
         Token.new(TokenKinds::OPERATOR, :>=),
45
+        Token.new(TokenKinds::OPERATOR, :==),
45 46
         Token.new(TokenKinds::OPERATOR, :and),
46 47
         Token.new(TokenKinds::OPERATOR, :or),
47 48
         Token.new(TokenKinds::EOF)

+ 17
- 0
spec/parser_spec.rb View File

@@ -306,4 +306,21 @@ RSpec.describe Parser do
306 306
       ]
307 307
     )
308 308
   end
309
+
310
+  it 'gives function calls higher precedence than binary operations' do
311
+    expect(parse('x + func(y);')).to eq(
312
+      [
313
+          AST::Binary.new(
314
+            AST::Operators::ADD,
315
+            AST::Identifier.new('x'),
316
+            AST::FunctionCall.new(
317
+              AST::Identifier.new('func'),
318
+              [
319
+                AST::Identifier.new('y'),
320
+              ]
321
+            )
322
+          )
323
+      ]
324
+    )
325
+  end
309 326
 end

Loading…
Cancel
Save