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
 module AST
1
 module AST
2
+  require 'ahem/ast/assignment'
2
   require 'ahem/ast/atom'
3
   require 'ahem/ast/atom'
3
   require 'ahem/ast/array'
4
   require 'ahem/ast/array'
4
   require 'ahem/ast/binary'
5
   require 'ahem/ast/binary'

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

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
   end
12
   end
13
 
13
 
14
   def execute(env)
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
   end
20
   end
17
 end
21
 end

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

162
   end
162
   end
163
 
163
 
164
   def expression
164
   def expression
165
-    binary
165
+    assignment
166
   end
166
   end
167
 
167
 
168
   private
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
   def binary
183
   def binary
171
     left = comparison
184
     left = comparison
172
 
185
 

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

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

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
       ]
452
       ]
453
     )
453
     )
454
 	end
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
 end
466
 end

Loading…
Cancel
Save