Commits

catseye  committed daae52c

Make local variables Python-like. Add failing test cases.

  • Participants
  • Parent commits cc152ea

Comments (0)

Files changed (7)

File README.markdown

     with a union type which includes `void`, and `typecase` generalizes
     Eightebed's `ifvalid`.
 
-*   **Pascal**: All local variables used in a function must be declared at
-    the start of the function body.  (This may change to something more
-    Python-like in the near future.)
+*   **Python**: The first time a local variable is assigned counts as its
+    declaration as a local.
 
 *   **Ruby**: The last expression in a function body is the return value
     of that function; no explicit `return` is needed there.  (But unlike
               | "struct" ident "{" {ident ":" TExpr [";"]} "}"
               | ident (":" TExpr0 | "=" Literal).
     Arg     ::= ident [":" TExpr1].
-    Body    ::= "{" {VarDecl [";"]} {Stmt [";"]} "}".
-    VarDecl ::= "var" ident "=" Expr0.
+    Body    ::= "{" {Stmt [";"]} "}".
     Stmt    ::= "while" Expr0 Block
               | "typecase" ident "is" TExpr0 Block
               | "do" Expr0
               | "(" Expr0 ")"
               | "not" Expr1
               | Literal
-              | ident ["=" Expr0].
+              | ["var"] ident ["=" Expr0].
     Literal ::= strlit
               | ["-"] intlit
               | "true" | "false" | "null"
 An `if`/`else` lets you make decisions.
 
     | fun main() {
-    |   var a = 0;
+    |   a = 0;
     |   if 3 > 2 {
     |     a = 70
     |   } else {
 An `if` need not have an `else`.
 
     | fun main() {
-    |   var a = 60
+    |   a = 60
     |   if 3 > 2 {
     |     a = 70
     |   }
 `if` always typechecks to void, one branch or two.
 
     | fun main() {
-    |   var a = 60
+    |   a = 60
     |   if 3 > 2 {
     |     a = 70
     |   }
     = 
 
     | fun main() {
-    |   var a = 60
+    |   a = 60
     |   if 3 > 2 {
     |     a = 70
     |   } else {
 `while` loops.
 
     | fun main() {
-    |   var a = 0 var b = 4
+    |   a = 0 b = 4
     |   while b > 0 {
     |     a = a + b
     |     b = b - 1
 A `while` itself has void type.
 
     | fun main() {
-    |   var a = 0; var b = 4;
+    |   a = 0; b = 4;
     |   while b > 0 {
     |     a = a + b;
     |     b = b - 1;
 `break` may be used to prematurely exit a `while`.
 
     | fun main() {
-    |   var a = 0; var b = 0;
+    |   a = 0; b = 0;
     |   while true {
     |     a = a + b;
     |     b = b + 1;
 Local variables.
 
     | fun main() {
-    |   var a = 6;
-    |   var b = 7;
+    |   a = 6;
+    |   b = 7;
     |   a + b
     | }
     = 13
 
-Local variable names must be unique.
-
-    | fun main() {
-    |   var a = 6;
-    |   var a = 7;
-    |   a + b
-    | }
-    ? shadows
-
 Local variables can be assigned functions.
 
     | fun ancillary(x) { x * 2 }
     | fun main() {
-    |   var a = ancillary;
+    |   a = ancillary;
     |   a(7)
     | }
     = 14
 Local variables can be assigned.
 
     | fun main() {
-    |   var a = 6;
+    |   a = 6;
     |   a = a + 12;
     |   a
     | }
     = 18
 
     | fun main() {
-    |   var a = 6;
-    |   a = 99;
+    |   a = 6;
+    |   z = 99;
     |   a
     | }
-    = 99
+    = 6
 
     | fun main() {
-    |   var a = 6;
-    |   z = 99;
+    |   z = 6;
+    |   a
+    | }
+    ? undefined
+
+Local variables cannot occur in expressions until they are defined by an
+initial assignment.
+
+    | fun main() {
+    |   z = a * 10;
+    |   a = 10;
+    |   z
+    | }
+    ? undefined
+
+A local variables should not be defined inside an `if`... yeah, tricky, I don't
+think we're there yet.
+
+    | fun main() {
+    |   if (4 > 5) {
+    |     a = 10;
+    |   } else {
+    |     b = 11;
+    |   }
     |   a
     | }
     ? undefined
 
     | fun main() {
-    |   var z = 6;
-    |   a
+    |   if (4 > 5) {
+    |     a = 10;
+    |   } else {
+    |     b = 11;
+    |   }
+    |   b
     | }
     ? undefined
 
 void, so it can only really happen at the statement level.
 
     | fun main() {
-    |   var a = 0; var b = 0;
+    |   a = 0; b = 0;
     |   a = b = 9;
     | }
     ? type mismatch
 
-Variables may only be declared in a function body, not an inner block.
-
-    | fun main() {
-    |   var a = 0;
-    |   if 3 > 2 {
-    |     var a = 7;
-    |   }
-    |   a
-    | }
-    ? keyword
-
-Variables must be declared at the start of a function body.
-
-    | fun main() {
-    |   print("Hi");
-    |   var a = 1;
-    |   if 3 > 2 {
-    |     a = 7;
-    |   }
-    |   a
-    | }
-    ? keyword
-
 Variables in upper scopes may be modified.
 
     | fun main() {
-    |   var a = 0
+    |   a = 0
     |   if 3 > 2 {
     |     a = 4;
     |   }
     | }
     = 30
 
-Toplevel literals may not be updated.
+Toplevel literals may not be updated.  (And thus
 
     | factor = 5
     | fun main() {
     |   factor = 7
     | }
-    ? cannot assign
-
-Toplevel literals may not be shadowed.
-
-    | factor = 5;
-    | fun main() {
-    |   var factor = 7;
-    |   6 * factor
-    | }
     ? shadows
 
 Toplevel literals may be function literals (the syntax we've been using is just sugar.)
     | fun main() {
     |   foo(7)
     | }
-    ? cannot assign
+    ? shadows
 
 Factorial can be computed.
 
 Literal functions.
 
     | fun main() {
-    |   var inc = fun(x) { x + 1 };
+    |   inc = fun(x) { x + 1 };
     |   inc(7)
     | }
     = 8
     = 10
 
     | fun main() {
-    |   var a = 99;
+    |   a = 99;
     |   a = fun(x){ x + 1 }(9);
     |   a
     | }
 Literal functions can have local variables, loops, etc.
 
     | fun main() {
-    |   var z = 99;
+    |   z = 99;
     |   z = fun(x) {
-    |     var a = x;  var b = x;
+    |     a = x;  b = x;
     |     while a > 0 {
     |       b = b + a; a = a - 1;
     |     }
 Literal functions cannot access variables declared in enclosing scopes.
 
     | fun main() {
-    |   var oid = 19;
+    |   oid = 19;
     |   fun(x){ x + oid }(11);
     | }
     ? undefined
     | fun apply_and_add_one(f: (integer -> integer), x) { f(x) + 1 }
     | fun select(a) { if a > 10 { return double } else { return triple } }
     | fun main() {
-    |   var t = select(5);
-    |   var d = select(15);
-    |   var p = t(10);
+    |   t = select(5);
+    |   d = select(15);
+    |   p = t(10);
     |   apply_and_add_one(d, p)
     | }
     = 61
 `return` may be used to prematurely return a value from a function.
 
     | fun foo(y) {
-    |   var x = y
+    |   x = y
     |   while x > 0 {
     |     if x < 5 {
     |       return x;
 Some standard functions are builtin and available as toplevels.
 
     | fun main() {
-    |   var a = "hello";
-    |   var b = len(a);
+    |   a = "hello";
+    |   b = len(a);
     |   while b > 0 {
     |     print(a);
     |     b = b - 1;
 
     | struct person { name: string; age: integer }
     | main = fun() {
-    |   var j = make person(name:"Jake", age:23);
+    |   j = make person(name:"Jake", age:23);
+    |   print("ok")
     | }
-    = 
+    = ok
 
 And extract the fields from them.
 
     | struct person { name: string; age: integer }
     | main = fun() {
-    |   var j = make person(name:"Jake", age:23);
+    |   j = make person(name:"Jake", age:23);
     |   print(j.name)
     |   if j.age > 20 {
     |     print("Older than twenty")
 Structs must be defined somewhere.
 
     | main = fun() {
-    |   var j = make person(name:"Jake", age:23);
+    |   j = make person(name:"Jake", age:23);
     |   j
     | }
     ? undefined
 Structs need not be defined before use.
 
     | main = fun() {
-    |   var j = make person(name:"Jake", age:23);
+    |   j = make person(name:"Jake", age:23);
     |   j.age
     | }
     | struct person { name: string; age: integer }
 
     | struct person { name: string; age: integer }
     | main = fun() {
-    |   var j = make person(name:"Jake", age:"Old enough to know better");
+    |   j = make person(name:"Jake", age:"Old enough to know better");
     |   j.age
     | }
     ? type mismatch
 
     | struct person { name: string; age: integer }
     | main = fun() {
-    |   var j = make person(name:"Jake");
+    |   j = make person(name:"Jake");
     |   j.age
     | }
     ? argument mismatch
 
     | struct person { name: string }
     | main = fun() {
-    |   var j = make person(name:"Jake", age:23);
+    |   j = make person(name:"Jake", age:23);
     |   j.age
     | }
     ? argument mismatch
 
     | struct person { name: string; age: integer }
     | main = fun() {
-    |   var j = make person(age: 23, name:"Jake");
+    |   j = make person(age: 23, name:"Jake");
     |   j.age
     | }
     = 23
 
     /| struct person { age: integer; name: string }
     /| main = fun() {
-    /|   var j = make person(age: 23, name:"Jake");
-    /|   var k = make person(age: 23, name:"Jake");
+    /|   j = make person(age: 23, name:"Jake");
+    /|   k = make person(age: 23, name:"Jake");
     /|   j == k
     /| }
     /= True
 
     /| struct person { name: string; age: integer }
     /| main = fun() {
-    /|   var j = make person(age: 23, name:"Jake");
-    /|   var k = make person(name:"Jake", age: 23);
+    /|   j = make person(age: 23, name:"Jake");
+    /|   k = make person(name:"Jake", age: 23);
     /|   j == k
     /| }
     /= True
 
     /| struct person { age: integer; name: string }
     /| main = fun() {
-    /|   var j = make person(age: 23, name:"Jake");
-    /|   var k = make person(age: 23, name:"John");
+    /|   j = make person(age: 23, name:"Jake");
+    /|   k = make person(age: 23, name:"John");
     /|   j == k
     /| }
     /= False
     | struct person { name: string; age: integer }
     | fun wat(bouncer: person) { bouncer.age }
     | main = fun() {
-    |   var j = make person(name:"Jake", age:23);
+    |   j = make person(name:"Jake", age:23);
     |   wat(j)
     | }
     = 23
     | struct city { name: string; population: integer }
     | fun wat(hometown: city) { hometown }
     | main = fun() {
-    |   var j = make person(name:"Jake", age:23);
+    |   j = make person(name:"Jake", age:23);
     |   wat(j)
     | }
     ? type mismatch
 
     | struct person { name: string; name: string }
     | main = fun() {
-    |   var j = make person(name:"Jake", name:"Smith");
+    |   j = make person(name:"Jake", name:"Smith");
     | }
     ? defined
 
     | struct person { name: string; age: integer }
     | fun age(bouncer: person) { bouncer.age }
     | main = fun() {
-    |   var j = make person(name:"Jake", age:23);
+    |   j = make person(name:"Jake", age:23);
     |   age(j)
     | }
     = 23
     | struct person { name: string }
     | fun age(bouncer: person) { bouncer.age }
     | main = fun() {
-    |   var j = make person(name:"Jake");
+    |   j = make person(name:"Jake");
     |   age(j)
     | }
     ? undefined
     | struct person { name: string; age: integer }
     | struct city { population: integer; name: string }
     | main = fun() {
-    |   var j = make person(name:"Jake", age:23);
-    |   var w = make city(population:600000, name:"Winnipeg");
+    |   j = make person(name:"Jake", age:23);
+    |   w = make city(population:600000, name:"Winnipeg");
     |   print(j.name)
     |   print(w.name)
     | }
 The type after the `as` must be a union.
 
     | fun main() {
-    |   var a = 20;
-    |   var b = 30;
+    |   a = 20;
+    |   b = 30;
     |   a + b as integer
     | }
     ? bad cast
 The type after the `as` must be one of the types in the union.
 
     | fun main() {
-    |   var a = 20;
-    |   var b = 30;
+    |   a = 20;
+    |   b = 30;
     |   a + b as string|void
     | }
     ? bad cast
 The type after the `as` must be the type of the expression.
 
     | fun main() {
-    |   var a = 20;
-    |   var b = 30;
-    |   var c = a + b as integer|string
+    |   a = 20;
+    |   b = 30;
+    |   c = a + b as integer|string
     |   print("ok")
     | }
     = ok
     |   a + 1
     | }
     | main = fun() {
-    |   var a = 0;
+    |   a = 0;
     |   a = foo(a, 333 as integer|string);
     |   a = foo(a, "hiya" as integer|string);
     |   a
     |   a + 1
     | }
     | main = fun() {
-    |   var a = 0;
+    |   a = 0;
     |   a = foo(a, 333 as integer|string);
     |   a = foo(a, "hiya" as string|integer);
     |   a
 The `typecase` construct can operate on the "right" type of a union.
 
     | fun foo(a, b: integer|string) {
-    |   var r = a;
+    |   r = a;
     |   typecase b is integer {
     |     r = r + b;
     |   };
     |   r
     | }
     | main = fun() {
-    |   var a = 0;
+    |   a = 0;
     |   a = foo(a, 333 as integer|string);
     |   a = foo(a, "hiya" as integer|string);
     |   a
 The expression in a `typecase` must be a variable.
 
     | main = fun() {
-    |   var a = 333 as integer|string;
+    |   a = 333 as integer|string;
     |   typecase 333 is integer {
     |     print("what?")
     |   };
 Inside a `typecase` the variable cannot be updated.
 
     | main = fun() {
-    |   var a = 333 as integer|string;
+    |   a = 333 as integer|string;
     |   typecase a is integer {
     |     a = 700;
     |   };
 The union can include void.
 
     | main = fun() {
-    |   var j = null as void|integer;
+    |   j = null as void|integer;
     |   typecase j is void {
     |     print("nothing there")
     |   };
     |   next: list|integer;
     | }
     | main = fun() {
-    |   var l = make list(
+    |   l = make list(
     |     value: "first",
     |     next: make list(
     |       value: "second",
     |       next:0 as list|integer
     |     ) as list|integer)
-    |   var s = l.next
+    |   s = l.next
     |   typecase s is list {
     |     print(s.value)
     |   }
     |   make list(value:v, next:l as list|void)
     | }
     | fun nth(n, l: list) {
-    |   var u = l as list|void;
-    |   var v = u;
-    |   var k = n;
+    |   u = l as list|void;
+    |   v = u;
+    |   k = n;
     |   while k > 1 {
     |     typecase u is void { break; }
     |     typecase u is list { v = u.next; }
     |   return u
     | }
     | main = fun() {
-    |   var l = cons("first", singleton("second"));
-    |   var g = nth(2, l);
+    |   l = cons("first", singleton("second"));
+    |   g = nth(2, l);
     |   typecase g is list { print(g.value); }
     | }
     = second

File src/castile/backends/javascript.py

   print(repr(result));
 """)
         elif ast.tag == 'Defn':
-            self.out.write('%s = ' % ast.value)
+            self.out.write('var %s = ' % ast.value)
             self.compile(ast.children[0])
             self.out.write(';\n')
         elif ast.tag in ('StructDefn', 'Forward'):
             for child in ast.children:
                 self.compile(child)
         elif ast.tag == 'VarDecl':
-            self.out.write('%s = ' % ast.value)
-            self.compile(ast.children[0])
-            self.out.write(';\n')
+            self.out.write('var %s;\n' % ast.value)
         elif ast.tag == 'Block':
             self.out.write('{')
             for child in ast.children:

File src/castile/backends/ruby.py

             for child in ast.children:
                 self.compile(child)
         elif ast.tag == 'VarDecl':
-            self.out.write('%s = ' % ast.value)
-            self.compile(ast.children[0])
-            self.out.write('\n')
+            self.out.write('%s = nil;\n' % ast.value)
         elif ast.tag == 'Block':
             for child in ast.children:
                 self.compile(child)

File src/castile/backends/stackmac.py

             for child in ast.children:
                 self.compile(child)
         elif ast.tag == 'VarDecl':
-            self.compile(ast.children[0])
+            self.out.write('push 0\n')
             self.out.write('%s_local_%s=%s\n' %
                 (self.fun_lit, ast.value, self.local_pos))
             self.local_pos += 1

File src/castile/checker.py

         elif ast.tag == 'Body':
             self.context = ScopedContext({}, self.context,
                                          level='local')
-            for child in ast.children:
-                self.assert_eq(self.type_of(child), Void())
+            self.assert_eq(self.type_of(ast.children[1]), Void())
             self.context = self.context.parent
             ast.type = Void()
-        elif ast.tag == 'VarDecls':
-            for child in ast.children:
-                self.assert_eq(self.type_of(child), Void())
-            ast.type = Void()
-        elif ast.tag == 'VarDecl':
-            name = ast.value
-            if name in self.context:
-                raise CastileTypeError('declaration of %s shadows previous' % name)
-            self.set(name, self.type_of(ast.children[0]))
-            ast.type = Void()
         elif ast.tag == 'FunType':
             return_type = self.type_of(ast.children[0])
             ast.type = Function([self.type_of(c) for c in ast.children[1:]],
                 self.assert_eq(self.type_of(child), Void())
             ast.type = Void()
         elif ast.tag == 'Assignment':
-            t1 = self.type_of(ast.children[0])
+            t2 = self.type_of(ast.children[1])
+            t1 = None
+            name = ast.children[0].value
+            if ast.aux == 'defining instance':
+                if name in self.context:
+                    raise CastileTypeError('definition of %s shadows previous' % name)
+                self.set(name, t2)
+                t1 = t2
+            else:
+                if name not in self.context:
+                    raise CastileTypeError('variable %s used before definition' % name)
+                t1 = self.type_of(ast.children[0])
+            self.assert_eq(t1, t2)
+            # not quite useless now (typecase still likes this)
             if self.context.level(ast.children[0].value) != 'local':
                 raise CastileTypeError('cannot assign to non-local')
-            t2 = self.type_of(ast.children[1])
-            self.assert_eq(t1, t2)
             ast.type = Void()
         elif ast.tag == 'Make':
             t = self.type_of(ast.children[0])

File src/castile/eval.py

         if ast is None:
             ast = self.ast.children[1]
         if ast.tag == 'Body':
-            self.eval(ast.children[0])  # to collect locals
             return self.eval(ast.children[1])
-        elif ast.tag == 'VarDecls':
-            for child in ast.children:
-                self.eval(child)
-            return None
-        elif ast.tag == 'VarDecl':
-            name = ast.value
-            v = self.eval(ast.children[0])
-            self.locals[name] = v
-            return None
         elif ast.tag == 'Block':
             v1 = None
             for stmt in ast.children:

File src/castile/parser.py

 
     * It inserts a final `return` in a block where the last statement is a
       non-void expression.
+    * It collects all assigned-to variable names in a function body, and
+      turns them into VarDecl nodes.
 
     """
     def __init__(self, text):
         self.token = None
         self.type = None
         self.scan()
+        # for parser...
+        self.locals = None
 
     ### SCANNER ###
 
         # last expression to a 'return' if it's not a statement
         # (and inserts a 'return' if the block is empty)
         self.expect('{')
-        vardecls = []
-        while self.consume('var'):
-            id = self.expect_type('identifier')
-            self.expect('=')
-            e = self.expr0()
-            vardecls.append(AST('VarDecl', [e], value=id))
-            self.consume(';')
+        save_locals = self.locals
+        self.locals = set()
         stmts = []
         last = None
         while not self.on('}'):
         elif last is not None and last.tag not in self.STMT_TAGS:
             stmts[-1] = AST('Return', [stmts[-1]])
         self.expect('}')
-        vardecls = AST('VarDecls', vardecls)
+        vardecls = AST('VarDecls',
+            [AST('VarDecl', value=name) for name in self.locals]
+        )
         stmts = AST('Block', stmts)
+        self.locals = save_locals
         return AST('Body', [vardecls, stmts])
 
     def stmt(self):
             self.expect(')')
             return e
         else:
+            self.consume('var')
             id = self.expect_type('identifier')
             ast = AST('VarRef', value=id)
             if self.consume('='):
                 e = self.expr0()
-                ast = AST('Assignment', [ast, e])
+                aux = None
+                if id not in self.locals:
+                    self.locals.add(id)
+                    aux = 'defining instance'
+                ast = AST('Assignment', [ast, e], aux=aux)
             return ast
 
     def literal(self):