Browse Source

Add assignments, make declarations throw if var already exists

master
Dylan Baker 5 years ago
parent
commit
30e3ba32d9

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

@@ -1,4 +1,5 @@
1 1
 module AST
2
+  require 'ahem/ast/assignment'
2 3
   require 'ahem/ast/atom'
3 4
   require 'ahem/ast/array'
4 5
   require 'ahem/ast/binary'

+ 20
- 0
lib/ahem/ast/assignment.rb View File

@@ -0,0 +1,20 @@
1
+class AST::Assignment
2
+  attr_reader :name, :value
3
+
4
+  def initialize(name, value)
5
+    @name = name
6
+    @value = value
7
+  end
8
+
9
+  def ==(other)
10
+    other.is_a?(AST::Assignment) && other.name == @name && other.value == @value
11
+  end
12
+
13
+  def execute(env)
14
+    if env.get(@name.name)
15
+      env.set(@name.name, @value)
16
+    else
17
+      raise "Invalid assignment to undeclared variable #{@name.name}"
18
+    end
19
+  end
20
+end

+ 5
- 1
lib/ahem/ast/variable_declaration.rb View File

@@ -12,6 +12,10 @@ class AST::VariableDeclaration
12 12
   end
13 13
 
14 14
   def execute(env)
15
-    env.set(@name.name, @value)
15
+    if env.data[@name.name].nil?
16
+      env.set(@name.name, @value)
17
+    else
18
+      raise "Invalid declaration of previously declared variable #{@name.name}"
19
+    end
16 20
   end
17 21
 end

+ 14
- 1
lib/ahem/parser.rb View File

@@ -162,11 +162,24 @@ class Parser
162 162
   end
163 163
 
164 164
   def expression
165
-    binary
165
+    assignment
166 166
   end
167 167
 
168 168
   private
169 169
 
170
+  def assignment
171
+    expr = binary
172
+
173
+    if @current_token.type == TokenKinds::EQUALS
174
+      raise 'Invalid left hand side of assignment' unless expr.is_a?(AST::Identifier)
175
+      eat(TokenKinds::EQUALS)
176
+      value = expression
177
+      AST::Assignment.new(expr, value)
178
+    else
179
+      expr
180
+    end
181
+  end
182
+
170 183
   def binary
171 184
     left = comparison
172 185
 

+ 25
- 0
spec/ast/assignment_spec.rb View File

@@ -0,0 +1,25 @@
1
+RSpec.describe AST::Assignment do
2
+  it 'reassigns an existing variable' do
3
+    env = Environment.new
4
+    AST::VariableDeclaration.new(
5
+      AST::Identifier.new('x'),
6
+      AST::Number.new(5.0),
7
+    ).execute(env)
8
+    AST::Assignment.new(
9
+      AST::Identifier.new('x'),
10
+      AST::Number.new(6.0),
11
+    ).execute(env)
12
+    expect(env.get('x').execute(env)).to eq(6.0)
13
+  end
14
+
15
+  it 'raises for an undeclared variable' do
16
+    env = Environment.new
17
+    expect {
18
+      AST::Assignment.new(
19
+        AST::Identifier.new('x'),
20
+        AST::Number.new(6.0),
21
+      ).execute(env)
22
+    }.to raise_error("Undefined variable x")
23
+
24
+  end
25
+end

+ 24
- 0
spec/ast/variable_declaration_spec.rb View File

@@ -0,0 +1,24 @@
1
+RSpec.describe AST::VariableDeclaration do
2
+  it 'defines a variable' do
3
+    env = Environment.new
4
+    AST::VariableDeclaration.new(
5
+      AST::Identifier.new('x'),
6
+      AST::Number.new(5.0),
7
+    ).execute(env)
8
+    expect(env.get('x').execute(env)).to eq(5.0)
9
+  end
10
+
11
+  it 'raises if a variable is already defined' do
12
+    env = Environment.new
13
+    AST::VariableDeclaration.new(
14
+      AST::Identifier.new('x'),
15
+      AST::Number.new(5.0),
16
+    ).execute(env)
17
+    expect {
18
+      AST::VariableDeclaration.new(
19
+        AST::Identifier.new('x'),
20
+        AST::Number.new(6.0),
21
+      ).execute(env)
22
+    }.to raise_error('Invalid declaration of previously declared variable x')
23
+  end
24
+end

+ 11
- 0
spec/parser_spec.rb View File

@@ -452,4 +452,15 @@ RSpec.describe Parser do
452 452
       ]
453 453
     )
454 454
 	end
455
+
456
+  it 'parses assignments' do
457
+    expect(parse('x = 5;')).to eq(
458
+      [
459
+        AST::Assignment.new(
460
+          AST::Identifier.new('x'),
461
+          AST::Number.new(5.0),
462
+        )
463
+      ]
464
+    )
465
+  end
455 466
 end

Loading…
Cancel
Save