Compiler Design

Delta: An Incremental Compiler
Step 19: Loop/Exit statements

Description

Add support for loop/exit statements. This kind of statement should start with the loop keyword, followed by a list of zero or more statements enclosed inside curly braces (the body). Inside the body zero or more exit statements can be placed. This statement starts with the exit and when keywords, followed by an expression (the condition) and ends with a semicolon. The body of the loop is executed and repeated until the exit statement is executed with a condition that evaluates to true (any non-zero value).

It’s a semantic error to have an exit statement outside a loop statement. Also, make sure to consider loop, exit, and when as a Delta reserved keywords from now on.

Example

The Delta program:

var n, r, i;
n = 5;
r = 1;
i = 0;
loop {
    i = i + 1;
    exit when !((n + 1) - i);
    r = r * i;
}
r

should produce the following WAT code:

(module
  (func
    (export "_start")
    (result i32)
    (local $n i32)
    (local $r i32)
    (local $i i32)
    i32.const 5
    local.set $n
    i32.const 1
    local.set $r
    i32.const 0
    local.set $i
    block $00000
    loop
    local.get $i
    i32.const 1
    i32.add
    local.set $i
    local.get $n
    i32.const 1
    i32.add
    local.get $i
    i32.sub
    i32.eqz
    br_if $00000
    local.get $r
    local.get $i
    i32.mul
    local.set $r
    br 0
    end
    end
    local.get $r
  )
)

The above WAT function’s return value should be:

120

Unit Tests

# File: tests/test_19_loop_exit.py

from unittest import TestCase
from delta import Compiler, SyntaxMistake
from delta.semantics import SemanticMistake


class TestLoopExit(TestCase):

    def setUp(self):
        self.c = Compiler('program_start')

    def test_syntax_mistake(self):
        with self.assertRaises(SyntaxMistake):
            self.c.realize('loop {')

    def test_semantic_mistake1(self):
        with self.assertRaises(SemanticMistake):
            self.c.realize('var loop; 0')

    def test_semantic_mistake2(self):
        with self.assertRaises(SemanticMistake):
            self.c.realize('var exit; 0')

    def test_semantic_mistake3(self):
        with self.assertRaises(SemanticMistake):
            self.c.realize('var when; 0')

    def test_loop_exit_zero(self):
        self.assertEqual(0,
                         self.c.realize(
                            '''
                            var x, y;
                            x = 1;
                            y = 0;
                            loop {
                                x = x - 1;
                                exit when !x;
                                y = 1;
                            }
                            x + y
                            '''))

    def test_loop_exit_fact(self):
        self.assertEqual(120,
                         self.c.realize(
                            '''
                            var n, r, i;
                            n = 5;
                            r = 1;
                            i = 0;
                            loop {
                                i = i + 1;
                                exit when !((n + 1) - i);
                                r = r * i;
                            }
                            r
                            '''))

    def test_loop_exit_count_down(self):
        self.assertEqual(0,
                         self.c.realize(
                            '''
                            var i;
                            i = 10;
                            loop {
                                exit when !i;
                                i = i - 1;
                            }
                            i
                            '''))

    def test_loop_exit_skip_body(self):
        self.assertEqual(9,
                         self.c.realize(
                            '''
                            var n;
                            n = 10;
                            loop {
                                n = n - 1;
                                if false {
                                } else {
                                    exit when n;
                                }
                            }
                            n
                            '''))

    def test_loop_exit_fibo(self):
        self.assertEqual(55,
                         self.c.realize(
                            '''
                            var n, a, b;
                            n = 10;
                            a = 0;
                            b = 1;
                            loop {
                                exit when !n;
                                var t;
                                t = b;
                                b = a + b;
                                a = t;
                                n = n - 1;
                            }
                            a
                            '''))

    def test_loop_exit_nested(self):
        self.assertEqual(1500,
                         self.c.realize(
                            '''
                            var r, i;
                            r = 0;
                            i = 10;
                            loop {
                                var j;
                                j = 50;
                                loop {
                                    var k;
                                    k = 3;
                                    loop {
                                        r = r + 1;
                                        k = k - 1;
                                        exit when !k;
                                    }
                                    j = j - 1;
                                    exit when !j;
                                }
                                i = i - 1;
                                exit when !i;
                            }
                            r
                            '''))

    def test_loop_exit_multiple_exits(self):
        self.assertEqual(9,
                         self.c.realize(
                            '''
                            var r, i;
                            r = 0;
                            i = 10;
                            loop {
                                i = i - 1;
                                if i {
                                    exit when !(i % 2);
                                } else {
                                    exit when true;
                                }
                                r = r + i;
                                exit when !i;
                            }
                            r
                            '''))