Browse Source

For loops

master
Dylan Baker 4 years ago
parent
commit
56e876a3b2

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

@@ -7,6 +7,7 @@ module AST
7 7
   require 'ahem/ast/branch'
8 8
   require 'ahem/ast/class_definition'
9 9
   require 'ahem/ast/conditional'
10
+  require 'ahem/ast/for_loop'
10 11
   require 'ahem/ast/function_call'
11 12
   require 'ahem/ast/function_definition'
12 13
   require 'ahem/ast/hash'

+ 22
- 0
lib/ahem/ast/for_loop.rb View File

@@ -0,0 +1,22 @@
1
+class AST::ForLoop
2
+  attr_reader :iterator, :iterable, :block
3
+
4
+  def initialize(iterator, iterable, block)
5
+    @iterator = iterator
6
+    @iterable = iterable
7
+    @block = block
8
+  end
9
+
10
+  def ==(other)
11
+    other.is_a?(AST::ForLoop) && other.iterator == @iterator &&
12
+      other.iterable == @iterable && other.block == @block
13
+  end
14
+
15
+  def execute(env)
16
+    @iterable.execute(env).each do |i|
17
+      _env = Environment.new
18
+      _env.set(@iterator.name, i)
19
+      @block.execute(_env)
20
+    end
21
+  end
22
+end

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

@@ -127,6 +127,12 @@ class Lexer
127 127
     elsif source.match(/^private/)
128 128
       @position += 7
129 129
       Token.new(TokenKinds::PRIVATE)
130
+    elsif source.match(/^for/)
131
+      @position += 3
132
+      Token.new(TokenKinds::FOR)
133
+    elsif source.match(/^in/)
134
+      @position += 2
135
+      Token.new(TokenKinds::IN)
130 136
     elsif source.match(/^[a-z][a-zA-Z0-9_]*/)
131 137
       identifier = source.match(/^[a-z][a-zA-Z0-9_]*/)[0]
132 138
       @position += identifier.size

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

@@ -21,6 +21,8 @@ class Parser
21 21
       function_definition
22 22
     elsif @current_token.type == TokenKinds::CLASS
23 23
       class_definition
24
+    elsif @current_token.type == TokenKinds::FOR
25
+      for_loop
24 26
     else
25 27
       expr = expression
26 28
       eat(TokenKinds::SEMICOLON)
@@ -112,6 +114,15 @@ class Parser
112 114
     AST::Conditional.new(branches)
113 115
   end
114 116
 
117
+  def for_loop
118
+    eat(TokenKinds::FOR)
119
+    iterator = identifier
120
+    eat(TokenKinds::IN)
121
+    iterable = expression
122
+    body = block
123
+    AST::ForLoop.new(iterator, iterable, body)
124
+  end
125
+
115 126
   def parameters
116 127
     params = Array.new
117 128
     eat(TokenKinds::LPAREN)

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

@@ -9,9 +9,11 @@ module TokenKinds
9 9
   ELSE = :else
10 10
   EOF = :eof
11 11
   EQUALS = :equals
12
+  FOR = :for
12 13
   FUNCTION = :function
13 14
   IDENTIFIER = :identifier
14 15
   IF = :if
16
+  IN = :in
15 17
   LBRACE = :lbrace
16 18
   LBRACKET = :lbracket
17 19
   LPAREN = :lparen

+ 22
- 0
spec/ast/for_loop_spec.rb View File

@@ -0,0 +1,22 @@
1
+RSpec.describe AST::ForLoop do
2
+  it 'evaluates' do
3
+    expect {
4
+      AST::ForLoop.new(
5
+        AST::Identifier.new('x'),
6
+        AST::Array.new(
7
+          [
8
+            AST::Number.new(1.0),
9
+            AST::Number.new(2.0),
10
+            AST::Number.new(3.0),
11
+          ]
12
+        ),
13
+        AST::FunctionCall.new(
14
+          AST::Identifier.new('print'),
15
+          [
16
+            AST::Identifier.new('x')
17
+          ]
18
+        )
19
+      ).execute(Environment.new)
20
+    }.to output("1.0\n2.0\n3.0\n").to_stdout
21
+  end
22
+end

+ 10
- 0
spec/lexer_spec.rb View File

@@ -137,4 +137,14 @@ RSpec.describe Lexer do
137 137
       ]
138 138
     )
139 139
   end
140
+
141
+  it 'lexes for' do
142
+    expect(Lexer.new('for in').scan_all).to eq(
143
+      [
144
+        Token.new(TokenKinds::FOR),
145
+        Token.new(TokenKinds::IN),
146
+        Token.new(TokenKinds::EOF),
147
+      ]
148
+    )
149
+  end
140 150
 end

+ 21
- 0
spec/parser_spec.rb View File

@@ -358,4 +358,25 @@ RSpec.describe Parser do
358 358
       ]
359 359
     )
360 360
   end
361
+
362
+  it 'parses for loops' do
363
+    expect(parse('for x in xs { print(x); }')).to eq(
364
+      [
365
+        AST::ForLoop.new(
366
+          AST::Identifier.new('x'),
367
+          AST::Identifier.new('xs'),
368
+          AST::Block.new(
369
+            [
370
+              AST::FunctionCall.new(
371
+                AST::Identifier.new('print'),
372
+                [
373
+                  AST::Identifier.new('x')
374
+                ]
375
+              )
376
+            ]
377
+          )
378
+        )
379
+      ]
380
+    )
381
+  end
361 382
 end

Loading…
Cancel
Save