-# 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
## 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
- 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`.
#define config2context(_conf) container_of(_conf, struct parse_context, \
config)
+###### Parser: reduce
+ struct parse_context *c = config2context(config);
+
###### Parser: code
#include <unistd.h>
int fd;
int len;
char *file;
- struct section *s;
+ struct section *s, *ss;
char *section = NULL;
struct parse_context context = {
.config = {
.ignored = (1 << TK_line_comment)
- | (1 << TK_block_comment),
- .number_chars = ".,_+-",
+ | (1 << TK_block_comment)
+ | (1 << TK_mark),
+ .number_chars = ".,_+- ",
.word_start = "_",
.word_cont = "_",
},
## 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 decls
+ ## print const decls
+ ## print type decls
print_exec(context.prog, 0, brackets);
}
if (context.prog && doexec && !context.parse_error) {
}
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);
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.
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
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
c->parse_error = 1;
}
-## Data Structures
-
-One last introductory step before detailing the language elements and
-providing their four requirements is to establish the data structures
-to store these elements.
+## Entities: declared and predeclared.
-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
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.
int (*cmp_eq)(struct value v1, struct value v2);
struct value (*dup)(struct value val);
void (*free)(struct value val);
+ void (*free_type)(struct type *t);
int (*compat)(struct type *this, struct type *other);
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
};
struct type *t = context.typelist;
context.typelist = t->next;
+ if (t->free_type)
+ t->free_type(t);
free(t);
}
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"
###### Grammar
$void
- OpenScope -> ${ scope_push(config2context(config)); }$
-
+ OpenScope -> ${ scope_push(c); }$
Each variable records a scope depth and is in one of four states:
in scope. It is permanently out of scope now and can be removed from
the "in scope" stack.
-
###### variable fields
int depth, min_depth;
enum { OutScope, PendingScope, CondScope, InScope } scope;
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;
v = t->previous;
free_value(t->val);
- if (t->min_depth == 0)
+ if (t->depth == 0)
+ // This is a global constant
free_exec(t->where_decl);
free(t);
}
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
switch (v ? v->scope : OutScope) {
case OutScope:
case PendingScope:
- /* Signal an error - once that is possible */
+ /* Caller will report the error */
return NULL;
case CondScope:
/* All CondScope variables of this name need to be merged
static void var_block_close(struct parse_context *c, enum closetype ct)
{
- /* close of all variables that are in_scope */
+ /* Close off all variables that are in_scope */
struct variable *v, **vp, *v2;
scope_pop(c);
for (vp = &c->in_scope;
v = *vp, v && v->depth > c->scope_depth && v->min_depth > c->scope_depth;
) {
- switch (ct) {
+ if (v->name->var == v) switch (ct) {
case CloseElse:
case CloseParallel: /* handle PendingScope */
switch(v->scope) {
}
break;
}
- if (v->scope == OutScope)
+ if (v->scope == OutScope || v->name->var != v)
*vp = v->in_scope;
else
vp = &v->in_scope;
#### 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
###### core functions
static struct type *propagate_types(struct exec *prog, struct parse_context *c, int *ok,
- struct type *type, int rules)
+ struct type *type, int rules);
+ static struct type *__propagate_types(struct exec *prog, struct parse_context *c, int *ok,
+ struct type *type, int rules)
{
struct type *t;
return Tnone;
}
+ static struct type *propagate_types(struct exec *prog, struct parse_context *c, int *ok,
+ struct type *type, int rules)
+ {
+ struct type *ret = __propagate_types(prog, c, ok, type, rules);
+
+ if (c->parse_error)
+ *ok = 0;
+ return ret;
+ }
+
#### Interpreting
Interpreting an `exec` doesn't require anything but the `exec`. State
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
complex types and connected them in to the data structures and the
different phases of parse, analyse, print, interpret.
-For now, just arrays.
+Thus far we have arrays and structs.
#### Arrays
$0->array.member = $<4;
$0->array.vsize = NULL;
{
- struct parse_context *c = config2context(config);
char tail[3];
mpq_t num;
if (number_parse(num, tail, $2.txt) == 0)
}$
| [ IDENTIFIER ] Type ${ {
- struct parse_context *c = config2context(config);
struct variable *v = var_ref(c, $2.txt);
if (!v)
- tok_err(config2context(config), "error: name undeclared", &$2);
+ tok_err(c, "error: name undeclared", &$2);
else if (!v->constant)
- tok_err(config2context(config), "error: array size must be a constant", &$2);
+ tok_err(c, "error: array size must be a constant", &$2);
$0 = calloc(1, sizeof(struct type));
*($0) = array_prototype;
###### print binode cases
case Index:
- print_exec(b->left, -1, 0);
+ print_exec(b->left, -1, bracket);
printf("[");
- print_exec(b->right, -1, 0);
+ print_exec(b->right, -1, bracket);
printf("]");
break;
t = propagate_types(b->left, c, ok, NULL, rules & Rnoconstant);
if (!t || t->compat != array_compat) {
type_err(c, "error: %1 cannot be indexed", prog, t, 0, NULL);
- *ok = 0;
return NULL;
} else {
if (!type_compat(type, t->array.member, rules)) {
type_err(c, "error: have %1 but need %2", prog,
t->array.member, rules, type);
- *ok = 0;
}
return t->array.member;
}
break;
}
-## Language elements
+#### Structs
+
+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 an element by calculation, you need to know what you
+want up-front.
+
+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.
+
+Structs are declared separately from program code - they cannot be
+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.
+
+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:
+ x:number = 0
+ y:number = 0
+
+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 {
+ int nfields;
+ struct field {
+ struct text name;
+ struct type *type;
+ struct value init;
+ } *fields;
+ } structure;
+
+###### value union fields
+ struct {
+ 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)
+ {
+ struct value ret;
+
+ ret.type = type;
+ ret.structure.fields = NULL;
+ return ret;
+ }
+
+ static struct value structure_init(struct type *type)
+ {
+ struct value ret;
+ int i;
+
+ ret.type = type;
+ ret.structure.fields = calloc(type->structure.nfields,
+ sizeof(ret.structure.fields[0]));
+ for (i = 0; ret.structure.fields && i < type->structure.nfields; i++)
+ ret.structure.fields[i] = val_init(type->structure.fields[i].type);
+ return ret;
+ }
+
+ static void structure_free(struct value val)
+ {
+ int i;
+
+ if (val.structure.fields)
+ for (i = 0; i < val.type->structure.nfields; i++)
+ free_value(val.structure.fields[i]);
+ free(val.structure.fields);
+ }
+
+ static void structure_free_type(struct type *t)
+ {
+ int i;
+ for (i = 0; i < t->structure.nfields; i++)
+ free_value(t->structure.fields[i].init);
+ free(t->structure.fields);
+ }
+
+ static struct type structure_prototype = {
+ .prepare = structure_prepare,
+ .init = structure_init,
+ .free = structure_free,
+ .free_type = structure_free_type,
+ .print_type_decl = structure_print_type,
+ };
+
+###### exec type
+ Xfieldref,
+
+###### ast
+ struct fieldref {
+ struct exec;
+ struct exec *left;
+ int index;
+ struct text name;
+ };
+
+###### free exec cases
+ case Xfieldref:
+ free_exec(cast(fieldref, e)->left);
+ free(e);
+ break;
+
+###### variable grammar
+
+ | Variable . IDENTIFIER ${ {
+ struct fieldref *fr = new_pos(fieldref, $2);
+ fr->left = $<1;
+ fr->name = $3.txt;
+ fr->index = -2;
+ $0 = fr;
+ } }$
+
+###### print exec cases
+
+ case Xfieldref:
+ {
+ struct fieldref *f = cast(fieldref, e);
+ print_exec(f->left, -1, bracket);
+ printf(".%.*s", f->name.len, f->name.txt);
+ break;
+ }
+
+###### ast functions
+ static int find_struct_index(struct type *type, struct text field)
+ {
+ int i;
+ for (i = 0; i < type->structure.nfields; i++)
+ if (text_cmp(type->structure.fields[i].name, field) == 0)
+ return i;
+ return -1;
+ }
+
+###### propagate exec cases
+
+ case Xfieldref:
+ {
+ struct fieldref *f = cast(fieldref, prog);
+ struct type *st = propagate_types(f->left, c, ok, NULL, 0);
+
+ if (!st)
+ type_err(c, "error: unknown type for field access", f->left,
+ NULL, 0, NULL);
+ else if (st->prepare != structure_prepare)
+ type_err(c, "error: field reference attempted on %1, not a struct",
+ f->left, st, 0, NULL);
+ else if (f->index == -2) {
+ f->index = find_struct_index(st, f->name);
+ if (f->index < 0)
+ type_err(c, "error: cannot find requested field in %1",
+ f->left, st, 0, NULL);
+ }
+ if (f->index >= 0) {
+ struct type *ft = st->structure.fields[f->index].type;
+ if (!type_compat(type, ft, rules))
+ type_err(c, "error: have %1 but need %2", prog,
+ ft, rules, type);
+ return ft;
+ }
+ break;
+ }
+
+###### interp exec cases
+ case Xfieldref:
+ {
+ struct fieldref *f = cast(fieldref, e);
+ struct value *lleft = linterp_exec(f->left);
+ lrv = &lleft->structure.fields[f->index];
+ break;
+ }
+
+###### ast
+ struct fieldlist {
+ struct fieldlist *prev;
+ struct field f;
+ };
+
+###### ast functions
+ static void free_fieldlist(struct fieldlist *f)
+ {
+ if (!f)
+ return;
+ free_fieldlist(f->prev);
+ free_value(f->f.init);
+ free(f);
+ }
+
+###### top level grammar
+ DeclareStruct -> struct IDENTIFIER FieldBlock ${ {
+ struct type *t =
+ add_type(c, $2.txt, &structure_prototype);
+ int cnt = 0;
+ struct fieldlist *f;
+
+ for (f = $3; f; f=f->prev)
+ cnt += 1;
+
+ t->structure.nfields = cnt;
+ t->structure.fields = calloc(cnt, sizeof(struct field));
+ f = $3;
+ while (cnt > 0) {
+ cnt -= 1;
+ t->structure.fields[cnt] = f->f;
+ f->f.init = val_prepare(Tnone);
+ f = f->prev;
+ }
+ } }$
+ | DeclareStruct NEWLINE
+
+ $void
+ Newlines -> NEWLINE
+ | Newlines NEWLINE
+ Open -> {
+ | Newlines {
+ Close -> }
+ | Newlines }
+ $*fieldlist
+ FieldBlock -> Open FieldList } ${ $0 = $<2; }$
+ | Open SimpleFieldList } ${ $0 = $<2; }$
+ | : FieldList $$NEWLINE ${ $0 = $<2; }$
+
+ FieldList -> FieldLines ${ $0 = $<1; }$
+ | Newlines FieldLines ${ $0 = $<2; }$
+ FieldLines -> SimpleFieldListLine ${ $0 = $<1; }$
+ | FieldLines SimpleFieldListLine ${
+ $2->prev = $<1;
+ $0 = $<2;
+ }$
+
+ SimpleFieldListLine -> SimpleFieldList NEWLINE ${ $0 = $<1; }$
+ | SimpleFieldListLine NEWLINE ${ $0 = $<1; }$
+ | ERROR NEWLINE ${ tok_err(c, "Syntax error in struct field", &$1); }$
+
+ SimpleFieldList -> Field ${ $0 = $<1; }$
+ | SimpleFieldList ; Field ${
+ $3->prev = $<1;
+ $0 = $<3;
+ }$
+ | SimpleFieldList ; ${
+ $0 = $<1;
+ }$
+
+ Field -> IDENTIFIER : Type = Expression ${ {
+ int ok;
+
+ $0 = calloc(1, sizeof(struct fieldlist));
+ $0->f.name = $1.txt;
+ $0->f.type = $<3;
+ $0->f.init = val_prepare($0->f.type);
+ do {
+ ok = 1;
+ propagate_types($<5, c, &ok, $3, 0);
+ } while (ok == 2);
+ if (!ok)
+ c->parse_error = 1;
+ else
+ $0->f.init = interp_exec($5);
+ } }$
+ | IDENTIFIER : Type ${
+ $0 = calloc(1, sizeof(struct fieldlist));
+ $0->f.name = $1.txt;
+ $0->f.type = $<3;
+ $0->f.init = val_init($3);
+ }$
+
+###### forward decls
+ static void structure_print_type(struct type *t, FILE *f);
+
+###### value functions
+ static void structure_print_type(struct type *t, FILE *f)
+ {
+ int i;
+
+ fprintf(f, "struct %.*s:\n", t->name.len, t->name.txt);
+
+ for (i = 0; i < t->structure.nfields; i++) {
+ struct field *fl = t->structure.fields + i;
+ fprintf(f, " %.*s : ", fl->name.len, fl->name.txt);
+ type_print(fl->type, f);
+ if (fl->init.type->print) {
+ fprintf(f, " = ");
+ if (fl->init.type == Tstr)
+ fprintf(f, "\"");
+ print_value(fl->init);
+ if (fl->init.type == Tstr)
+ fprintf(f, "\"");
+ }
+ printf("\n");
+ }
+ }
+
+###### print type decls
+ {
+ struct type *t;
+ int target = -1;
+
+ while (target != 0) {
+ int i = 0;
+ for (t = context.typelist; t ; t=t->next)
+ if (t->print_type_decl) {
+ i += 1;
+ if (i == target)
+ break;
+ }
-Each language element needs to be parsed, printed, analysed,
+ if (target == -1) {
+ target = i;
+ } else {
+ t->print_type_decl(t, stdout);
+ target -= 1;
+ }
+ }
+ }
+
+## Executables: the elements of code
+
+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.
if (number_parse($0->val.num, tail, $1.txt) == 0)
mpq_init($0->val.num);
if (tail[0])
- tok_err(config2context(config), "error: unsupported number suffix",
+ tok_err(c, "error: unsupported number suffix",
&$1);
}
}$
char tail[3];
string_parse(&$1, '\\', &$0->val.str, tail);
if (tail[0])
- tok_err(config2context(config), "error: unsupported string suffix",
+ tok_err(c, "error: unsupported string suffix",
&$1);
}
}$
char tail[3];
string_parse(&$1, '\\', &$0->val.str, tail);
if (tail[0])
- tok_err(config2context(config), "error: unsupported string suffix",
+ tok_err(c, "error: unsupported string suffix",
&$1);
}
}$
case Xval:
{
struct val *val = cast(val, prog);
- if (!type_compat(type, val->val.type, rules)) {
+ if (!type_compat(type, val->val.type, rules))
type_err(c, "error: expected %1%r found %2",
prog, type, rules, val->val.type);
- *ok = 0;
- }
return val->val.type;
}
$*var
VariableDecl -> IDENTIFIER : ${ {
- struct variable *v = var_decl(config2context(config), $1.txt);
+ struct variable *v = var_decl(c, $1.txt);
$0 = new_pos(var, $1);
$0->var = v;
if (v)
v->where_decl = $0;
else {
- v = var_ref(config2context(config), $1.txt);
+ v = var_ref(c, $1.txt);
$0->var = v;
- type_err(config2context(config), "error: variable '%v' redeclared",
- $0, Tnone, 0, Tnone);
- type_err(config2context(config), "info: this is where '%v' was first declared",
- v->where_decl, Tnone, 0, Tnone);
+ type_err(c, "error: variable '%v' redeclared",
+ $0, NULL, 0, NULL);
+ type_err(c, "info: this is where '%v' was first declared",
+ v->where_decl, NULL, 0, NULL);
}
} }$
| IDENTIFIER :: ${ {
- struct variable *v = var_decl(config2context(config), $1.txt);
+ struct variable *v = var_decl(c, $1.txt);
$0 = new_pos(var, $1);
$0->var = v;
if (v) {
v->where_decl = $0;
v->constant = 1;
} else {
- v = var_ref(config2context(config), $1.txt);
+ v = var_ref(c, $1.txt);
$0->var = v;
- type_err(config2context(config), "error: variable '%v' redeclared",
- $0, Tnone, 0, Tnone);
- type_err(config2context(config), "info: this is where '%v' was first declared",
- v->where_decl, Tnone, 0, Tnone);
+ type_err(c, "error: variable '%v' redeclared",
+ $0, NULL, 0, NULL);
+ type_err(c, "info: this is where '%v' was first declared",
+ v->where_decl, NULL, 0, NULL);
}
} }$
| IDENTIFIER : Type ${ {
- struct variable *v = var_decl(config2context(config), $1.txt);
+ struct variable *v = var_decl(c, $1.txt);
$0 = new_pos(var, $1);
$0->var = v;
if (v) {
v->where_set = $0;
v->val = val_prepare($<3);
} else {
- v = var_ref(config2context(config), $1.txt);
+ v = var_ref(c, $1.txt);
$0->var = v;
- type_err(config2context(config), "error: variable '%v' redeclared",
- $0, Tnone, 0, Tnone);
- type_err(config2context(config), "info: this is where '%v' was first declared",
- v->where_decl, Tnone, 0, Tnone);
+ type_err(c, "error: variable '%v' redeclared",
+ $0, NULL, 0, NULL);
+ type_err(c, "info: this is where '%v' was first declared",
+ v->where_decl, NULL, 0, NULL);
}
} }$
| IDENTIFIER :: Type ${ {
- struct variable *v = var_decl(config2context(config), $1.txt);
+ struct variable *v = var_decl(c, $1.txt);
$0 = new_pos(var, $1);
$0->var = v;
if (v) {
v->val = val_prepare($<3);
v->constant = 1;
} else {
- v = var_ref(config2context(config), $1.txt);
+ v = var_ref(c, $1.txt);
$0->var = v;
- type_err(config2context(config), "error: variable '%v' redeclared",
- $0, Tnone, 0, Tnone);
- type_err(config2context(config), "info: this is where '%v' was first declared",
- v->where_decl, Tnone, 0, Tnone);
+ type_err(c, "error: variable '%v' redeclared",
+ $0, NULL, 0, NULL);
+ type_err(c, "info: this is where '%v' was first declared",
+ v->where_decl, NULL, 0, NULL);
}
} }$
$*exec
Variable -> IDENTIFIER ${ {
- struct variable *v = var_ref(config2context(config), $1.txt);
+ struct variable *v = var_ref(c, $1.txt);
$0 = new_pos(var, $1);
if (v == NULL) {
/* This might be a label - allocate a var just in case */
- v = var_decl(config2context(config), $1.txt);
+ v = var_decl(c, $1.txt);
if (v) {
- v->val = val_prepare(Tlabel);
- v->val.label = &v->val;
+ v->val = val_prepare(Tnone);
+ v->where_decl = $0;
v->where_set = $0;
}
}
$*type
Type -> IDENTIFIER ${
- $0 = find_type(config2context(config), $1.txt);
+ $0 = find_type(c, $1.txt);
if (!$0) {
- tok_err(config2context(config),
+ tok_err(c,
"error: undefined type", &$1);
$0 = Tnone;
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
- *ok = 0; // NOTEST
+ type_err(c, "%d:BUG: no variable!!", prog, NULL, 0, NULL); // NOTEST
return Tnone; // NOTEST
}
if (v->merged)
prog, NULL, 0, NULL);
type_err(c, "info: name was defined as a constant here",
v->where_decl, NULL, 0, NULL);
- *ok = 0;
return v->val.type;
}
+ if (v->val.type == Tnone && v->where_decl == prog)
+ type_err(c, "error: variable used but not declared: %v",
+ prog, NULL, 0, NULL);
if (v->val.type == NULL) {
if (type && *ok != 0) {
v->val = val_prepare(type);
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);
- *ok = 0;
+ v->val.type, rules, NULL);
}
if (!type)
return v->val.type;
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,
###### Grammar
+ $LEFT if $$ifelse
+ ## expr precedence
+
$*exec
- Expression -> BoolExpr if BoolExpr else BoolExpr ${ {
+ Expression -> Expression if Expression else Expression $$ifelse ${ {
struct binode *b1 = new(binode);
struct binode *b2 = new(binode);
b1->op = CondExpr;
b2->right = $<5;
$0 = b1;
} }$
- | BoolExpr ${ $0 = $<1; }$
+ ## expression grammar
###### print binode cases
case CondExpr:
b2 = cast(binode, b->right);
- print_exec(b2->left, -1, 0);
+ if (bracket) printf("(");
+ print_exec(b2->left, -1, bracket);
printf(" if ");
- print_exec(b->left, -1, 0);
+ print_exec(b->left, -1, bracket);
printf(" else ");
- print_exec(b2->right, -1, 0);
+ print_exec(b2->right, -1, bracket);
+ if (bracket) printf(")");
break;
###### propagate binode cases
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
OrElse,
Not,
-###### Grammar
+###### expr precedence
+ $LEFT or
+ $LEFT and
+ $LEFT not
- $*exec
- BoolExpr -> BoolExpr or BTerm ${ {
+###### expression grammar
+ | Expression or Expression ${ {
struct binode *b = new(binode);
b->op = Or;
b->left = $<1;
b->right = $<3;
$0 = b;
} }$
- | BoolExpr or else BTerm ${ {
+ | Expression or else Expression ${ {
struct binode *b = new(binode);
b->op = OrElse;
b->left = $<1;
b->right = $<4;
$0 = b;
} }$
- | BTerm ${ $0 = $<1; }$
- BTerm -> BTerm and BFact ${ {
+ | Expression and Expression ${ {
struct binode *b = new(binode);
b->op = And;
b->left = $<1;
b->right = $<3;
$0 = b;
} }$
- | BTerm and then BFact ${ {
+ | Expression and then Expression ${ {
struct binode *b = new(binode);
b->op = AndThen;
b->left = $<1;
b->right = $<4;
$0 = b;
} }$
- | BFact ${ $0 = $<1; }$
- BFact -> not BFact ${ {
+ | not Expression ${ {
struct binode *b = new(binode);
b->op = Not;
b->right = $<2;
$0 = b;
} }$
- ## other BFact
###### print binode cases
case And:
- print_exec(b->left, -1, 0);
+ if (bracket) printf("(");
+ print_exec(b->left, -1, bracket);
printf(" and ");
- print_exec(b->right, -1, 0);
+ print_exec(b->right, -1, bracket);
+ if (bracket) printf(")");
break;
case AndThen:
- print_exec(b->left, -1, 0);
+ if (bracket) printf("(");
+ print_exec(b->left, -1, bracket);
printf(" and then ");
- print_exec(b->right, -1, 0);
+ print_exec(b->right, -1, bracket);
+ if (bracket) printf(")");
break;
case Or:
- print_exec(b->left, -1, 0);
+ if (bracket) printf("(");
+ print_exec(b->left, -1, bracket);
printf(" or ");
- print_exec(b->right, -1, 0);
+ print_exec(b->right, -1, bracket);
+ if (bracket) printf(")");
break;
case OrElse:
- print_exec(b->left, -1, 0);
+ if (bracket) printf("(");
+ print_exec(b->left, -1, bracket);
printf(" or else ");
- print_exec(b->right, -1, 0);
+ print_exec(b->right, -1, bracket);
+ if (bracket) printf(")");
break;
case Not:
+ if (bracket) printf("(");
printf("not ");
- print_exec(b->right, -1, 0);
+ print_exec(b->right, -1, bracket);
+ if (bracket) printf(")");
break;
###### propagate binode cases
/* both must be Tbool, result is Tbool */
propagate_types(b->left, c, ok, Tbool, 0);
propagate_types(b->right, c, ok, Tbool, 0);
- if (type && type != Tbool) {
+ if (type && type != Tbool)
type_err(c, "error: %1 operation found where %2 expected", prog,
Tbool, 0, type);
- *ok = 0;
- }
return Tbool;
###### interp binode cases
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
Eql,
NEql,
-###### other BFact
- | Expr CMPop Expr ${ {
- struct binode *b = new(binode);
- b->op = $2.op;
- b->left = $<1;
- b->right = $<3;
- $0 = b;
+###### expr precedence
+ $LEFT < > <= >= == != CMPop
+
+###### expression grammar
+ | Expression CMPop Expression ${ {
+ struct binode *b = new(binode);
+ b->op = $2.op;
+ b->left = $<1;
+ b->right = $<3;
+ $0 = b;
} }$
- | Expr ${ $0 = $<1; }$
###### Grammar
case GtrEq:
case Eql:
case NEql:
- print_exec(b->left, -1, 0);
+ if (bracket) printf("(");
+ print_exec(b->left, -1, bracket);
switch(b->op) {
case Less: printf(" < "); break;
case LessEq: printf(" <= "); break;
case NEql: printf(" != "); break;
default: abort(); // NOTEST
}
- print_exec(b->right, -1, 0);
+ print_exec(b->right, -1, bracket);
+ if (bracket) printf(")");
break;
###### propagate binode cases
if (t)
t = propagate_types(b->left, c, ok, t, 0);
}
- if (!type_compat(type, Tbool, 0)) {
+ if (!type_compat(type, Tbool, 0))
type_err(c, "error: Comparison returns %1 but %2 expected", prog,
Tbool, rules, type);
- *ok = 0;
- }
return Tbool;
###### interp binode cases
Absolute, Negate,
Bracket,
-###### Grammar
+###### expr precedence
+ $LEFT + - Eop
+ $LEFT * / % ++ Top
+ $LEFT Uop
- $*exec
- Expr -> Expr Eop Term ${ {
+###### expression grammar
+ | Expression Eop Expression ${ {
struct binode *b = new(binode);
b->op = $2.op;
b->left = $<1;
b->right = $<3;
$0 = b;
} }$
- | Term ${ $0 = $<1; }$
- Term -> Term Top Factor ${ {
+ | Expression Top Expression ${ {
struct binode *b = new(binode);
b->op = $2.op;
b->left = $<1;
b->right = $<3;
$0 = b;
} }$
- | Factor ${ $0 = $<1; }$
- Factor -> ( Expression ) ${ {
+ | ( Expression ) ${ {
struct binode *b = new_pos(binode, $1);
b->op = Bracket;
b->right = $<2;
$0 = b;
} }$
- | Uop Factor ${ {
+ | Uop Expression ${ {
struct binode *b = new(binode);
b->op = $1.op;
b->right = $<2;
case Divide:
case Concat:
case Rem:
- print_exec(b->left, indent, 0);
+ if (bracket) printf("(");
+ print_exec(b->left, indent, bracket);
switch(b->op) {
case Plus: fputs(" + ", stdout); break;
case Minus: fputs(" - ", stdout); break;
case Concat: fputs(" ++ ", stdout); break;
default: abort(); // NOTEST
} // NOTEST
- print_exec(b->right, indent, 0);
+ print_exec(b->right, indent, bracket);
+ if (bracket) printf(")");
break;
case Absolute:
+ if (bracket) printf("(");
printf("+");
- print_exec(b->right, indent, 0);
+ print_exec(b->right, indent, bracket);
+ if (bracket) printf(")");
break;
case Negate:
+ if (bracket) printf("(");
printf("-");
- print_exec(b->right, indent, 0);
+ print_exec(b->right, indent, bracket);
+ if (bracket) printf(")");
break;
case Bracket:
printf("(");
- print_exec(b->right, indent, 0);
+ print_exec(b->right, indent, bracket);
printf(")");
break;
* unary ops fit here too */
propagate_types(b->left, c, ok, Tnum, 0);
propagate_types(b->right, c, ok, Tnum, 0);
- if (!type_compat(type, Tnum, 0)) {
+ if (!type_compat(type, Tnum, 0))
type_err(c, "error: Arithmetic returns %1 but %2 expected", prog,
Tnum, rules, type);
- *ok = 0;
- }
return Tnum;
case Concat:
/* both must be Tstr, result is Tstr */
propagate_types(b->left, c, ok, Tstr, 0);
propagate_types(b->right, c, ok, Tstr, 0);
- if (!type_compat(type, Tstr, 0)) {
+ if (!type_compat(type, Tstr, 0))
type_err(c, "error: Concat returns %1 but %2 expected", prog,
Tstr, rules, type);
- *ok = 0;
- }
return Tstr;
case Bracket:
rv.str = text_join(left.str, right.str);
break;
-
###### value functions
static struct text text_join(struct text a, struct text b)
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.
###### Grammar
- $void
- OptNL -> Newlines
- |
-
- Newlines -> NEWLINE
- | Newlines NEWLINE
-
$*binode
- Open -> {
- | NEWLINE {
- Close -> }
- | NEWLINE }
Block -> Open Statementlist Close ${ $0 = $<2; }$
- | Open Newlines Statementlist Close ${ $0 = $<3; }$
| Open SimpleStatements } ${ $0 = reorder_bilist($<2); }$
- | Open Newlines SimpleStatements } ${ $0 = reorder_bilist($<3); }$
- | : Statementlist ${ $0 = $<2; }$
| : SimpleStatements ${ $0 = reorder_bilist($<2); }$
+ | : Statementlist $$OUT ${ $0 = $<2; }$
Statementlist -> ComplexStatements ${ $0 = reorder_bilist($<1); }$
+ | Newlines ComplexStatements ${ $0 = reorder_bilist($<2); }$
ComplexStatements -> ComplexStatements ComplexStatement ${
- $0 = new(binode);
- $0->op = Block;
- $0->left = $<1;
- $0->right = $<2;
+ if ($2 == NULL) {
+ $0 = $<1;
+ } else {
+ $0 = new(binode);
+ $0->op = Block;
+ $0->left = $<1;
+ $0->right = $<2;
+ }
}$
- | ComplexStatements NEWLINE ${ $0 = $<1; }$
| ComplexStatement ${
- $0 = new(binode);
- $0->op = Block;
- $0->left = NULL;
- $0->right = $<1;
+ if ($1 == NULL) {
+ $0 = NULL;
+ } else {
+ $0 = new(binode);
+ $0->op = Block;
+ $0->left = NULL;
+ $0->right = $<1;
+ }
}$
$*exec
- ComplexStatement -> SimpleStatements NEWLINE ${
+ ComplexStatement -> SimpleStatementLine ${
$0 = reorder_bilist($<1);
}$
+ | ComplexStatement NEWLINE ${ $0 = $<1; }$
## ComplexStatement Grammar
$*binode
$0->left = NULL;
$0->right = $<1;
}$
- | SimpleStatements ; ${ $0 = $<1; }$
+
+ SimpleStatementLine -> SimpleStatements NEWLINE ${ $0 = $<1; }$
+ | SimpleStatements ; NEWLINE ${ $0 = $<1; }$
+ | SimpleStatementLine NEWLINE ${ $0 = $<1; }$
SimpleStatement -> pass ${ $0 = NULL; }$
+ | ERROR ${ tok_err(c, "Syntax error in statement", &$1); }$
## SimpleStatement Grammar
###### print binode cases
if (b->left == NULL)
printf("pass");
else
- print_exec(b->left, indent, 0);
+ print_exec(b->left, indent, bracket);
if (b->right) {
printf("; ");
- print_exec(b->right, indent, 0);
+ print_exec(b->right, indent, bracket);
}
} else {
// block, one per line
if (t && t != Tnone && t != Tbool) {
if (!type)
type = t;
- else if (t != type) {
+ else if (t != type)
type_err(c, "error: expected %1%r, found %2",
e->left, type, rules, t);
- *ok = 0;
- }
}
}
return type;
while (b) {
if (b->left) {
printf(" ");
- print_exec(b->left, -1, 0);
+ print_exec(b->left, -1, bracket);
if (b->right)
printf(",");
}
| VariableDecl ${
if ($1->var->where_set == NULL) {
- type_err(config2context(config), "Variable declared with no type or value: %v",
+ type_err(c,
+ "Variable declared with no type or value: %v",
$1, NULL, 0, NULL);
} else {
$0 = new(binode);
case Assign:
do_indent(indent, "");
- print_exec(b->left, indent, 0);
+ print_exec(b->left, indent, bracket);
printf(" = ");
- print_exec(b->right, indent, 0);
+ print_exec(b->right, indent, bracket);
if (indent >= 0)
printf("\n");
break;
{
struct variable *v = cast(var, b->left)->var;
do_indent(indent, "");
- print_exec(b->left, indent, 0);
+ print_exec(b->left, indent, bracket);
if (cast(var, b->left)->var->constant) {
if (v->where_decl == v->where_set) {
printf("::");
}
if (b->right) {
printf("= ");
- print_exec(b->right, indent, 0);
+ print_exec(b->right, indent, bracket);
}
if (indent >= 0)
printf("\n");
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)
propagate_types(b->left, c, ok, t,
(b->op == Assign ? Rnoconstant : 0));
}
- if (t && t->dup == NULL) {
+ if (t && t->dup == NULL)
type_err(c, "error: cannot assign value of type %1", b, t, 0, NULL);
- *ok = 0;
- }
return Tnone;
break;
$0 = new_pos(binode, $1);
$0->op = Use;
$0->right = $<2;
+ if ($0->right->type == Xvar) {
+ struct var *v = cast(var, $0->right);
+ if (v->var->val.type == Tnone) {
+ /* Convert this to a label */
+ v->var->val = val_prepare(Tlabel);
+ v->var->val.label = &v->var->val;
+ }
+ }
}$
###### print binode cases
case Use:
do_indent(indent, "use ");
- print_exec(b->right, -1, 0);
+ print_exec(b->right, -1, bracket);
if (indent >= 0)
printf("\n");
break;
case Xcond_statement: free_cond_statement(cast(cond_statement, e)); break;
###### ComplexStatement Grammar
- | CondStatement ${ $0 = $<1; }$
+ | CondStatement $$NEWLINE ${ $0 = $<1; }$
###### Grammar
$*cond_statement
// both ForThen and Whilepart open scopes, and CondSuffix only
// closes one - so in the first branch here we have another to close.
- CondStatement -> ForThen WhilePart CondSuffix ${
+ CondStatement -> ForPart ThenPart WhilePart CondSuffix ${
+ $0 = $<4;
+ $0->forpart = $<1;
+ $0->thenpart = $<2;
+ $0->condpart = $3.condpart; $3.condpart = NULL;
+ $0->dopart = $3.dopart; $3.dopart = NULL;
+ var_block_close(c, CloseSequential);
+ }$
+ | ForPart WhilePart CondSuffix ${
$0 = $<3;
- $0->forpart = $1.forpart; $1.forpart = NULL;
- $0->thenpart = $1.thenpart; $1.thenpart = NULL;
+ $0->forpart = $<1;
+ $0->thenpart = NULL;
$0->condpart = $2.condpart; $2.condpart = NULL;
$0->dopart = $2.dopart; $2.dopart = NULL;
- var_block_close(config2context(config), CloseSequential);
+ var_block_close(c, CloseSequential);
}$
| WhilePart CondSuffix ${
$0 = $<2;
$0->condpart = $1.condpart; $1.condpart = NULL;
$0->dopart = $1.dopart; $1.dopart = NULL;
}$
- | SwitchPart CondSuffix ${
- $0 = $<2;
+ | SwitchPart CasePart CondSuffix ${
+ $0 = $<3;
$0->condpart = $<1;
+ $2->next = $0->casepart;
+ $0->casepart = $<2;
}$
| IfPart IfSuffix ${
$0 = $<2;
$0->condpart = $1.condpart; $1.condpart = NULL;
$0->thenpart = $1.thenpart; $1.thenpart = NULL;
// This is where we close an "if" statement
- var_block_close(config2context(config), CloseSequential);
+ var_block_close(c, CloseSequential);
}$
CondSuffix -> IfSuffix ${
$0 = $<1;
// This is where we close scope of the whole
// "for" or "while" statement
- var_block_close(config2context(config), CloseSequential);
+ var_block_close(c, CloseSequential);
}$
| CasePart CondSuffix ${
$0 = $<2;
}$
$*casepart
- CasePart -> Newlines case Expression OpenScope Block ${
- $0 = calloc(1,sizeof(struct casepart));
- $0->value = $<3;
- $0->action = $<5;
- var_block_close(config2context(config), CloseParallel);
- }$
- | case Expression OpenScope Block ${
+ CasePart -> case Expression OpenScope Block ${
$0 = calloc(1,sizeof(struct casepart));
$0->value = $<2;
$0->action = $<4;
- var_block_close(config2context(config), CloseParallel);
+ var_block_close(c, CloseParallel);
}$
+ | CasePart NEWLINE ${ $0 = $<1; }$
$*cond_statement
- IfSuffix -> Newlines ${ $0 = new(cond_statement); }$
- | Newlines else OpenScope Block ${
- $0 = new(cond_statement);
- $0->elsepart = $<4;
- var_block_close(config2context(config), CloseElse);
- }$
+ IfSuffix -> ${ $0 = new(cond_statement); }$
+ | IfSuffix NEWLINE ${ $0 = $<1; }$
| else OpenScope Block ${
$0 = new(cond_statement);
$0->elsepart = $<3;
- var_block_close(config2context(config), CloseElse);
- }$
- | Newlines else OpenScope CondStatement ${
- $0 = new(cond_statement);
- $0->elsepart = $<4;
- var_block_close(config2context(config), CloseElse);
+ var_block_close(c, CloseElse);
}$
| else OpenScope CondStatement ${
$0 = new(cond_statement);
$0->elsepart = $<3;
- var_block_close(config2context(config), CloseElse);
+ var_block_close(c, CloseElse);
}$
-
$*exec
// These scopes are closed in CondSuffix
- ForPart -> for OpenScope SimpleStatements ${
+ ForPart -> for OpenScope SimpleStatements ; ${
+ $0 = reorder_bilist($<3);
+ }$
+ | for OpenScope SimpleStatements NEWLINE ${
$0 = reorder_bilist($<3);
}$
| for OpenScope Block ${
$0 = $<3;
}$
+ | ForPart NEWLINE ${ $0 = $<1; }$
- ThenPart -> then OpenScope SimpleStatements ${
+ ThenPart -> then OpenScope SimpleStatements ; ${
+ $0 = reorder_bilist($<3);
+ var_block_close(c, CloseSequential);
+ }$
+ | then OpenScope SimpleStatements NEWLINE ${
$0 = reorder_bilist($<3);
- var_block_close(config2context(config), CloseSequential);
+ var_block_close(c, CloseSequential);
}$
| then OpenScope Block ${
$0 = $<3;
- var_block_close(config2context(config), CloseSequential);
- }$
-
- ThenPartNL -> ThenPart OptNL ${
- $0 = $<1;
+ var_block_close(c, CloseSequential);
}$
+ | ThenPart NEWLINE ${ $0 = $<1; }$
// This scope is closed in CondSuffix
- WhileHead -> while OpenScope Block ${
- $0 = $<3;
- }$
+ WhileHead -> while OpenScope Block ${ $0 = $<3; }$
+ | WhileHead NEWLINE ${ $0 = $<1; }$
$cond_statement
- ForThen -> ForPart OptNL ThenPartNL ${
- $0.forpart = $<1;
- $0.thenpart = $<3;
- }$
- | ForPart OptNL ${
- $0.forpart = $<1;
- }$
-
// This scope is closed in CondSuffix
WhilePart -> while OpenScope Expression Block ${
$0.type = Xcond_statement;
$0.condpart = $<3;
$0.dopart = $<4;
}$
- | WhileHead OptNL do Block ${
+ | WhileHead do Block ${
$0.type = Xcond_statement;
$0.condpart = $<1;
- $0.dopart = $<4;
+ $0.dopart = $<3;
}$
+ | WhilePart NEWLINE ${ $0 = $<1; }$
IfPart -> if OpenScope Expression OpenScope Block ${
$0.type = Xcond_statement;
$0.condpart = $<3;
$0.thenpart = $<5;
- var_block_close(config2context(config), CloseParallel);
+ var_block_close(c, CloseParallel);
}$
- | if OpenScope Block OptNL then OpenScope Block ${
+ | if OpenScope Block then OpenScope Block ${
$0.type = Xcond_statement;
$0.condpart = $<3;
- $0.thenpart = $<7;
- var_block_close(config2context(config), CloseParallel);
+ $0.thenpart = $<6;
+ var_block_close(c, CloseParallel);
}$
+ | IfPart NEWLINE ${ $0 = $<1; }$
$*exec
// This scope is closed in CondSuffix
| switch OpenScope Block ${
$0 = $<3;
}$
+ | SwitchPart NEWLINE ${ $0 = $<1; }$
###### print exec cases
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.
$void
Ocean -> DeclarationList
+ | Newlines DeclarationList
DeclarationList -> Declaration
| DeclarationList Declaration
- Declaration -> DeclareConstant
+ Declaration -> Declaration NEWLINE
+ | DeclareConstant
| DeclareProgram
- | NEWLINE
+ | DeclareStruct
+ | ERROR NEWLINE ${
+ tok_err(c,
+ "error: unhandled parse error", &$1);
+ }$
## top level grammar
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
###### top level grammar
DeclareConstant -> const Open ConstList Close
- | const Open Newlines ConstList Close
| const Open SimpleConstList }
- | const Open Newlines SimpleConstList }
- | const : ConstList
- | const SimpleConstList
-
- ConstList -> ComplexConsts
- ComplexConsts -> ComplexConst ComplexConsts
- | ComplexConst
- ComplexConst -> SimpleConstList NEWLINE
- SimpleConstList -> Const ; SimpleConstList
+ | const : ConstList $$NEWLINE
+ | const SimpleConstList NEWLINE
+
+ ConstList -> ConstLines
+ | Newlines ConstLines
+ ConstLines -> ConstLines SimpleConstLine
+ | SimpleConstLine
+ SimpleConstList -> SimpleConstList ; Const
| Const
- | Const ; SimpleConstList ;
+ | SimpleConstList ;
+ SimpleConstLine -> SimpleConstList NEWLINE
+ | SimpleConstLine NEWLINE
+ | ERROR NEWLINE ${ tok_err(c, "Syntax error in constant", &$1); }$
$*type
CType -> Type ${ $0 = $<1; }$
int ok;
struct variable *v;
- v = var_decl(config2context(config), $1.txt);
+ v = var_decl(c, $1.txt);
if (v) {
struct var *var = new_pos(var, $1);
v->where_decl = var;
var->var = v;
v->constant = 1;
} else {
- v = var_ref(config2context(config), $1.txt);
- tok_err(config2context(config), "error: name already declared", &$1);
- type_err(config2context(config), "info: this is where '%v' was first declared",
+ v = var_ref(c, $1.txt);
+ tok_err(c, "error: name already declared", &$1);
+ type_err(c, "info: this is where '%v' was first declared",
v->where_decl, NULL, 0, NULL);
}
do {
ok = 1;
- propagate_types($5, config2context(config), &ok, $3, 0);
+ propagate_types($5, c, &ok, $3, 0);
} while (ok == 2);
if (!ok)
- config2context(config)->parse_error = 1;
+ c->parse_error = 1;
else if (v) {
v->val = interp_exec($5);
}
} }$
-###### print decls
+###### print const decls
{
struct variable *v;
int target = -1;
###### top level grammar
DeclareProgram -> Program ${ {
- struct parse_context *c = config2context(config);
if (c->prog)
type_err(c, "Program defined a second time",
$1, NULL, 0, NULL);
c->prog = $<1;
} }$
-
$*binode
- Program -> program OpenScope Varlist Block OptNL ${
+ Program -> program OpenScope Varlist Block ${
$0 = new(binode);
$0->op = Program;
$0->left = reorder_bilist($<3);
$0->right = $<4;
- var_block_close(config2context(config), CloseSequential);
- if (config2context(config)->scope_stack) abort();
- }$
- | ERROR ${
- tok_err(config2context(config),
- "error: unhandled parse error", &$1);
+ var_block_close(c, CloseSequential);
+ if (c->scope_stack && !c->parse_error) abort();
}$
Varlist -> Varlist ArgDecl ${
$*var
ArgDecl -> IDENTIFIER ${ {
- struct variable *v = var_decl(config2context(config), $1.txt);
+ struct variable *v = var_decl(c, $1.txt);
$0 = new(var);
$0->var = v;
} }$
## 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
+ demos :: sayhello
sayhello : oceani
- @echo "===== TEST ====="
- ./oceani --section "test: hello" oceani.mdc 55 33
+ @echo "===== DEMO ====="
+ ./oceani --section "demo: hello" oceani.mdc 55 33
-###### test: hello
+###### demo: hello
const:
- pi ::= 3.1415926
+ pi ::= 3.141_592_6
four ::= 2 + 2 ; five ::= 10/2
const pie ::= "I like Pie";
cake ::= "The cake is"
++ " a lie"
+ struct fred:
+ size:[four]number
+ name:string
+ alive:Boolean
+
program A B:
print "Hello World, what lovely oceans you have!"
- print "are there", five, "?"
+ print "Are there", five, "?"
print pi, pie, "but", cake
/* When a variable is defined in both branches of an 'if',
case GiveUp:
print "Closest I found was", mid
- size::=55
+ size::= 10
list:[size]number
list[0] = 1234
+ // "middle square" PRNG. Not particularly good, but one my
+ // Dad taught me - the first one I ever heard of.
for i:=1; then i = i + 1; while i < size:
n := list[i-1] * list[i-1]
- list[i] = (n / 100) % 10000
+ list[i] = (n / 100) % 10 000
- print "Before sort:"
+ print "Before sort:",
for i:=0; then i = i + 1; while i < size:
- print "list[",i,"]=",list[i]
+ print "", list[i],
+ print
for i := 1; then i=i+1; while i < size:
for j:=i-1; then j=j-1; while j >= 0:
t:= list[j]
list[j] = list[j+1]
list[j+1] = t
- print "After sort:"
+ print " After sort:",
for i:=0; then i = i + 1; while i < size:
- print "list[",i,"]=",list[i]
+ print "", list[i],
+ print
+
+ if 1 == 2: print "yes" else: print "no"
+
+ bob:fred
+ bob.name = "Hello"
+ bob.alive = (bob.name == "Hello")
+ print "bob", "is" if bob.alive else "isn't", "alive"