From 17b2c8c16fd6dadf052fefc20e2ee8c7f8c3da0b Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Mon, 8 Nov 2021 08:21:30 +1100 Subject: [PATCH] oceani: Add functions 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 --- csrc/oceani-tests.mdc | 115 ++++++++- csrc/oceani.mdc | 526 +++++++++++++++++++++++++++++++----------- 2 files changed, 499 insertions(+), 142 deletions(-) diff --git a/csrc/oceani-tests.mdc b/csrc/oceani-tests.mdc index 5d11197..d9be788 100644 --- a/csrc/oceani-tests.mdc +++ b/csrc/oceani-tests.mdc @@ -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); \ - if (ran < (ran + skip) *0.96) exit(1) }' \ + if (ran < (ran + skip) *0.968) exit(1) }' \ coverage/oceani.mdc.gcov @rm -f .tmp* @@ -110,9 +110,7 @@ calculations on them. ###### 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 ab, a>=b, a= 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 @@ -833,7 +867,7 @@ various places that `type_err()` are called. 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 @@ -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: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: "main" defined a second time ###### test: type_err_const1 const @@ -879,6 +914,74 @@ various places that `type_err()` are called. ###### 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 diff --git a/csrc/oceani.mdc b/csrc/oceani.mdc index 3aa170b..f257736 100644 --- a/csrc/oceani.mdc +++ b/csrc/oceani.mdc @@ -114,7 +114,6 @@ structures can be used. struct token_config config; char *file_name; int parse_error; - struct exec *prog; ## parse context }; @@ -243,24 +242,18 @@ structures can be used. 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; } - 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_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; @@ -268,7 +261,9 @@ structures can be used. 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); @@ -365,6 +360,9 @@ context so indicate that parsing failed. ###### 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 @@ -1212,7 +1210,7 @@ all pending-scope variables become conditionally scoped. */ 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; @@ -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)) - 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; @@ -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 -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 - static void scope_finalize(struct parse_context *c) + static int scope_finalize(struct parse_context *c) { struct binding *b; + int size = 0; 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; - 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.local); ### 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 - return 0; // NOTEST + return 0; } 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, @@ -1520,7 +1520,7 @@ also want to know what sort of bracketing to use. static void do_indent(int i, char *str) { - while (i--) + while (i-- > 0) printf(" "); printf("%s", str); } @@ -1583,10 +1583,11 @@ propagation is needed. 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); +###### core functions + 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) - 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; @@ -2316,7 +2317,7 @@ function will be needed. 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; @@ -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 @@ -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. +###### 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 - Func, List, + List, ###### 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($right = $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($right = $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 = $scope_stack && !c->parse_error) abort(); - }$ + $*binode Args -> ${ $0 = NULL; }$ | Varlist ${ $0 = $<1; }$ | Varlist ; ${ $0 = $<1; }$ @@ -2684,7 +2769,7 @@ link to find the primary instance. } else fputs("???", stderr); // NOTEST } else - fputs("NOTVAR", stderr); // NOTEST + fputs("NOTVAR", stderr); break; ###### propagate exec cases @@ -3128,7 +3213,7 @@ expression operator, and the `CMPop` non-terminal will match one of them. break; } -### Expressions: The rest +### Expressions: Arithmetic etc. 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; }$ +###### Grammar + $eop Eop -> + ${ $0.op = Plus; }$ | - ${ $0.op = Minus; }$ @@ -3372,6 +3459,110 @@ should only insert brackets were needed for precedence. 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 = $right = reorder_bilist($op = Funcall; + b->left = $right = NULL; + $0 = b; + } }$ + +###### SimpleStatement Grammar + + | Variable ( ExpressionList ) ${ { + struct binode *b = new(binode); + b->op = Funcall; + b->left = $right = reorder_bilist($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 @@ -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) - if (v->depth == 0) { + if (v->depth == 0 && v->constant) { 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. @@ -4513,55 +4705,112 @@ analysis is a bit more interesting at this level. ###### 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 = $type = add_type(c, funcname, &function_prototype); + $0->type->function.params = reorder_bilist($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 = $type = add_type(c, funcname, &function_prototype); + $0->type->function.params = reorder_bilist($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 = $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 - 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 }; - 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; - 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 */ @@ -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 } + 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; - 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); @@ -4628,14 +4882,16 @@ analysis is a bit more interesting at this level. } al = cast(binode, al->right); } - v = interp_exec(c, p, &vtype); + v = interp_exec(c, progp->function, &vtype); 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. @@ -4665,9 +4921,7 @@ things which will likely grow as the languages grows. 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 -- 2.43.0