From 5a438bb96ff5bd82e67312b79f593ecfcbf32529 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Wed, 3 Nov 2021 11:46:54 +1100 Subject: [PATCH] oceani: drop "program" in favour of "func" Being to introduce functions. Currently only the 'main' function which receives an array of strings. This array has a variable size. Signed-off-by: NeilBrown --- csrc/oceani-tests.mdc | 41 +++--- csrc/oceani.mdc | 289 ++++++++++++++++++++++++++++-------------- 2 files changed, 219 insertions(+), 111 deletions(-) diff --git a/csrc/oceani-tests.mdc b/csrc/oceani-tests.mdc index 11c3f19..881fee7 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 @ awk '/NOTEST/ { next } /^ *[1-9]/ {ran+=1} /^ *###/ {skip+=1} \ END {printf "coverage: %6.2f%%\n", ran * 100 / (ran + skip); \ - if (ran < (ran + skip) *0.9495) exit(1) }' \ + if (ran < (ran + skip) *0.944) exit(1) }' \ coverage/oceani.mdc.gcov @rm -f .tmp* @@ -110,7 +110,9 @@ calculations on them. ###### test: valvar - program: + func main + argv:[argc::]string + do a := 23; b:=12 ; b1 := -b print a, b, a+b, a-b, a*b, a/b, a%b print ab, a>=b, acode, &context.config, dotrace ? stderr : NULL); if (!context.prog) { - fprintf(stderr, "oceani: no program found.\n"); + fprintf(stderr, "oceani: no main function found.\n"); context.parse_error = 1; } if (context.prog && doprint) { @@ -557,6 +560,30 @@ Named type are stored in a simple linked list. Objects of each type are free(t); } +Type can be specified for local variables, for fields in a structure, +for formal parameters to functions, and possibly elsewhere. Different +rules may apply in different contexts. As a minimum, a named type may +always be used. Currently the type of a formal parameter can be +different from types in other contexts, so we have a separate grammar +symbol for those. + +###### Grammar + + $*type + Type -> IDENTIFIER ${ + $0 = find_type(c, $1.txt); + if (!$0) { + tok_err(c, + "error: undefined type", &$1); + + $0 = Tnone; + } + }$ + ## type grammar + + FormalType -> Type ${ $0 = $<1; }$ + ## formal type grammar + #### Base Types Values of the base types can be numbers, which we represent as @@ -1500,8 +1527,8 @@ propagation is needed. Interpreting an `exec` doesn't require anything but the `exec`. State is stored in variables and each variable will be directly linked from -within the `exec` tree. The exception to this is the whole `program` -which needs to look at command line arguments. The `program` will be +within the `exec` tree. The exception to this is the `main` function +which needs to look at command line arguments. This function will be interpreted separately. Each `exec` can return a value combined with a type in `struct lrval`. @@ -1510,7 +1537,6 @@ the location of a value, which can be updated, in `lval`. Others will set `lval` to NULL indicating that there is a value of appropriate type in `rval`. - ###### core functions struct lrval { @@ -1595,6 +1621,12 @@ Arrays can be declared by giving a size and a type, as `[size]type' so size can be either a literal number, or a named constant. Some day an arbitrary expression will be supported. +As a formal parameter to a function, the array can be declared with a +new variable as the size: `name:[size::number]string`. The `size` +variable is set to the size of the array and must be a constant. As +`number` is the only supported type, it can be left out: +`name:[size::]string`. + Arrays cannot be assigned. When pointers are introduced we will also introduce array slices which can refer to part or all of an array - the assignment syntax will create a slice. For now, an array can only @@ -1617,6 +1649,7 @@ with a const size by whether they are prepared at parse time or not. ###### type union fields struct { + int unspec; // size is unspecified - vsize must be set. short size; short static_size; struct variable *vsize; @@ -1691,6 +1724,15 @@ with a const size by whether they are prepared at parse time or not. /* Both are arrays, so we can look at details */ if (!type_compat(require->array.member, have->array.member, 0)) return 0; + if (have->array.unspec && require->array.unspec) { + if (have->array.vsize && require->array.vsize && + have->array.vsize != require->array.vsize) + /* sizes might not be the same */ + return 0; + return 1; + } + if (have->array.unspec || require->array.unspec) + return 1; if (require->array.vsize == NULL && have->array.vsize == NULL) return require->array.size == have->array.size; @@ -1702,7 +1744,8 @@ with a const size by whether they are prepared at parse time or not. fputs("[", f); if (type->array.vsize) { struct binding *b = type->array.vsize->name; - fprintf(f, "%.*s]", b->name.len, b->name.txt); + fprintf(f, "%.*s%s]", b->name.len, b->name.txt, + type->array.unspec ? "::" : ""); } else fprintf(f, "%d]", type->array.size); type_print(type->array.member, f); @@ -1766,6 +1809,28 @@ with a const size by whether they are prepared at parse time or not. $0->array.vsize = v; } }$ +###### Grammar + $*type + OptType -> Type ${ $0 = $<1; }$ + | ${ $0 = NULL; }$ + +###### formal type grammar + + | [ IDENTIFIER :: OptType ] Type ${ { + struct variable *v = var_decl(c, $ID.txt); + struct text noname = { "", 0 }; + + v->type = $constant = 1; + if (!v->type) + v->type = Tnum; + $0 = add_type(c, noname, &array_prototype); + $0->array.member = $<6; + $0->array.size = 0; + $0->array.unspec = 1; + $0->array.vsize = v; + } }$ + ###### Binode types Index, @@ -2169,6 +2234,92 @@ 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. + +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. + +The parameters can be specified either in parentheses as a list, such as + +##### Example: function 1 + + func main(av:[ac::number]string) + code block + +or as an indented list of one parameter per line + +##### Example: function 2 + + func main + argv:[argc::number]string + do + code block + +###### Binode types + Func, List, + +###### Grammar + + $TERM func main + + $*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(); + }$ + + Args -> ${ $0 = NULL; }$ + | Varlist ${ $0 = $<1; }$ + | Varlist ; ${ $0 = $<1; }$ + | Varlist NEWLINE ${ $0 = $<1; }$ + + Varlist -> Varlist ; ArgDecl ${ + $0 = new(binode); + $0->op = List; + $0->left = $right = $op = List; + $0->left = NULL; + $0->right = $ IDENTIFIER : FormalType ${ { + struct variable *v = var_decl(c, $1.txt); + $0 = new(var); + $0->var = v; + v->type = $ IDENTIFIER ${ - $0 = find_type(c, $1.txt); - if (!$0) { - tok_err(c, - "error: undefined type", &$1); - - $0 = Tnone; - } - }$ - ## type grammar - ###### print exec cases case Xvar: { @@ -2437,7 +2576,7 @@ link to find the primary instance. ###### format cases case 'v': - if (loc->type == Xvar) { + if (loc && loc->type == Xvar) { struct var *v = cast(var, loc); if (v->var) { struct binding *b = v->var->name; @@ -3700,7 +3839,6 @@ defined. // may or may not end with EOL // WhilePart and IfPart include an appropriate Suffix - // Both ForPart and Whilepart open scopes, and CondSuffix only // closes one - so in the first branch here we have another to close. CondStatement -> ForPart OptNL ThenPart OptNL WhilePart CondSuffix ${ @@ -4054,8 +4192,8 @@ At the top level of a file there will be a number of declarations. Many of the things that can be declared haven't been described yet, such as functions, procedures, imports, and probably more. For now there are two sorts of things that can appear at the top -level. They are predefined constants, `struct` types, and the main -program. While the syntax will allow the main program to appear +level. They are predefined constants, `struct` types, and the `main` +function. While the syntax will allow the `main` function to appear multiple times, that will trigger an error if it is actually attempted. The various declarations do not return anything. They store the @@ -4081,11 +4219,13 @@ various declarations in the parse context. "error: unhandled parse error", &$1); }$ | DeclareConstant - | DeclareProgram + | DeclareFunction | DeclareStruct ## top level grammar + ## Grammar + ### The `const` section As well as being defined in with the code that uses them, constants @@ -4193,80 +4333,51 @@ searching through for the Nth constant for decreasing N. } } -### Finally the whole program. +### Finally the whole `main` function. -Somewhat reminiscent of Pascal a (current) Ocean program starts with -the keyword "program" and a list of variable names which are assigned -values from command line arguments. Following this is a `block` which -is the code to execute. Unlike Pascal, constants and other -declarations come *before* the program. +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. -The whole program is not interpreted by `interp_exec` as that isn't +The function is not interpreted by `interp_exec` as that isn't passed the argument list which the program requires. Similarly type analysis is a bit more interesting at this level. -###### Binode types - Program, - ###### top level grammar - DeclareProgram -> Program ${ { + DeclareFunction -> MainFunction ${ { if (c->prog) - type_err(c, "Program defined a second time", + type_err(c, "\"main\" defined a second time", $1, NULL, 0, NULL); else c->prog = $<1; } }$ - $TERM program - - $*binode - Program -> program OpenScope Varlist ColonBlock Newlines ${ - $0 = new(binode); - $0->op = Program; - $0->left = reorder_bilist($right = $scope_stack && !c->parse_error) abort(); - }$ - - Varlist -> Varlist ArgDecl ${ - $0 = new(binode); - $0->op = Program; - $0->left = $<1; - $0->right = $<2; - }$ - | ${ $0 = NULL; }$ - - $*var - ArgDecl -> IDENTIFIER ${ { - struct variable *v = var_decl(c, $1.txt); - $0 = new(var); - $0->var = v; - } }$ - - ## Grammar - ###### print binode cases - case Program: - do_indent(indent, "program"); + case Func: + case List: + 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"); + printf(") {\n"); else - printf(":\n"); + printf(")\n"); print_exec(b->right, indent+1, bracket); if (bracket) do_indent(indent, "}\n"); break; ###### propagate binode cases - case Program: abort(); // NOTEST + case List: + case Func: abort(); // NOTEST ###### core functions @@ -4284,21 +4395,16 @@ analysis is a bit more interesting at this level. 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)) { - struct var *v; ok = 1; switch (arg++) { - case 0: /* argc */ - v = cast(var, b->left); - argv_type->array.vsize = v->var; - propagate_types(b->left, c, &ok, Tnum, 0); - break; - case 1: /* argv */ + case 0: /* argv */ propagate_types(b->left, c, &ok, argv_type, 0); break; - default: /* invalid */ - propagate_types(b->left, c, &ok, Tnone, 0); + default: /* invalid */ // NOTEST + propagate_types(b->left, c, &ok, Tnone, 0); // NOTEST } } @@ -4338,15 +4444,11 @@ analysis is a bit more interesting at this level. int i; switch (anum++) { - case 0: /* argc */ - if (v->var->type == Tnum) { - mpq_init(argcq); - mpq_set_ui(argcq, argc, 1); - memcpy(vl, &argcq, sizeof(argcq)); - } - break; - case 1: /* argv */ + case 0: /* argv */ t = v->var->type; + mpq_init(argcq); + mpq_set_ui(argcq, argc, 1); + memcpy(var_value(c, t->array.vsize), &argcq, sizeof(argcq)); t->prepare_type(c, t, 0); array_init(v->var->type, vl); for (i = 0; i < argc; i++) { @@ -4367,7 +4469,8 @@ analysis is a bit more interesting at this level. } ###### interp binode cases - case Program: abort(); // NOTEST + case List: + case Func: abort(); // NOTEST ## And now to test it out. @@ -4397,7 +4500,9 @@ things which will likely grow as the languages grows. name:string alive:Boolean - program argc argv: + func main + argv:[argc::]string + do print "Hello World, what lovely oceans you have!" print "Are there", five, "?" print pi, pie, "but", cake -- 2.43.0