]> ocean-lang.org Git - ocean/commitdiff
Oceani - Jamison Creek Version JamisonCreek-3
authorNeilBrown <neil@brown.name>
Sat, 18 May 2019 20:46:13 +0000 (06:46 +1000)
committerNeilBrown <neil@brown.name>
Sat, 18 May 2019 20:46:13 +0000 (06:46 +1000)
Clean up text and provide new version name.
Additions for this version include:
 - type identifiers
 - arrays and structs
 - global const
 - "and then" "or else" "if .. else"
 - test suite
 - valgrind testing, coverage testing

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

index a8c7746b8197e59a5a0976315271413cc84a6d64..ce70c49e2b6231240905d843456be054ef10ab3d 100644 (file)
@@ -1,4 +1,4 @@
-# Ocean Interpreter - Stoney Creek version
+# Ocean Interpreter - Jamison Creek version
 
 Ocean is intended to be a compiled language, so this interpreter is
 not targeted at being the final product.  It is, rather, an intermediate
@@ -29,35 +29,31 @@ be.
 
 ## Current version
 
-This second version of the interpreter exists to test out the
-structured statement providing conditions and iteration, and simple
-variable scoping.  Clearly we need some minimal other functionality so
-that values can be tested and instructions iterated over.  All that
-functionality is clearly not normative at this stage (not that
-anything is **really** normative yet) and will change, so early test
-code will certainly break in later versions.
+This third version of the interpreter exists to test out some initial
+ideas relating to types.  Particularly it adds arrays (indexed from
+zero) and simple structures.  Basic control flow and variable scoping
+are already fairly well established, as are basic numerical and
+boolean operators.
 
-The under-test parts of the language are:
+Some operators that have only recently been added, and so have not
+generated all that much experience yet are "and then" and "or else" as
+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).
 
- - conditional/looping structured statements
- - the `use` statement which is needed for that
- - Variable binding using ":=" and "::=", and assignment using "=".
+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.
 
-Elements which are present to make a usable language are:
-
- - "blocks" of multiple statements.
- - `pass`: a statement which does nothing.
- - expressions: `+`, `-`, `*`, `/`, `%` can apply to numbers and `++` can
-   catenate strings.  `and`, `or`, `not` manipulate Booleans, and
-   normal comparison operators can work on all three types.
- - `print`: will print the values in a list of expressions.
- - `program`: is given a list of identifiers to initialize from
-   arguments.
+The current scalar types are "number", "Boolean", and "string".
+Boolean will likely stay in its current form, the other two might, but
+could just as easily be changed.
 
 ## Naming
 
 Versions of the interpreter which obviously do not support a complete
-language will be named after creeks and streams.  This one is Stoney
+language will be named after creeks and streams.  This one is Jamison
 Creek.
 
 Once we have something reasonably resembling a complete language, the
@@ -76,7 +72,7 @@ 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.
+- Execute the program, if no parsing or consistency errors were found.
 
 This is all performed by a single C program extracted with
 `parsergen`.
@@ -168,7 +164,7 @@ structures can be used.
                int fd;
                int len;
                char *file;
-               struct section *s;
+               struct section *s, *ss;
                char *section = NULL;
                struct parse_context context = {
                        .config = {
@@ -215,31 +211,28 @@ structures can be used.
                ## context initialization
 
                if (section) {
-                       struct section *ss;
                        for (ss = s; ss; ss = ss->next) {
                                struct text sec = ss->section;
                                if (sec.len == strlen(section) &&
                                    strncmp(sec.txt, section, sec.len) == 0)
                                        break;
                        }
-                       if (ss)
-                               parse_oceani(ss->code, &context.config,
-                                            dotrace ? stderr : NULL);
-                       else {
+                       if (!ss) {
                                fprintf(stderr, "oceani: cannot find section %s\n",
                                        section);
                                exit(1);
                        }
                } else
-                       parse_oceani(s->code, &context.config,
-                                    dotrace ? stderr : NULL);
+                       ss = s;
+               parse_oceani(ss->code, &context.config, dotrace ? stderr : NULL);
+
                if (!context.prog) {
                        fprintf(stderr, "oceani: no program found.\n");
                        context.parse_error = 1;
                }
                if (context.prog && doprint) {
                        ## print const decls
-                       ## print decls
+                       ## print type decls
                        print_exec(context.prog, 0, brackets);
                }
                if (context.prog && doexec && !context.parse_error) {
@@ -249,9 +242,8 @@ structures can be used.
                        }
                        interp_prog(context.prog, argv+optind+1);
                }
-               if (context.prog) {
-                       free_exec(context.prog);
-               }
+               free_exec(context.prog);
+
                while (s) {
                        struct section *t = s->next;
                        code_free(s->code);
@@ -294,12 +286,12 @@ outside the if, the variables in the different branches are distinct
 and can be of different types.
 
 Determining the types of all variables early is important for
-processing command line arguments.  These can be assigned to any type
-of variable, but we must first know the correct type so any required
-conversion can happen.  If a variable is associated with a command
-line argument but no type can be interpreted (e.g. the variable is
-only ever used in a `print` statement), then the type is set to
-'string'.
+processing command line arguments.  These can be assigned to any of
+several types of variable, but we must first know the correct type so
+any required conversion can happen.  If a variable is associated with
+a command line argument but no type can be interpreted (e.g. the
+variable is only ever used in a `print` statement), then the type is
+set to 'string'.
 
 Undeclared names may only appear in "use" statements and "case" expressions.
 These names are given a type of "label" and a unique value.
@@ -307,9 +299,9 @@ This allows them to fill the role of a name in an enumerated type, which
 is useful for testing the `switch` statement.
 
 As we will see, the condition part of a `while` statement can return
-either a Boolean or some other type.  This requires that the expect
-type that gets passed around comprises a type (`enum vtype`) and a
-flag to indicate that `Vbool` is also permitted.
+either a Boolean or some other type.  This requires that the expected
+type that gets passed around comprises a type and a flag to indicate
+that `Tbool` is also permitted.
 
 As there are, as yet, no distinct types that are compatible, there
 isn't much subtlety in the analysis.  When we have distinct number
@@ -319,8 +311,9 @@ types, this will become more interesting.
 
 When analysis discovers an inconsistency it needs to report an error;
 just refusing to run the code ensures that the error doesn't cascade,
-but by itself it isn't very useful.  A clear understand of the sort of
-error message that are useful will help guide the process of analysis.
+but by itself it isn't very useful.  A clear understanding of the sort
+of error message that are useful will help guide the process of
+analysis.
 
 At a simplistic level, the only sort of error that type analysis can
 report is that the type of some construct doesn't match a contextual
@@ -399,17 +392,15 @@ context so indicate that parsing failed.
                c->parse_error = 1;
        }
 
-## Data Structures
+## Entities: declared and predeclared.
 
-One last introductory step before detailing the language elements and
-providing their four requirements is to establish the data structures
-to store these elements.
-
-There are two key objects that we need to work with: executable
-elements which comprise the program, and values which the program
-works with.  Between these are the variables in their various scopes
-which hold the values, and types which classify the values stored and
-manipulatd by executables.
+There are various "things" that the language and/or the interpreter
+needs to know about to parse and execute a program.  These include
+types, variables, values, and executable code.  These are all lumped
+together under the term "entities" (calling them "objects" would be
+confusing) and introduced here.  These will introduced and described
+here.  The following section will present the different specific code
+elements which comprise or manipulate these various entities.
 
 ### Types
 
@@ -423,8 +414,9 @@ better integrated into the language.
 Rather than requiring every numeric type to support all numeric
 operations (add, multiple, etc), we allow types to be able to present
 as one of a few standard types: integer, float, and fraction.  The
-existance of these conversion functions enable types to determine if
-they are compatible with other types.
+existence of these conversion functions eventaully enable types to
+determine if they are compatible with other types, though such types
+have not yet been implemented.
 
 Named type are stored in a simple linked list.  Objects of each type are "values"
 which are often passed around by value.
@@ -446,7 +438,6 @@ which are often passed around by value.
                struct value (*parse)(struct type *type, char *str);
                void (*print)(struct value val);
                void (*print_type)(struct type *type, FILE *f);
-               void (*print_type_decl)(struct type *type, FILE *f);
                int (*cmp_order)(struct value v1, struct value v2);
                int (*cmp_eq)(struct value v1, struct value v2);
                struct value (*dup)(struct value val);
@@ -456,6 +447,7 @@ which are often passed around by value.
                long long (*to_int)(struct value *v);
                double (*to_float)(struct value *v);
                int (*to_mpq)(mpq_t *q, struct value *v);
+               ## type functions
                union {
                        ## type union fields
                };
@@ -623,11 +615,18 @@ with anything.  There are two special cases with type compatibility,
 both related to the Conditional Statement which will be described
 later.  In some cases a Boolean can be accepted as well as some other
 primary type, and in others any type is acceptable except a label (`Vlabel`).
-A separate function encode these cases will simplify some code later.
+A separate function encoding these cases will simplify some code later.
 
 When assigning command line arguments to variables, we need to be able
 to parse each type from a string.
 
+The distinction beteen "prepare" and "init" needs to be explained.
+"init" sets up an initial value, such as "zero" or the empty string.
+"prepare" simply prepares the data structure so that if "free" gets
+called on it, it won't do something silly.  Normally a value will be
+stored after "prepare" but before "free", but this might not happen if
+there are errors.
+
 ###### includes
        #include <gmp.h>
        #include "string.h"
@@ -1004,7 +1003,6 @@ like "if" and the code following it.
        $void
        OpenScope -> ${ scope_push(config2context(config)); }$
 
-
 Each variable records a scope depth and is in one of four states:
 
 - "in scope".  This is the case between the declaration of the
@@ -1054,6 +1052,13 @@ merging variables, we need to also adjust the 'merged' pointer on any
 other variables that had previously been merged with the one that will
 no longer be primary.
 
+A variable that is no longer the most recent instance of a name may
+still have "pending" scope, if it might still be merged with most
+recent instance.  These variables don't really belong in the
+"in_scope" list, but are not immediately removed when a new instance
+is found.  Instead, they are detected and ignored when considering the
+list of in_scope names.
+
 ###### variable fields
        struct variable *merged;
 
@@ -1089,6 +1094,7 @@ no longer be primary.
                        v = t->previous;
                        free_value(t->val);
                        if (t->min_depth == 0)
+                               // This is a global constant
                                free_exec(t->where_decl);
                        free(t);
                }
@@ -1104,21 +1110,23 @@ the latest usage.  This is determined from `min_depth`.  When a
 conditionally visible variable gets affirmed like this, it is also
 merged with other conditionally visible variables with the same name.
 
-When we parse a variable declaration we either signal an error if the
+When we parse a variable declaration we either report an error if the
 name is currently bound, or create a new variable at the current nest
 depth if the name is unbound or bound to a conditionally scoped or
 pending-scope variable.  If the previous variable was conditionally
 scoped, it and its homonyms becomes out-of-scope.
 
 When we parse a variable reference (including non-declarative
-assignment) we signal an error if the name is not bound or is bound to
+assignment) we report an error if the name is not bound or is bound to
 a pending-scope variable; update the scope if the name is bound to a
 conditionally scoped variable; or just proceed normally if the named
 variable is in scope.
 
 When we exit a scope, any variables bound at this level are either
-marked out of scope or pending-scoped, depending on whether the
-scope was sequential or parallel.
+marked out of scope or pending-scoped, depending on whether the scope
+was sequential or parallel.  Here a "parallel" scope means the "then"
+or "else" part of a conditional, or any "case" or "else" branch of a
+switch.  Other scopes are "sequential".
 
 When exiting a parallel scope we check if there are any variables that
 were previously pending and are still visible. If there are, then
@@ -1350,9 +1358,9 @@ slowly.
 #### Freeing
 
 The parser generator requires a `free_foo` function for each struct
-that stores attributes and they will be `exec`s and subtypes there-of.
-So we need `free_exec` which can handle all the subtypes, and we need
-`free_binode`.
+that stores attributes and they will often be `exec`s and subtypes
+there-of.  So we need `free_exec` which can handle all the subtypes,
+and we need `free_binode`.
 
 ###### ast functions
 
@@ -1476,7 +1484,13 @@ within the `exec` tree.  The exception to this is the whole `program`
 which needs to look at command line arguments.  The `program` will be
 interpreted separately.
 
-Each `exec` can return a value, which may be `Tnone` but must be non-NULL;
+Each `exec` can return a value, which may be `Tnone` but must be
+non-NULL;  Some `exec`s will return the location of a value, which can
+be updates.  To support this, each exec case must store either a value
+in `val` or the pointer to a value in `lval`.  If `lval` is set, but a
+simple value is required, `inter_exec()` will dereference `lval` to
+get the value.
+
 
 ###### core functions
 
@@ -1769,10 +1783,10 @@ make a copy of an array with controllable depth.
 A `struct` is a data-type that contains one or more other data-types.
 It differs from an array in that each member can be of a different
 type, and they are accessed by name rather than by number.  Thus you
-cannot choose and element by calculation, you need to know what you
+cannot choose an element by calculation, you need to know what you
 want up-front.
 
-The language makes no promises about how a give structure will be
+The language makes no promises about how a given structure will be
 stored in memory - it is free to rearrange fields to suit whatever
 criteria seems important.
 
@@ -1781,16 +1795,16 @@ declared in-line in a variable declaration like arrays can.  A struct
 is given a name and this name is used to identify the type - the name
 is not prefixed by the word `struct` as it would be in C.
 
-Each component datum is identified much like a variable is declared,
-with a name, one or to colons, and a type.  The type cannot be omitted
-as there is no opportunity to deduce the type from usage.  An initial
-value can be given following an equals sign, so
-
 Structs are only treated as the same if they have the same name.
 Simply having the same fields in the same order is not enough.  This
 might change once we can create structure initializes from a list of
 values.
 
+Each component datum is identified much like a variable is declared,
+with a name, one or two colons, and a type.  The type cannot be omitted
+as there is no opportunity to deduce the type from usage.  An initial
+value can be given following an equals sign, so
+
 ##### Example: a struct type
 
        struct complex:
@@ -1800,6 +1814,11 @@ values.
 would declare a type called "complex" which has two number fields,
 each initialised to zero.
 
+Struct will need to be declared separately from the code that uses
+them, so we will need to be able to print out the declaration of a
+struct when reprinting the whole program.  So a `print_type_decl` type
+function will be needed.
+
 ###### type union fields
 
        struct {
@@ -1816,6 +1835,9 @@ each initialised to zero.
                struct value *fields;
        } structure;
 
+###### type functions
+       void (*print_type_decl)(struct type *type, FILE *f);
+
 ###### value functions
 
        static struct value structure_prepare(struct type *type)
@@ -2059,7 +2081,7 @@ each initialised to zero.
                }
        }
 
-###### print decls
+###### print type decls
        {
                struct type *t;
                int target = -1;
@@ -2082,9 +2104,9 @@ each initialised to zero.
                }
        }
 
-## Language elements
+## Executables: the elements of code
 
-Each language element needs to be parsed, printed, analysed,
+Each code element needs to be parsed, printed, analysed,
 interpreted, and freed.  There are several, so let's just start with
 the easy ones and work our way up.
 
@@ -2245,9 +2267,9 @@ link to find the primary instance.
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Tnone, 0, Tnone);
+                                $0, NULL, 0, NULL);
                        type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Tnone, 0, Tnone);
+                                v->where_decl, NULL, 0, NULL);
                }
        } }$
            | IDENTIFIER :: ${ {
@@ -2261,9 +2283,9 @@ link to find the primary instance.
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Tnone, 0, Tnone);
+                                $0, NULL, 0, NULL);
                        type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Tnone, 0, Tnone);
+                                v->where_decl, NULL, 0, NULL);
                }
        } }$
            | IDENTIFIER : Type ${ {
@@ -2278,9 +2300,9 @@ link to find the primary instance.
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Tnone, 0, Tnone);
+                                $0, NULL, 0, NULL);
                        type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Tnone, 0, Tnone);
+                                v->where_decl, NULL, 0, NULL);
                }
        } }$
            | IDENTIFIER :: Type ${ {
@@ -2296,9 +2318,9 @@ link to find the primary instance.
                        v = var_ref(config2context(config), $1.txt);
                        $0->var = v;
                        type_err(config2context(config), "error: variable '%v' redeclared",
-                                $0, Tnone, 0, Tnone);
+                                $0, NULL, 0, NULL);
                        type_err(config2context(config), "info: this is where '%v' was first declared",
-                                v->where_decl, Tnone, 0, Tnone);
+                                v->where_decl, NULL, 0, NULL);
                }
        } }$
 
@@ -2362,7 +2384,7 @@ link to find the primary instance.
                struct var *var = cast(var, prog);
                struct variable *v = var->var;
                if (!v) {
-                       type_err(c, "%d:BUG: no variable!!", prog, Tnone, 0, Tnone); // NOTEST
+                       type_err(c, "%d:BUG: no variable!!", prog, NULL, 0, NULL); // NOTEST
                        *ok = 0;                                        // NOTEST
                        return Tnone;                                   // NOTEST
                }
@@ -2388,7 +2410,7 @@ link to find the primary instance.
                        type_err(c, "error: expected %1%r but variable '%v' is %2", prog,
                                 type, rules, v->val.type);
                        type_err(c, "info: this is where '%v' was set to %1", v->where_set,
-                                v->val.type, rules, Tnone);
+                                v->val.type, rules, NULL);
                        *ok = 0;
                }
                if (!type)
@@ -2428,10 +2450,10 @@ what an "Expression" is.  The next level up is "BoolExpr", which
 comes next.
 
 Conditional expressions are of the form "value `if` condition `else`
-other_value".  There is no associativite with this operator: the
-values and conditions can only be other conditional expressions if
-they are enclosed in parentheses.  Allowing nesting without
-parentheses would be too confusing.
+other_value".  They associate to the right, so everything to the right
+of `else` is part of an else value, while only the BoolExpr to the
+left of `if` is the if values.  Between `if` and `else` there is no
+room for ambiguity, so a full conditional expression is allowed in there.
 
 ###### Binode types
        CondExpr,
@@ -2439,7 +2461,7 @@ parentheses would be too confusing.
 ###### Grammar
 
        $*exec
-       Expression -> BoolExpr if BoolExpr else BoolExpr ${ {
+       Expression -> BoolExpr if Expression else Expression ${ {
                        struct binode *b1 = new(binode);
                        struct binode *b2 = new(binode);
                        b1->op = CondExpr;
@@ -2493,7 +2515,7 @@ parentheses would be too confusing.
 The next class of expressions to use the `binode` will be Boolean
 expressions.  As I haven't implemented precedence in the parser
 generator yet, we need different names for each precedence level used
-by expressions.  The outer most or lowest level precedence are
+by expressions.  The outer most or lowest level precedence after
 conditional expressions are Boolean operators which form an `BoolExpr`
 out of `BTerm`s and `BFact`s.  As well as `or` `and`, and `not` we
 have `and then` and `or else` which only evaluate the second operand
@@ -2621,7 +2643,7 @@ if the result would make a difference.
 
 Of slightly higher precedence that Boolean expressions are
 Comparisons.
-A comparison takes arguments of any type, but the two types must be
+A comparison takes arguments of any comparable type, but the two types must be
 the same.
 
 To simplify the parsing we introduce an `eop` which can record an
@@ -2649,11 +2671,11 @@ expression operator.
 
 ###### other BFact
        | Expr CMPop Expr ${ {
-                       struct binode *b = new(binode);
-                       b->op = $2.op;
-                       b->left = $<1;
-                       b->right = $<3;
-                       $0 = b;
+               struct binode *b = new(binode);
+               b->op = $2.op;
+               b->left = $<1;
+               b->right = $<3;
+               $0 = b;
        } }$
        | Expr ${ $0 = $<1; }$
 
@@ -2939,12 +2961,11 @@ precedence is handled better I might be able to discard this.
                return rv;
        }
 
-
 ### Blocks, Statements, and Statement lists.
 
 Now that we have expressions out of the way we need to turn to
 statements.  There are simple statements and more complex statements.
-Simple statements do not contain newlines, complex statements do.
+Simple statements do not contain (syntactic) newlines, complex statements do.
 
 Statements often come in sequences and we have corresponding simple
 statement lists and complex statement lists.
@@ -3243,7 +3264,8 @@ it is declared, and error will be raised as the name is created as
 
        | VariableDecl ${
                        if ($1->var->where_set == NULL) {
-                               type_err(config2context(config), "Variable declared with no type or value: %v",
+                               type_err(config2context(config),
+                                        "Variable declared with no type or value: %v",
                                         $1, NULL, 0, NULL);
                        } else {
                                $0 = new(binode);
@@ -3311,7 +3333,7 @@ it is declared, and error will be raised as the name is created as
                        if (propagate_types(b->right, c, ok, t, 0) != t)
                                if (b->left->type == Xvar)
                                        type_err(c, "info: variable '%v' was set as %1 here.",
-                                                cast(var, b->left)->var->where_set, t, rules, Tnone);
+                                                cast(var, b->left)->var->where_set, t, rules, NULL);
                } else {
                        t = propagate_types(b->right, c, ok, NULL, Rnolabel);
                        if (t)
@@ -3877,12 +3899,11 @@ it is time to clarify what those places are.
 
 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, named types, and probably
-more.
+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 and the main program.  While the
-syntax will allow the main program to appear multiple times, that will
-trigger an error if it is actually attempted.
+level.  They are predefined constants, `struct` types, and the main
+program.  While the syntax will allow the main program to appear
+multiple times, that will trigger an error if it is actually attempted.
 
 The various declarations do not return anything.  They store the
 various declarations in the parse context.
@@ -3912,7 +3933,7 @@ later interpretation stage.  Once we add functions to the language, we
 will need rules concern which, if any, can be used to define a top
 level constant.
 
-Constants are defined in a sectiont that starts with the reserved word
+Constants are defined in a section that starts with the reserved word
 `const` and then has a block with a list of assignment statements.
 For syntactic consistency, these must use the double-colon syntax to
 make it clear that they are constants.  Type can also be given: if
@@ -4155,10 +4176,11 @@ analysis is a bit more interesting at this level.
 
 ## And now to test it out.
 
-Having a language requires having a "hello world" program. I'll
+Having a language requires having a "hello world" program.  I'll
 provide a little more than that: a program that prints "Hello world"
 finds the GCD of two numbers, prints the first few elements of
-Fibonacci, and performs a binary search for a number.
+Fibonacci, performs a binary search for a number, and a few other
+things which will likely grow as the languages grows.
 
 ###### File: oceani.mk
        tests :: sayhello