]> ocean-lang.org Git - ocean/commitdiff
oceani: Add functions
authorNeilBrown <neil@brown.name>
Sun, 7 Nov 2021 21:21:30 +0000 (08:21 +1100)
committerNeilBrown <neil@brown.name>
Fri, 12 Nov 2021 23:09:44 +0000 (10:09 +1100)
As yet, functions cannot return a value, but they can be passed
parameters.
They are declared a bit like consts, but there isn't a common
header for multiple constants.

A pointer to the body of the function is stored in the 'global' area,
like the vale of any other constant.

Functions can be called as a statement - providing they don't return
anything - or as an expression, which will currently be a type error as
they cannot return the correct type.

We allocate a new 'local' frame for each function call, and free it when
the function completes.

Signed-off-by: NeilBrown <neil@brown.name>
csrc/oceani-tests.mdc
csrc/oceani.mdc

index 5d1119785ea11760170e6fb180d6b32d4df5eb63..d9be788450c88baf56d37a6e613b18fb31e88dff 100644 (file)
@@ -80,7 +80,7 @@ arguments separated from the name by commas.  For each test, there is a section
                @mv *.gcov coverage ; [ -f .gcov ] && mv .gcov coverage || true
                @[ -n "$$SKIP_COVERAGE_CHECK" ] || awk '/NOTEST/ { next } /^ *[1-9]/ {ran+=1} /^ *###/ {skip+=1} \
                    END {printf "coverage: %6.2f%%\n", ran * 100 / (ran + skip); \
                @mv *.gcov coverage ; [ -f .gcov ] && mv .gcov coverage || true
                @[ -n "$$SKIP_COVERAGE_CHECK" ] || awk '/NOTEST/ { next } /^ *[1-9]/ {ran+=1} /^ *###/ {skip+=1} \
                    END {printf "coverage: %6.2f%%\n", ran * 100 / (ran + skip); \
-                        if (ran < (ran + skip) *0.96) exit(1) }' \
+                        if (ran < (ran + skip) *0.968) exit(1) }' \
                        coverage/oceani.mdc.gcov
                @rm -f .tmp*
 
                        coverage/oceani.mdc.gcov
                @rm -f .tmp*
 
@@ -110,9 +110,7 @@ calculations on them.
 
 ###### test: valvar
 
 
 ###### test: valvar
 
-       func main
-               argv:[argc::]string
-       do
+       func main(argv:[argc::]string)
                a := 23; b:=12 ; b1 := -b
                print a, b, a+b, a-b, a*b, a/b, a%b
                print a<b, a<=b, a>b, a>=b, a<a, a==b, a==a
                a := 23; b:=12 ; b1 := -b
                print a, b, a+b, a-b, a*b, a/b, a%b
                print a<b, a<=b, a>b, a>=b, a<a, a==b, a==a
@@ -613,6 +611,42 @@ Time to test if structure declarations and accesses work correctly.
        peter False 4
        janine False 9
 
        peter False 4
        janine False 9
 
+## Functions
+
+Test functions.  They don't return anything, so we need to get them to print
+
+###### test list
+       oceani_tests += functions
+
+###### test: functions
+
+       func test1 then
+               t: Boolean
+       do
+               if t:
+                       print "true"
+
+       func noarg
+       do
+               pass
+
+       func test(n:number; s:string)
+               if n >= 1:
+                       print n,s,
+                       test(n-1, "."++s)
+               else
+                       print "done"
+       func main()
+               for i:=0; then i = i + 1; while i < 5:
+                       test(i, " ")
+
+###### output: functions
+       done
+       1  done
+       2  1 . done
+       3  2 . 1 .. done
+       4  3 . 2 .. 1 ... done
+
 ## Test code with syntax errors
 
 Syntax errors aren't handled well yet - the result is almost always a
 ## Test code with syntax errors
 
 Syntax errors aren't handled well yet - the result is almost always a
@@ -833,7 +867,7 @@ various places that `type_err()` are called.
        oceani: type error in program - not running.
 
 ###### test list
        oceani: type error in program - not running.
 
 ###### test list
-       oceani_failing_tests += type_err_const type_err_const1 missing_program
+       oceani_failing_tests += type_err_const type_err_const1 missing_program bad_main
 
 ###### test: type_err_const
        const
 
 ###### test: type_err_const
        const
@@ -856,9 +890,10 @@ various places that `type_err()` are called.
        .tmp.code:4:8: info: this is where 'bar' was first declared
        .tmp.code:8:8: error: variable 'foo' redeclared
        .tmp.code:3:8: info: this is where 'foo' was first declared
        .tmp.code:4:8: info: this is where 'bar' was first declared
        .tmp.code:8:8: error: variable 'foo' redeclared
        .tmp.code:3:8: info: this is where 'foo' was first declared
+       .tmp.code:12:5: error: function 'main' redeclared
+       .tmp.code:7:5: info: this is where 'main' was first declared
        .tmp.code:13:8: error: variable 'foo' redeclared
        .tmp.code:3:8: info: this is where 'foo' was first declared
        .tmp.code:13:8: error: variable 'foo' redeclared
        .tmp.code:3:8: info: this is where 'foo' was first declared
-       .tmp.code:13:8: "main" defined a second time
 
 ###### test: type_err_const1
        const
 
 ###### test: type_err_const1
        const
@@ -879,6 +914,74 @@ various places that `type_err()` are called.
 ###### output: missing_program
        oceani: no main function found.
 
 ###### output: missing_program
        oceani: no main function found.
 
+###### test: bad_main
+       func main(foo:string)
+               print foo
+
+###### output: bad_main
+       .tmp.code:??:??: error: expected  argv but variable 'foo' is string
+       .tmp.code:??:??: info: this is where 'NOTVAR' was set to string
+       oceani: main has wrong type.
+
+Test for type errors with functions
+
+###### test list
+       oceani_failing_tests += func_err_args func_err_redeclare
+
+###### test: func_err_args
+
+       func test1(a:number; b:string; c:[3]Boolean)
+               print a, b, c[1]
+
+       func test2(a:number; b:string; c:[3]Boolean)
+               print a, b, c[1]
+
+       func main()
+               truth:[3]Boolean
+               truth[1] = True
+               test1(1,"hello")
+               test1("hello",1)
+               test1(1, "two", truth)
+               test1(1, 2, truth)
+               test1(1, "lo", truth, 4)
+               print test(), test1(1,2,3)
+               if test1 == test2:
+                       pass
+
+###### output: func_err_args
+       .tmp.code:11:14: error: insufficient arguments to function.
+       .tmp.code:12:14: error: expected number found string
+       .tmp.code:12:22: error: expected string found number
+       .tmp.code:12:14: error: insufficient arguments to function.
+       .tmp.code:14:17: error: expected string found number
+       .tmp.code:15:14: error: too many arguments to function.
+       .tmp.code:16:14: error: attempt to call a non-function.
+       .tmp.code:16:32: error: expected string found number
+       .tmp.code:16:28: error: insufficient arguments to function.
+       .tmp.code:17:20: error: expected  func but variable 'test2' is  func
+       .tmp.code:??:??: info: this is where 'NOTVAR' was set to  func
+       oceani: type error in program - not running.
+
+###### test: func_err_redeclare
+
+       func test1(a:number; b:string; c:[3]Boolean)
+               print a, b, c[1]
+
+       func test1
+       do
+               pass
+
+       func test1 then
+               b:Boolean
+       do
+               pass
+
+###### output: func_err_redeclare
+       .tmp.code:5:5: error: function 'test1' redeclared
+       .tmp.code:2:5: info: this is where 'test1' was first declared
+       .tmp.code:9:5: error: function 'test1' redeclared
+       .tmp.code:2:5: info: this is where 'test1' was first declared
+
 ## Test erroneous command line args
 
 To improve coverage, we want to test correct handling of strange command
 ## Test erroneous command line args
 
 To improve coverage, we want to test correct handling of strange command
index 3aa170bd28978a117f7dc27603339437a37727ca..f257736edc328baf8ba313d28f3bd7826e4e5004 100644 (file)
@@ -114,7 +114,6 @@ structures can be used.
                struct token_config config;
                char *file_name;
                int parse_error;
                struct token_config config;
                char *file_name;
                int parse_error;
-               struct exec *prog;
                ## parse context
        };
 
                ## parse context
        };
 
@@ -243,24 +242,18 @@ structures can be used.
 
                parse_oceani(ss->code, &context.config, dotrace ? stderr : NULL);
 
 
                parse_oceani(ss->code, &context.config, dotrace ? stderr : NULL);
 
-               if (!context.prog) {
-                       fprintf(stderr, "oceani: no main function found.\n");
+               if (!context.parse_error && !analyse_funcs(&context)) {
+                       fprintf(stderr, "oceani: type error in program - not running.\n");
                        context.parse_error = 1;
                }
                        context.parse_error = 1;
                }
-               if (context.prog && !context.parse_error) {
-                       if (!analyse_prog(context.prog, &context)) {
-                               fprintf(stderr, "oceani: type error in program - not running.\n");
-                               context.parse_error = 1;
-                       }
-               }
-               if (context.prog && doprint) {
+
+               if (doprint) {
                        ## print const decls
                        ## print type decls
                        ## print const decls
                        ## print type decls
-                       print_exec(context.prog, 0, brackets);
+                       ## print func decls
                }
                }
-               if (context.prog && doexec && !context.parse_error)
-                       interp_prog(&context, context.prog, argc - optind, argv+optind);
-               free_exec(context.prog);
+               if (doexec && !context.parse_error)
+                       interp_main(&context, argc - optind, argv + optind);
 
                while (s) {
                        struct section *t = s->next;
 
                while (s) {
                        struct section *t = s->next;
@@ -268,7 +261,9 @@ structures can be used.
                        free(s);
                        s = t;
                }
                        free(s);
                        s = t;
                }
-               ## free global vars
+               if (!context.parse_error) {
+                       ## free global vars
+               }
                ## free context types
                ## free context storage
                exit(context.parse_error ? 1 : 0);
                ## free context types
                ## free context storage
                exit(context.parse_error ? 1 : 0);
@@ -365,6 +360,9 @@ context so indicate that parsing failed.
 ###### forward decls
 
        static void fput_loc(struct exec *loc, FILE *f);
 ###### forward decls
 
        static void fput_loc(struct exec *loc, FILE *f);
+       static void type_err(struct parse_context *c,
+                            char *fmt, struct exec *loc,
+                            struct type *t1, int rules, struct type *t2);
 
 ###### core functions
 
 
 ###### core functions
 
@@ -1212,7 +1210,7 @@ all pending-scope variables become conditionally scoped.
                                 */
                                continue;
                        v->min_depth = c->scope_depth;
                                 */
                                continue;
                        v->min_depth = c->scope_depth;
-                       if (v->scope == InScope) {
+                       if (v->scope == InScope && e) {
                                /* This variable gets cleaned up when 'e' finishes */
                                variable_unlink_exec(v);
                                v->cleanup_exec = e;
                                /* This variable gets cleaned up when 'e' finishes */
                                variable_unlink_exec(v);
                                v->cleanup_exec = e;
@@ -1349,7 +1347,7 @@ tell if it was set or not later.
                        t->prepare_type(c, t, 1);       // NOTEST
 
                if (c->global_size & (t->align - 1))
                        t->prepare_type(c, t, 1);       // NOTEST
 
                if (c->global_size & (t->align - 1))
-                       c->global_size = (c->global_size + t->align) & ~(t->align-1);   // UNTESTED
+                       c->global_size = (c->global_size + t->align) & ~(t->align-1);
                if (!v) {
                        v = &scratch;
                        v->type = t;
                if (!v) {
                        v = &scratch;
                        v->type = t;
@@ -1370,14 +1368,15 @@ As global values are found -- struct field initializers, labels etc --
 
 When the program is fully parsed, we need to walk the list of variables
 to find any that weren't merged away and that aren't global, and to
 
 When the program is fully parsed, we need to walk the list of variables
 to find any that weren't merged away and that aren't global, and to
-calculate the frame size and assign a frame position for each variable.
-For this we have `scope_finalize()`.
+calculate the frame size and assign a frame position for each
+variable.  For this we have `scope_finalize()`.
 
 ###### ast functions
 
 
 ###### ast functions
 
-       static void scope_finalize(struct parse_context *c)
+       static int scope_finalize(struct parse_context *c)
        {
                struct binding *b;
        {
                struct binding *b;
+               int size = 0;
 
                for (b = c->varlist; b; b = b->next) {
                        struct variable *v;
 
                for (b = c->varlist; b; b = b->next) {
                        struct variable *v;
@@ -1387,18 +1386,19 @@ For this we have `scope_finalize()`.
                                        continue;
                                if (v->global)
                                        continue;
                                        continue;
                                if (v->global)
                                        continue;
-                               if (c->local_size & (t->align - 1))
-                                       c->local_size = (c->local_size + t->align) & ~(t->align-1);
-                               v->frame_pos = c->local_size;
-                               c->local_size += v->type->size;
+                               if (!t)
+                                       continue;
+                               if (size & (t->align - 1))
+                                       size = (size + t->align) & ~(t->align-1);
+                               v->frame_pos = size;
+                               size += v->type->size;
                        }
                }
                        }
                }
-               c->local = calloc(1, c->local_size);
+               return size;
        }
 
 ###### free context storage
        free(context.global);
        }
 
 ###### free context storage
        free(context.global);
-       free(context.local);
 
 ### Executables
 
 
 ### Executables
 
@@ -1462,12 +1462,12 @@ from the `exec_types` enum.
                if (loc->type == Xbinode)
                        return __fput_loc(cast(binode,loc)->left, f) ||
                               __fput_loc(cast(binode,loc)->right, f);  // NOTEST
                if (loc->type == Xbinode)
                        return __fput_loc(cast(binode,loc)->left, f) ||
                               __fput_loc(cast(binode,loc)->right, f);  // NOTEST
-               return 0;                       // NOTEST
+               return 0;
        }
        static void fput_loc(struct exec *loc, FILE *f)
        {
                if (!__fput_loc(loc, f))
        }
        static void fput_loc(struct exec *loc, FILE *f)
        {
                if (!__fput_loc(loc, f))
-                       fprintf(f, "??:??: ");  // NOTEST
+                       fprintf(f, "??:??: ");
        }
 
 Each different type of `exec` node needs a number of functions defined,
        }
 
 Each different type of `exec` node needs a number of functions defined,
@@ -1520,7 +1520,7 @@ also want to know what sort of bracketing to use.
 
        static void do_indent(int i, char *str)
        {
 
        static void do_indent(int i, char *str)
        {
-               while (i--)
+               while (i-- > 0)
                        printf("    ");
                printf("%s", str);
        }
                        printf("    ");
                printf("%s", str);
        }
@@ -1583,10 +1583,11 @@ propagation is needed.
                        fputs(" (labels not permitted)", stderr);
                break;
 
                        fputs(" (labels not permitted)", stderr);
                break;
 
-###### core functions
-
+###### forward decls
        static struct type *propagate_types(struct exec *prog, struct parse_context *c, int *ok,
                                            struct type *type, int rules);
        static struct type *propagate_types(struct exec *prog, struct parse_context *c, int *ok,
                                            struct type *type, int rules);
+###### core functions
+
        static struct type *__propagate_types(struct exec *prog, struct parse_context *c, int *ok,
                                              struct type *type, int rules)
        {
        static struct type *__propagate_types(struct exec *prog, struct parse_context *c, int *ok,
                                              struct type *type, int rules)
        {
@@ -1817,7 +1818,7 @@ with a const size by whether they are prepared at parse time or not.
        static int array_compat(struct type *require, struct type *have)
        {
                if (have->compat != require->compat)
        static int array_compat(struct type *require, struct type *have)
        {
                if (have->compat != require->compat)
-                       return 0;       // UNTESTED
+                       return 0;
                /* Both are arrays, so we can look at details */
                if (!type_compat(require->array.member, have->array.member, 0))
                        return 0;
                /* Both are arrays, so we can look at details */
                if (!type_compat(require->array.member, have->array.member, 0))
                        return 0;
@@ -2316,7 +2317,7 @@ function will be needed.
                while (target != 0) {
                        int i = 0;
                        for (t = context.typelist; t ; t=t->next)
                while (target != 0) {
                        int i = 0;
                        for (t = context.typelist; t ; t=t->next)
-                               if (t->print_type_decl) {
+                               if (t->print_type_decl && !t->check_args) {
                                        i += 1;
                                        if (i == target)
                                                break;
                                        i += 1;
                                        if (i == target)
                                                break;
@@ -2331,16 +2332,13 @@ function will be needed.
                }
        }
 
                }
        }
 
-### Functions
-
-A function is a named chunk of code which can be passed parameters and
-can return results.  Each function has an implicit type which includes
-the set of parameters and the return value.  As yet these types cannot
-be declared separate from the function itself.
+#### Functions
 
 
-In fact, only one function is currently possible - `main`.  `main` is
-passed an array of strings together with the size of the array, and
-doesn't return anything.  The strings are command line arguments.
+A function is a chunk of code which can be passed parameters and can
+return results (though results are not yet implemented).  Each function
+has a type which includes the set of parameters and the return value.
+As yet these types cannot be declared separately from the function
+itself.
 
 The parameters can be specified either in parentheses as a list, such as
 
 
 The parameters can be specified either in parentheses as a list, such as
 
@@ -2361,39 +2359,126 @@ or as an indented list of one parameter per line
 For constructing these lists we use a `List` binode, which will be
 further detailed when Expression Lists are introduced.
 
 For constructing these lists we use a `List` binode, which will be
 further detailed when Expression Lists are introduced.
 
+###### type union fields
+
+       struct {
+               struct binode *params;
+               int local_size;
+       } function;
+
+###### value union fields
+       struct exec *function;
+
+###### type functions
+       void (*check_args)(struct parse_context *c, int *ok,
+                          struct type *require, struct exec *args);
+
+###### value functions
+
+       static void function_free(struct type *type, struct value *val)
+       {
+               free_exec(val->function);
+               val->function = NULL;
+       }
+
+       static int function_compat(struct type *require, struct type *have)
+       {
+               // FIXME can I do anything here yet?
+               return 0;
+       }
+
+       static void function_check_args(struct parse_context *c, int *ok,
+                                       struct type *require, struct exec *args)
+       {
+               /* This should be 'compat', but we don't have a 'tuple' type to
+                * hold the type of 'args'
+                */
+               struct binode *arg = cast(binode, args);
+               struct binode *param = require->function.params;
+
+               while (param) {
+                       struct var *pv = cast(var, param->left);
+                       if (!arg) {
+                               type_err(c, "error: insufficient arguments to function.",
+                                        args, NULL, 0, NULL);
+                               break;
+                       }
+                       *ok = 1;
+                       propagate_types(arg->left, c, ok, pv->var->type, 0);
+                       param = cast(binode, param->right);
+                       arg = cast(binode, arg->right);
+               }
+               if (arg)
+                       type_err(c, "error: too many arguments to function.",
+                                args, NULL, 0, NULL);
+       }
+
+       static void function_print(struct type *type, struct value *val)
+       {
+               print_exec(val->function, 1, 0);
+       }
+
+       static void function_print_type_decl(struct type *type, FILE *f)
+       {
+               struct binode *b;
+               fprintf(f, "(");
+               for (b = type->function.params; b; b = cast(binode, b->right)) {
+                       struct variable *v = cast(var, b->left)->var;
+                       fprintf(f, "%.*s%s", v->name->name.len, v->name->name.txt,
+                               v->constant ? "::" : ":");
+                       type_print(v->type, f);
+                       if (b->right)
+                               fprintf(f, "; ");
+               }
+               fprintf(f, ")\n");
+       }
+
+       static void function_free_type(struct type *t)
+       {
+               free_exec(t->function.params);
+       }
+
+       static struct type function_prototype = {
+               .size = sizeof(void*),
+               .align = sizeof(void*),
+               .free = function_free,
+               .compat = function_compat,
+               .check_args = function_check_args,
+               .print = function_print,
+               .print_type_decl = function_print_type_decl,
+               .free_type = function_free_type,
+       };
+
+###### declare terminals
+
+       $TERM func
+
 ###### Binode types
 ###### Binode types
-       Func, List,
+       List,
 
 ###### Grammar
 
 
 ###### Grammar
 
-       $TERM func main
+       $*variable
+       FuncName -> IDENTIFIER ${ {
+                       struct variable *v = var_decl(c, $1.txt);
+                       struct var *e = new_pos(var, $1);
+                       e->var = v;
+                       if (v) {
+                               v->where_decl = e;
+                               $0 = v;
+                       } else {
+                               v = var_ref(c, $1.txt);
+                               e->var = v;
+                               type_err(c, "error: function '%v' redeclared",
+                                       e, NULL, 0, NULL);
+                               type_err(c, "info: this is where '%v' was first declared",
+                                       v->where_decl, NULL, 0, NULL);
+                               free_exec(e);
+                       }
+               } }$
 
 
-       $*binode
-       MainFunction -> func main ( OpenScope Args ) Block Newlines ${
-                       $0 = new(binode);
-                       $0->op = Func;
-                       $0->left = reorder_bilist($<Ar);
-                       $0->right = $<Bl;
-                       var_block_close(c, CloseSequential, $0);
-                       if (c->scope_stack && !c->parse_error) abort();
-               }$
-               | func main IN OpenScope OptNL Args OUT OptNL do Block Newlines ${
-                       $0 = new(binode);
-                       $0->op = Func;
-                       $0->left = reorder_bilist($<Ar);
-                       $0->right = $<Bl;
-                       var_block_close(c, CloseSequential, $0);
-                       if (c->scope_stack && !c->parse_error) abort();
-               }$
-               | func main NEWLINE OpenScope OptNL do Block Newlines ${
-                       $0 = new(binode);
-                       $0->op = Func;
-                       $0->left = NULL;
-                       $0->right = $<Bl;
-                       var_block_close(c, CloseSequential, $0);
-                       if (c->scope_stack && !c->parse_error) abort();
-               }$
 
 
+       $*binode
        Args -> ${ $0 = NULL; }$
                | Varlist ${ $0 = $<1; }$
                | Varlist ; ${ $0 = $<1; }$
        Args -> ${ $0 = NULL; }$
                | Varlist ${ $0 = $<1; }$
                | Varlist ; ${ $0 = $<1; }$
@@ -2684,7 +2769,7 @@ link to find the primary instance.
                        } else
                                fputs("???", stderr);   // NOTEST
                } else
                        } else
                                fputs("???", stderr);   // NOTEST
                } else
-                       fputs("NOTVAR", stderr);        // NOTEST
+                       fputs("NOTVAR", stderr);
                break;
 
 ###### propagate exec cases
                break;
 
 ###### propagate exec cases
@@ -3128,7 +3213,7 @@ expression operator, and the `CMPop` non-terminal will match one of them.
                break;
        }
 
                break;
        }
 
-### Expressions: The rest
+### Expressions: Arithmetic etc.
 
 The remaining expressions with the highest precedence are arithmetic,
 string concatenation, and string conversion.  String concatenation
 
 The remaining expressions with the highest precedence are arithmetic,
 string concatenation, and string conversion.  String concatenation
@@ -3192,6 +3277,8 @@ should only insert brackets were needed for precedence.
                | Value ${ $0 = $<1; }$
                | Variable ${ $0 = $<1; }$
 
                | Value ${ $0 = $<1; }$
                | Variable ${ $0 = $<1; }$
 
+###### Grammar
+
        $eop
        Eop ->    + ${ $0.op = Plus; }$
                | - ${ $0.op = Minus; }$
        $eop
        Eop ->    + ${ $0.op = Plus; }$
                | - ${ $0.op = Minus; }$
@@ -3372,6 +3459,110 @@ should only insert brackets were needed for precedence.
                return rv;
        }
 
                return rv;
        }
 
+### Function calls
+
+A function call can appear either as an expression or as a statement.
+As functions cannot yet return values, only the statement version will work.
+We use a new 'Funcall' binode type to link the function with a list of
+arguments, form with the 'List' nodes.
+
+###### Binode types
+       Funcall,
+
+###### expression grammar
+       | Variable ( ExpressionList ) ${ {
+               struct binode *b = new(binode);
+               b->op = Funcall;
+               b->left = $<V;
+               b->right = reorder_bilist($<EL);
+               $0 = b;
+       } }$
+       | Variable ( ) ${ {
+               struct binode *b = new(binode);
+               b->op = Funcall;
+               b->left = $<V;
+               b->right = NULL;
+               $0 = b;
+       } }$
+
+###### SimpleStatement Grammar
+
+       | Variable ( ExpressionList ) ${ {
+               struct binode *b = new(binode);
+               b->op = Funcall;
+               b->left = $<V;
+               b->right = reorder_bilist($<EL);
+               $0 = b;
+       } }$
+
+###### print binode cases
+
+       case Funcall:
+               do_indent(indent, "");
+               print_exec(b->left, -1, bracket);
+               printf("(");
+               for (b = cast(binode, b->right); b; b = cast(binode, b->right)) {
+                       if (b->left) {
+                               printf(" ");
+                               print_exec(b->left, -1, bracket);
+                               if (b->right)
+                                       printf(",");
+                       }
+               }
+               printf(")");
+               if (indent >= 0)
+                       printf("\n");
+               break;
+
+###### propagate binode cases
+
+       case Funcall: {
+               /* Every arg must match formal parameter, and result
+                * is return type of function (currently Tnone).
+                */
+               struct binode *args = cast(binode, b->right);
+               struct var *v = cast(var, b->left);
+
+               if (!v->var->type || v->var->type->check_args == NULL) {
+                       type_err(c, "error: attempt to call a non-function.",
+                                prog, NULL, 0, NULL);
+                       return NULL;
+               }
+               v->var->type->check_args(c, ok, v->var->type, args);
+               return Tnone;
+       }
+
+###### interp binode cases
+
+       case Funcall: {
+               struct var *v = cast(var, b->left);
+               struct type *t = v->var->type;
+               void *oldlocal = c->local;
+               int old_size = c->local_size;
+               void *local = calloc(1, t->function.local_size);
+               struct value *fbody = var_value(c, v->var);
+               struct binode *arg = cast(binode, b->right);
+               struct binode *param = t->function.params;
+
+               while (param) {
+                       struct var *pv = cast(var, param->left);
+                       struct type *vtype = NULL;
+                       struct value val = interp_exec(c, arg->left, &vtype);
+                       struct value *lval;
+                       c->local = local; c->local_size = t->function.local_size;
+                       lval = var_value(c, pv->var);
+                       c->local = oldlocal; c->local_size = old_size;
+                       memcpy(lval, &val, vtype->size);
+                       param = cast(binode, param->right);
+                       arg = cast(binode, arg->right);
+               }
+               c->local = local; c->local_size = t->function.local_size;
+               right = interp_exec(c, fbody->function, &rtype);
+               c->local = oldlocal; c->local_size = old_size;
+               free(local);
+               break;
+       }
+
 ### Blocks, Statements, and Statement lists.
 
 Now that we have expressions out of the way we need to turn to
 ### Blocks, Statements, and Statement lists.
 
 Now that we have expressions out of the way we need to turn to
@@ -4473,7 +4664,7 @@ searching through for the Nth constant for decreasing N.
                while (target != 0) {
                        int i = 0;
                        for (v = context.in_scope; v; v=v->in_scope)
                while (target != 0) {
                        int i = 0;
                        for (v = context.in_scope; v; v=v->in_scope)
-                               if (v->depth == 0) {
+                               if (v->depth == 0 && v->constant) {
                                        i += 1;
                                        if (i == target)
                                                break;
                                        i += 1;
                                        if (i == target)
                                                break;
@@ -4499,11 +4690,12 @@ searching through for the Nth constant for decreasing N.
                }
        }
 
                }
        }
 
-### Finally the whole `main` function.
+### Function declarations
+
+The code in an Ocean program is all stored in function declarations.
+One of the functions must be named `main` and it must accept an array of
+strings as a parameter - the command line arguments.
 
 
-An Ocean program can currently have only one function - `main` - and
-that must exist.  It expects an array of strings with a provided size.
-Following this is a `block` which is the code to execute.
 
 As this is the top level, several things are handled a bit
 differently.
 
 As this is the top level, several things are handled a bit
 differently.
@@ -4513,55 +4705,112 @@ analysis is a bit more interesting at this level.
 
 ###### top level grammar
 
 
 ###### top level grammar
 
-       DeclareFunction -> MainFunction ${ {
-               if (c->prog)
-                       type_err(c, "\"main\" defined a second time",
-                                $1, NULL, 0, NULL);
-               else
-                       c->prog = $<1;
-       } }$
+       $*variable
+       DeclareFunction -> func FuncName ( OpenScope Args ) Block Newlines ${ {
+                       struct text funcname = { " func", 5};
+                       $0 = $<FN;
+                       if ($0) {
+                               struct value fn = {.function = $<Bl};
+                               $0->type = add_type(c, funcname, &function_prototype);
+                               $0->type->function.params = reorder_bilist($<Ar);
+                               global_alloc(c, $0->type, $0, &fn);
+                               var_block_close(c, CloseSequential, fn.function);
+                       } else
+                               var_block_close(c, CloseSequential, NULL);
+               } }$
+               | func FuncName then IN OpenScope OptNL Args OUT OptNL do Block Newlines ${ {
+                       // FIXME that 'then' should not be there.
+                       struct text funcname = { " func", 5};
+                       $0 = $<FN;
+                       if ($0) {
+                               struct value fn = {.function = $<Bl};
+                               $0->type = add_type(c, funcname, &function_prototype);
+                               $0->type->function.params = reorder_bilist($<Ar);
+                               global_alloc(c, $0->type, $0, &fn);
+                               var_block_close(c, CloseSequential, fn.function);
+                       } else
+                               var_block_close(c, CloseSequential, NULL);
+               } }$
+               | func FuncName NEWLINE OpenScope OptNL do Block Newlines ${ {
+                       struct text funcname = { " func", 5};
+                       $0 = $<FN;
+                       if ($0) {
+                               struct value fn = {.function = $<Bl};
+                               $0->type = add_type(c, funcname, &function_prototype);
+                               $0->type->function.params = NULL;
+                               global_alloc(c, $0->type, $0, &fn);
+                               var_block_close(c, CloseSequential, fn.function);
+                       } else
+                               var_block_close(c, CloseSequential, NULL);
+               } }$
 
 
-###### print binode cases
-       case Func:
-               do_indent(indent, "func main(");
-               for (b2 = cast(binode, b->left); b2; b2 = cast(binode, b2->right)) {
-                       struct variable *v = cast(var, b2->left)->var;
-                       printf(" ");
-                       print_exec(b2->left, 0, 0);
-                       printf(":");
-                       type_print(v->type, stdout);
-               }
-               if (bracket)
-                       printf(") {\n");
-               else
-                       printf(")\n");
-               print_exec(b->right, indent+1, bracket);
-               if (bracket)
-                       do_indent(indent, "}\n");
-               break;
+###### print func decls
+       {
+               struct variable *v;
+               int target = -1;
 
 
-###### propagate binode cases
-       case Func: abort();             // NOTEST
+               while (target != 0) {
+                       int i = 0;
+                       for (v = context.in_scope; v; v=v->in_scope)
+                               if (v->depth == 0 && v->type && v->type->check_args) {
+                                       i += 1;
+                                       if (i == target)
+                                               break;
+                               }
+
+                       if (target == -1) {
+                               target = i;
+                       } else {
+                               struct value *val = var_value(&context, v);
+                               printf("func %.*s", v->name->name.len, v->name->name.txt);
+                               v->type->print_type_decl(v->type, stdout);
+                               if (brackets)
+                                       print_exec(val->function, 0, brackets);
+                               else
+                                       print_value(v->type, val);
+                               printf("/* frame size %d */\n", v->type->function.local_size);
+                               target -= 1;
+                       }
+               }
+       }
 
 ###### core functions
 
 
 ###### core functions
 
-       static int analyse_prog(struct exec *prog, struct parse_context *c)
+       static int analyse_funcs(struct parse_context *c)
        {
        {
-               struct binode *bp = cast(binode, prog);
+               struct variable *v;
+               int ok = 1;
+               for (v = c->in_scope; ok && v; v = v->in_scope) {
+                       struct value *val;
+                       if (v->depth != 0 || !v->type || !v->type->check_args)
+                               continue;
+                       val = var_value(c, v);
+                       do {
+                               ok = 1;
+                               propagate_types(val->function, c, &ok, Tnone, 0);
+                       } while (ok == 2);
+                       if (ok)
+                               /* Make sure everything is still consistent */
+                               propagate_types(val->function, c, &ok, Tnone, 0);
+                       v->type->function.local_size = scope_finalize(c);
+               }
+               return ok;
+       }
+
+       static int analyse_main(struct type *type, struct parse_context *c)
+       {
+               struct binode *bp = type->function.params;
                struct binode *b;
                int ok = 1;
                int arg = 0;
                struct type *argv_type;
                struct text argv_type_name = { " argv", 5 };
 
                struct binode *b;
                int ok = 1;
                int arg = 0;
                struct type *argv_type;
                struct text argv_type_name = { " argv", 5 };
 
-               if (!bp)
-                       return 0;       // NOTEST
-
                argv_type = add_type(c, argv_type_name, &array_prototype);
                argv_type->array.member = Tstr;
                argv_type->array.unspec = 1;
 
                argv_type = add_type(c, argv_type_name, &array_prototype);
                argv_type->array.member = Tstr;
                argv_type->array.unspec = 1;
 
-               for (b = cast(binode, bp->left); b; b = cast(binode, b->right)) {
+               for (b = bp; b; b = cast(binode, b->right)) {
                        ok = 1;
                        switch (arg++) {
                        case 0: /* argv */
                        ok = 1;
                        switch (arg++) {
                        case 0: /* argv */
@@ -4570,35 +4819,40 @@ analysis is a bit more interesting at this level.
                        default: /* invalid */  // NOTEST
                                propagate_types(b->left, c, &ok, Tnone, 0);     // NOTEST
                        }
                        default: /* invalid */  // NOTEST
                                propagate_types(b->left, c, &ok, Tnone, 0);     // NOTEST
                        }
+                       if (!ok)
+                               c->parse_error = 1;
                }
 
                }
 
-               do {
-                       ok = 1;
-                       propagate_types(bp->right, c, &ok, Tnone, 0);
-               } while (ok == 2);
-               if (!ok)
-                       return 0;
-
-               /* Make sure everything is still consistent */
-               propagate_types(bp->right, c, &ok, Tnone, 0);
-               if (!ok)
-                       return 0;       // UNTESTED
-               scope_finalize(c);
-               return 1;
+               return !c->parse_error;
        }
 
        }
 
-       static void interp_prog(struct parse_context *c, struct exec *prog, 
-                               int argc, char **argv)
+       static void interp_main(struct parse_context *c, int argc, char **argv)
        {
        {
-               struct binode *p = cast(binode, prog);
+               struct value *progp = NULL;
+               struct text main_name = { "main", 4 };
+               struct variable *mainv;
                struct binode *al;
                int anum = 0;
                struct value v;
                struct type *vtype;
 
                struct binode *al;
                int anum = 0;
                struct value v;
                struct type *vtype;
 
-               if (!prog)
-                       return;         // NOTEST
-               al = cast(binode, p->left);
+               mainv = var_ref(c, main_name);
+               if (mainv)
+                       progp = var_value(c, mainv);
+               if (!progp || !progp->function) {
+                       fprintf(stderr, "oceani: no main function found.\n");
+                       c->parse_error = 1;
+                       return;
+               }
+               if (!analyse_main(mainv->type, c)) {
+                       fprintf(stderr, "oceani: main has wrong type.\n");
+                       c->parse_error = 1;
+                       return;
+               }
+               al = mainv->type->function.params;
+
+               c->local_size = mainv->type->function.local_size;
+               c->local = calloc(1, c->local_size);
                while (al) {
                        struct var *v = cast(var, al->left);
                        struct value *vl = var_value(c, v->var);
                while (al) {
                        struct var *v = cast(var, al->left);
                        struct value *vl = var_value(c, v->var);
@@ -4628,14 +4882,16 @@ analysis is a bit more interesting at this level.
                        }
                        al = cast(binode, al->right);
                }
                        }
                        al = cast(binode, al->right);
                }
-               v = interp_exec(c, p, &vtype);
+               v = interp_exec(c, progp->function, &vtype);
                free_value(vtype, &v);
                free_value(vtype, &v);
+               free(c->local);
+               c->local = NULL;
        }
 
        }
 
-###### interp binode cases
-       case Func:
-               rv = interp_exec(c, b->right, &rvtype);
-               break;
+###### ast functions
+       void free_variable(struct variable *v)
+       {
+       }
 
 ## And now to test it out.
 
 
 ## And now to test it out.
 
@@ -4665,9 +4921,7 @@ things which will likely grow as the languages grows.
                name:string
                alive:Boolean
 
                name:string
                alive:Boolean
 
-       func main
-               argv:[argc::]string
-       do
+       func main(argv:[argc::]string)
                print "Hello World, what lovely oceans you have!"
                print "Are there", five, "?"
                print pi, pie, "but", cake
                print "Hello World, what lovely oceans you have!"
                print "Are there", five, "?"
                print pi, pie, "but", cake