]> ocean-lang.org Git - ocean/commitdiff
oceani: drop "program" in favour of "func"
authorNeilBrown <neil@brown.name>
Wed, 3 Nov 2021 00:46:54 +0000 (11:46 +1100)
committerNeilBrown <neil@brown.name>
Mon, 8 Nov 2021 09:56:36 +0000 (20:56 +1100)
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 <neil@brown.name>
csrc/oceani-tests.mdc
csrc/oceani.mdc

index 11c3f19ffcd93a4c8760d9cbe85a3505befd46ae..881fee7229e7642869bca66b3cc79c9d808ad63d 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
                @ 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 a<b, a<=b, a>b, a>=b, a<a, a==b, a==a
@@ -147,7 +149,7 @@ Next we change the value of variables
 
 ###### test: setvar
 
-       program:
+       func main()
                a := 4
                a = a * a
                a = (a + a) * (a + a)
@@ -170,7 +172,7 @@ Now some contants
                cake ::= "The cake is"
                  ++ " a lie"
 
-       program:
+       func main()
                print "Hello World, what lovely oceans you have!"
                print "are there", five, "?"
                print pi, pie, "but", cake
@@ -187,7 +189,7 @@ Test merging of variables from multiple cases
 
 ###### test: varmerge
 
-       program:
+       func main()
                for i:=0; then i=i+1; while i < 5:
                        switch i:
                                case 0: num:="zero"
@@ -223,7 +225,7 @@ Now we need to test if/else and some different loops
 
 ###### test: cond_loop
 
-       program:
+       func main()
                a := 4
                if a < 5:
                        print "Success"
@@ -295,7 +297,7 @@ Here I break it into two parts, keeping the array code separate.
 
 ###### test: sayhello
 
-       program ac av:
+       func main(av:[ac::number]string)
                A := $av[1]; B := $av[2]
                astr := av[3]
                bbool := av[ac-1] == "True"
@@ -392,7 +394,7 @@ Here I break it into two parts, keeping the array code separate.
 ###### test list
        oceani_tests += "insert_sort"
 ###### test: insert_sort
-       program:
+       func main()
                size::=55
                list:[size]number
                list[0] = 1_234
@@ -538,7 +540,7 @@ ad-hoc things array related.
 
 ###### test: arrays
 
-       program:
+       func main()
                bools:[5]Boolean
                strings:[4]string
 
@@ -573,7 +575,8 @@ Time to test if structure declarations and accesses work correctly.
 
        struct baz { a:number; b:Boolean; }
 
-       program:
+       func main
+       do
                info:[4]foo
 
                for i:=0; then i=i+1; while i < 4:
@@ -634,14 +637,14 @@ separate file first, then run from there.
 
 ###### test: syn1
 
-       program:
+       func main()
                if then else while do
 
 ###### output: syn1
        .tmp.code:3:11: Syntax error in statement: then
 
 ###### test: tokerr
-       program:
+       func main()
                a := 1i  // imaginary numbers aren't understood
                b:[2i]number // array sizes are handled separately
                c:[3.14159]Boolean // array sizes must be integers
@@ -691,7 +694,7 @@ various places that `type_err()` are called.
 
 ###### test: type_err1
 
-       program:
+       func main()
                print "hello" ++ 5, 5 ++ "hello"
 
                b ::= 3
@@ -711,7 +714,7 @@ various places that `type_err()` are called.
 
 ###### test: type_err2
 
-       program:
+       func main()
                a := 1
                a := 2
                a ::= 3
@@ -736,7 +739,7 @@ various places that `type_err()` are called.
                a: number
                b:string = "hello"
 
-       program:
+       func main()
                c := "hello"
                c = c + 1
                c = "hello" ++ (True and False)
@@ -798,7 +801,7 @@ various places that `type_err()` are called.
        oceani: type error in program - not running.
 
 ###### test: type_err4
-       program:
+       func main()
                a:=1; b=2; c::=3
                print a, b, c
 
@@ -817,7 +820,7 @@ various places that `type_err()` are called.
                bar ::= "string" + 56
        const
                bar ::= "baz"
-       program:
+       func main()
                foo := 4
                print foo, bar
 
@@ -832,7 +835,7 @@ various places that `type_err()` are called.
        const
                foo : number = 45
                bar := "string"
-       program:
+       func main()
                foo := 4
                print foo, bar
 
@@ -845,7 +848,7 @@ various places that `type_err()` are called.
                foo::="bar"
 
 ###### output: missing_program
-       oceani: no program found.
+       oceani: no main function found.
 
 ## Test erroneous command line args
 
index 415c024e2dd55cd086b765c5bb4c93493c548cb9..fd753ce5e0b0d43484fb6959e19cb30c7ee04aab 100644 (file)
@@ -41,10 +41,12 @@ short-circuit Boolean operators, and the "if ... else" trinary
 operator which can select between two expressions based on a third
 (which appears syntactically in the middle).
 
-Elements that are present purely to make a usable language, and
-without any expectation that they will remain, are the "program'
-clause, which provides a list of variables to received command-line
-arguments, and the "print" statement which performs simple output.
+The "func" clause currently only allows a "main" function to be
+declared.  That will be extended when proper function support is added.
+
+An element that is present purely to make a usable language, and
+without any expectation that they will remain, is the "print" statement
+which performs simple output.
 
 The current scalar types are "number", "Boolean", and "string".
 Boolean will likely stay in its current form, the other two might, but
@@ -72,7 +74,8 @@ So the main requirements of the interpreter are:
 - Parse the program, possibly with tracing,
 - Analyse the parsed program to ensure consistency,
 - Print the program,
-- Execute the program, if no parsing or consistency errors were found.
+- Execute the "main" function in the program, if no parsing or
+  consistency errors were found.
 
 This is all performed by a single C program extracted with
 `parsergen`.
@@ -230,7 +233,7 @@ structures can be used.
                parse_oceani(ss->code, &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 = $<OT;
+               v->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($<Ar);
+                       $0->right = $<Bl;
+                       var_block_close(c, CloseSequential);
+                       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);
+                       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);
+                       if (c->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 = $<Vl;
+                       $0->right = $<AD;
+               }$
+               | ArgDecl ${
+                       $0 = new(binode);
+                       $0->op = List;
+                       $0->left = NULL;
+                       $0->right = $<AD;
+               }$
+
+       $*var
+       ArgDecl -> IDENTIFIER : FormalType ${ {
+               struct variable *v = var_decl(c, $1.txt);
+               $0 = new(var);
+               $0->var = v;
+               v->type = $<FT;
+       } }$
+
 ## Executables: the elements of code
 
 Each code element needs to be parsed, printed, analysed,
@@ -2412,18 +2563,6 @@ link to find the primary instance.
        } }$
        ## variable grammar
 
-       $*type
-       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($<Vl);
-               $0->right = $<Bl;
-               var_block_close(c, CloseSequential);
-               if (c->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