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
- 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`.
{NULL, 0, NULL, 0},
};
const char *options = "tpnbs";
+
+ static void pr_err(char *msg) // NOTEST
+ {
+ fprintf(stderr, "%s\n", msg); // NOTEST
+ } // NOTEST
+
int main(int argc, char *argv[])
{
int fd;
context.file_name = argv[optind];
len = lseek(fd, 0, 2);
file = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
- s = code_extract(file, file+len, NULL);
+ s = code_extract(file, file+len, pr_err);
if (!s) {
fprintf(stderr, "oceani: could not find any code in %s\n",
argv[optind]);
exit(1);
}
} else
- ss = s;
+ ss = s; // NOTEST
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) {
fprintf(stderr, "oceani: type error in program - not running.\n");
exit(1);
}
- interp_prog(&context, context.prog, argv+optind+1);
+ interp_prog(&context, context.prog, argc - optind, argv+optind);
}
free_exec(context.prog);
}
## free context vars
## free context types
+ ## free context storage
exit(context.parse_error ? 1 : 0);
}
static void type_print(struct type *type, FILE *f)
{
if (!type)
- fputs("*unknown*type*", f);
+ fputs("*unknown*type*", f); // NOTEST
else if (type->name.len)
fprintf(f, "%.*s", type->name.len, type->name.txt);
else if (type->print_type)
type->init(type, val);
}
- static void dup_value(struct type *type,
+ static void dup_value(struct type *type,
struct value *vold, struct value *vnew)
{
if (type && type->dup)
{
if (tl && tl->cmp_order)
return tl->cmp_order(tl, tr, left, right);
- if (tl && tl->cmp_eq)
- return tl->cmp_eq(tl, tr, left, right);
- return -1;
+ if (tl && tl->cmp_eq) // NOTEST
+ return tl->cmp_eq(tl, tr, left, right); // NOTEST
+ return -1; // NOTEST
}
static void print_value(struct type *type, struct value *v)
printf("*Unknown*"); // NOTEST
}
- static struct value *val_alloc(struct parse_context *c, struct type *t,
- struct value *init)
- {
- struct value *ret;
-
- if (t->prepare_type)
- t->prepare_type(c, t, 0);
-
- ret = calloc(1, t->size);
- if (init)
- memcpy(ret, init, t->size);
- else
- val_init(t, ret);
- return ret;
- }
-
###### forward decls
static void free_value(struct type *type, struct value *v);
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
static int type_compat(struct type *require, struct type *have, int rules)
{
if ((rules & Rboolok) && have == Tbool)
- return 1;
+ return 1; // NOTEST
if ((rules & Rnolabel) && have == Tlabel)
- return 0;
+ return 0; // NOTEST
if (!require || !have)
return 1;
static void _free_value(struct type *type, struct value *v)
{
if (!v)
- return;
+ return; // NOTEST
switch (type->vtype) {
case Vnone: break;
case Vstr: free(v->str.txt); break;
case Vbool:
val->bool = 0;
break;
- case Vlabel: // NOTEST
- val->label = NULL; // NOTEST
- break; // NOTEST
+ case Vlabel:
+ val->label = NULL;
+ break;
}
}
t->size = size;
t->align = size > sizeof(void*) ? sizeof(void*) : size;
if (t->size & (t->align - 1))
- t->size = (t->size | (t->align - 1)) + 1;
+ t->size = (t->size | (t->align - 1)) + 1; // NOTEST
return t;
}
struct variable {
struct variable *previous;
struct type *type;
- struct value *val;
struct binding *name;
struct exec *where_decl;// where name was declared
struct exec *where_set; // where type was set
is found. Instead, they are detected and ignored when considering the
list of in_scope names.
+The storage of the value of a variable will be described later. For now
+we just need to know that when a variable goes out of scope, it might
+need to be freed. For this we need to be able to find it, so assume that
+`var_value()` will provide that.
+
###### variable fields
struct variable *merged;
if (primary->merged)
// shouldn't happen
- primary = primary->merged;
+ primary = primary->merged; // NOTEST
for (v = primary->previous; v; v=v->previous)
if (v == secondary || v == secondary->merged ||
}
}
+###### forward decls
+ static struct value *var_value(struct parse_context *c, struct variable *v);
+
###### free context vars
while (context.varlist) {
struct variable *t = v;
v = t->previous;
- free_value(t->type, t->val);
- free(t->val);
+ free_value(t->type, var_value(&context, t));
if (t->depth == 0)
// This is a global constant
free_exec(t->where_decl);
v->scope = InScope;
v->in_scope = c->in_scope;
c->in_scope = v;
- v->val = NULL;
return v;
}
else if (v->previous &&
v->previous->scope == PendingScope)
v->scope = PendingScope;
- else if (v->type == Tlabel)
- v->scope = PendingScope;
- else if (v->name->var == v)
- v->scope = OutScope;
+ else if (v->type == Tlabel) // UNTESTED
+ v->scope = PendingScope; // UNTESTED
+ else if (v->name->var == v) // UNTESTED
+ v->scope = OutScope; // UNTESTED
if (ct == CloseElse) {
/* All Pending variables with this name
* are now Conditional */
if (v2->type != Tlabel)
v2->scope = OutScope;
break;
- case OutScope: break;
+ case OutScope: break; // UNTESTED
}
break;
case CloseSequential:
}
}
+#### Storing Values
+
+The value of a variable is store separately from the variable, on an
+analogue of a stack frame. There are (currently) two frames that can be
+active. A global frame which currently only stores constants, and a
+stacked frame which stores local variables. Each variable knows if it
+is global or not, and what its index into the frame is.
+
+Values in the global frame are known immediately they are relevant, so
+the frame needs to be reallocated as it grows so it can store those
+values. The local frame doesn't get values until the interpreted phase
+is started, so there is no need to allocate until the size is known.
+
+###### variable fields
+ short frame_pos;
+ short global;
+
+###### parse context
+
+ short global_size, global_alloc;
+ short local_size;
+ void *global, *local;
+
+###### ast functions
+
+ static struct value *var_value(struct parse_context *c, struct variable *v)
+ {
+ if (!v->global) {
+ if (!c->local || !v->type)
+ return NULL;
+ if (v->frame_pos + v->type->size > c->local_size) {
+ printf("INVALID frame_pos\n"); // NOTEST
+ exit(2); // NOTEST
+ }
+ return c->local + v->frame_pos;
+ }
+ if (c->global_size > c->global_alloc) {
+ int old = c->global_alloc;
+ c->global_alloc = (c->global_size | 1023) + 1024;
+ c->global = realloc(c->global, c->global_alloc);
+ memset(c->global + old, 0, c->global_alloc - old);
+ }
+ return c->global + v->frame_pos;
+ }
+
+ static struct value *global_alloc(struct parse_context *c, struct type *t,
+ struct variable *v, struct value *init)
+ {
+ struct value *ret;
+ struct variable scratch;
+
+ if (t->prepare_type)
+ t->prepare_type(c, t, 1); // NOTEST
+
+ if (c->global_size & (t->align - 1))
+ c->global_size = (c->global_size + t->align) & ~(t->align-1); // UNTESTED
+ if (!v) {
+ v = &scratch;
+ v->type = t;
+ }
+ v->frame_pos = c->global_size;
+ v->global = 1;
+ c->global_size += v->type->size;
+ ret = var_value(c, v);
+ if (init)
+ memcpy(ret, init, t->size);
+ else
+ val_init(t, ret);
+ return ret;
+ }
+
+As global values are found -- struct field initializers, labels etc --
+`global_alloc()` is called to record the value in the global frame.
+
+When the program is fully parsed, we need to walk the list of variables
+to find any that weren't merged away and that aren't global, and to
+calculate the frame size and assign a frame position for each variable.
+For this we have `scope_finalize()`.
+
+###### ast functions
+
+ static void scope_finalize(struct parse_context *c)
+ {
+ struct binding *b;
+
+ for (b = c->varlist; b; b = b->next) {
+ struct variable *v;
+ for (v = b->var; v; v = v->previous) {
+ struct type *t = v->type;
+ if (v->merged && v->merged != v)
+ continue;
+ if (v->global)
+ continue;
+ if (c->local_size & (t->align - 1))
+ c->local_size = (c->local_size + t->align) & ~(t->align-1);
+ v->frame_pos = c->local_size;
+ c->local_size += v->type->size;
+ }
+ }
+ c->local = calloc(1, c->local_size);
+ }
+
+###### free context storage
+ free(context.global);
+ free(context.local);
+
### Executables
Executables can be lots of different things. In many cases an
static int __fput_loc(struct exec *loc, FILE *f)
{
if (!loc)
- return 0; // NOTEST
+ return 0;
if (loc->line >= 0) {
fprintf(f, "%d:%d: ", loc->line, loc->column);
return 1;
}
if (loc->type == Xbinode)
return __fput_loc(cast(binode,loc)->left, f) ||
- __fput_loc(cast(binode,loc)->right, f);
- return 0;
+ __fput_loc(cast(binode,loc)->right, f); // NOTEST
+ return 0; // NOTEST
}
static void fput_loc(struct exec *loc, FILE *f)
{
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`.
set `lval` to NULL indicating that there is a value of appropriate type
in `rval`.
-
###### core functions
struct lrval {
rvtype = ret.type = Tnone;
if (!e) {
- ret.lval = lrv;
- ret.rval = rv;
- return ret;
+ ret.lval = lrv; // UNTESTED
+ ret.rval = rv; // UNTESTED
+ return ret; // UNTESTED
}
switch(e->type) {
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
###### type union fields
struct {
+ int unspec; // size is unspecified - vsize must be set.
short size;
short static_size;
struct variable *vsize;
static void array_prepare_type(struct parse_context *c, struct type *type,
int parse_time)
{
+ struct value *vsize;
mpz_t q;
if (!type->array.vsize || type->array.static_size)
return;
+ vsize = var_value(c, type->array.vsize);
mpz_init(q);
- mpz_tdiv_q(q, mpq_numref(type->array.vsize->val->num),
- mpq_denref(type->array.vsize->val->num));
+ mpz_tdiv_q(q, mpq_numref(vsize->num), mpq_denref(vsize->num));
type->array.size = mpz_get_si(q);
mpz_clear(q);
void *ptr = val->ptr;
if (!val)
- return;
+ return; // NOTEST
if (!type->array.static_size) {
- val->array = calloc(type->array.size,
+ val->array = calloc(type->array.size,
type->array.member->size);
ptr = val->array;
}
static int array_compat(struct type *require, struct type *have)
{
if (have->compat != require->compat)
- return 0;
+ return 0; // UNTESTED
/* 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) // UNTESTED
+ /* sizes might not be the same */
+ return 0; // UNTESTED
+ return 1;
+ }
+ if (have->array.unspec || require->array.unspec)
+ return 1; // UNTESTED
if (require->array.vsize == NULL && have->array.vsize == NULL)
return require->array.size == have->array.size;
- return require->array.vsize == have->array.vsize;
+ return require->array.vsize == have->array.vsize; // UNTESTED
}
static void array_print_type(struct type *type, FILE *f)
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);
$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,
if (t->structure.fields[i].init) {
free_value(t->structure.fields[i].type,
t->structure.fields[i].init);
- free(t->structure.fields[i].init);
}
free(t->structure.fields);
}
struct type *st = propagate_types(f->left, c, ok, NULL, 0);
if (!st)
- type_err(c, "error: unknown type for field access", f->left,
+ type_err(c, "error: unknown type for field access", f->left, // UNTESTED
NULL, 0, NULL);
else if (st->init != structure_init)
type_err(c, "error: field reference attempted on %1, not a struct",
return;
free_fieldlist(f->prev);
if (f->f.init) {
- free_value(f->f.type, f->f.init);
- free(f->f.init);
+ free_value(f->f.type, f->f.init); // UNTESTED
+ free(f->f.init); // UNTESTED
}
free(f);
}
| ERROR ${ tok_err(c, "Syntax error in struct field", &$1); }$
Field -> IDENTIFIER : Type = Expression ${ {
- int ok;
+ int ok; // UNTESTED
$0 = calloc(1, sizeof(struct fieldlist));
$0->f.name = $1.txt;
propagate_types($<5, c, &ok, $3, 0);
} while (ok == 2);
if (!ok)
- c->parse_error = 1;
+ c->parse_error = 1; // UNTESTED
else {
struct value vl = interp_exec(c, $5, NULL);
- $0->f.init = val_alloc(c, $0->f.type, &vl);
+ $0->f.init = global_alloc(c, $0->f.type, NULL, &vl);
}
} }$
| IDENTIFIER : Type ${
static void structure_print_type(struct type *t, FILE *f);
###### value functions
- static void structure_print_type(struct type *t, FILE *f)
- {
- int i;
+ static void structure_print_type(struct type *t, FILE *f) // UNTESTED
+ { // UNTESTED
+ int i; // UNTESTED
fprintf(f, "struct %.*s\n", t->name.len, t->name.txt);
if (fl->type->print && fl->init) {
fprintf(f, " = ");
if (fl->type == Tstr)
- fprintf(f, "\"");
+ fprintf(f, "\""); // UNTESTED
print_value(fl->type, fl->init);
if (fl->type == Tstr)
- fprintf(f, "\"");
+ fprintf(f, "\""); // UNTESTED
}
printf("\n");
}
}
###### print type decls
- {
- struct type *t;
+ { // UNTESTED
+ struct type *t; // UNTESTED
int target = -1;
while (target != 0) {
}
}
+### 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 ${ // UNTESTED
+ $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,
{
char tail[3];
if (number_parse($0->val.num, tail, $1.txt) == 0)
- mpq_init($0->val.num);
+ mpq_init($0->val.num); // UNTESTED
if (tail[0])
tok_err(c, "error: unsupported number suffix",
&$1);
v->where_decl = $0;
v->where_set = $0;
v->type = $<Type;
- v->val = NULL;
} else {
v = var_ref(c, $1.txt);
$0->var = v;
v->where_decl = $0;
v->where_set = $0;
v->type = $<Type;
- v->val = NULL;
v->constant = 1;
} else {
v = var_ref(c, $1.txt);
/* This might be a label - allocate a var just in case */
v = var_decl(c, $1.txt);
if (v) {
- v->val = NULL;
v->type = Tnone;
v->where_decl = $0;
v->where_set = $0;
} }$
## 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:
{
###### 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;
if (v->type == NULL) {
if (type && *ok != 0) {
v->type = type;
- v->val = NULL;
v->where_set = prog;
*ok = 2;
}
struct variable *v = var->var;
if (v->merged)
- v = v->merged;
- lrv = v->val;
+ v = v->merged; // UNTESTED
+ lrv = var_value(c, v);
rvtype = v->type;
break;
}
struct binode *b2 = cast(binode, b->right);
left = interp_exec(c, b->left, <ype);
if (left.bool)
- rv = interp_exec(c, b2->left, &rvtype);
+ rv = interp_exec(c, b2->left, &rvtype); // UNTESTED
else
rv = interp_exec(c, b2->right, &rvtype);
}
if (t)
propagate_types(b->right, c, ok, t, 0);
else {
- t = propagate_types(b->right, c, ok, NULL, Rnolabel);
- if (t)
- t = propagate_types(b->left, c, ok, t, 0);
+ t = propagate_types(b->right, c, ok, NULL, Rnolabel); // UNTESTED
+ if (t) // UNTESTED
+ t = propagate_types(b->left, c, ok, t, 0); // UNTESTED
}
if (!type_compat(type, Tbool, 0))
type_err(c, "error: Comparison returns %1 but %2 expected", prog,
/* op must be string, result is number */
propagate_types(b->left, c, ok, Tstr, 0);
if (!type_compat(type, Tnum, 0))
- type_err(c,
+ type_err(c, // UNTESTED
"error: Can only convert string to number, not %1",
prog, type, 0, NULL);
return Tnum;
char tail[3];
int neg = 0;
if (tx.txt[0] == '-') {
- neg = 1;
- tx.txt++;
- tx.len--;
+ neg = 1; // UNTESTED
+ tx.txt++; // UNTESTED
+ tx.len--; // UNTESTED
}
if (number_parse(rv.num, tail, tx) == 0)
- mpq_init(rv.num);
+ mpq_init(rv.num); // UNTESTED
else if (neg)
- mpq_neg(rv.num, rv.num);
+ mpq_neg(rv.num, rv.num); // UNTESTED
if (tail[0])
- printf("Unsupported suffix: %.*s\n", tx.len, tx.txt);
+ printf("Unsupported suffix: %.*s\n", tx.len, tx.txt); // UNTESTED
break;
case Block:
if (indent < 0) {
// simple statement
- if (b->left == NULL)
- printf("pass");
+ if (b->left == NULL) // UNTESTED
+ printf("pass"); // UNTESTED
else
- print_exec(b->left, indent, bracket);
- if (b->right) {
- printf("; ");
- print_exec(b->right, indent, bracket);
+ print_exec(b->left, indent, bracket); // UNTESTED
+ if (b->right) { // UNTESTED
+ printf("; "); // UNTESTED
+ print_exec(b->right, indent, bracket); // UNTESTED
}
} else {
// block, one per line
case Declare:
{
struct variable *v = cast(var, b->left)->var;
+ struct value *val;
if (v->merged)
v = v->merged;
- free_value(v->type, v->val);
- free(v->val);
+ val = var_value(c, v);
+ free_value(v->type, val);
+ if (v->type->prepare_type)
+ v->type->prepare_type(c, v->type, 0);
if (b->right) {
right = interp_exec(c, b->right, &rtype);
- v->val = val_alloc(c, v->type, &right);
+ memcpy(val, &right, rtype->size);
rtype = Tnone;
} else {
- v->val = val_alloc(c, v->type, NULL);
+ val_init(v->type, val);
}
break;
}
struct var *v = cast(var, $0->right);
if (v->var->type == Tnone) {
/* Convert this to a label */
+ struct value *val;
+
v->var->type = Tlabel;
- v->var->val = val_alloc(c, Tlabel, NULL);
- v->var->val->label = v->var->val;
+ val = global_alloc(c, Tlabel, v->var, NULL);
+ val->label = val;
}
}
}$
// 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 ${
do_indent(indent, "if");
if (cs->condpart && cs->condpart->type == Xbinode &&
cast(binode, cs->condpart)->op == Block) {
- if (bracket)
- printf(" {\n");
+ if (bracket) // UNTESTED
+ printf(" {\n"); // UNTESTED
else
- printf(":\n");
- print_exec(cs->condpart, indent+1, bracket);
- if (bracket)
- do_indent(indent, "}\n");
- if (cs->thenpart) {
- do_indent(indent, "then:\n");
- print_exec(cs->thenpart, indent+1, bracket);
+ printf(":\n"); // UNTESTED
+ print_exec(cs->condpart, indent+1, bracket); // UNTESTED
+ if (bracket) // UNTESTED
+ do_indent(indent, "}\n"); // UNTESTED
+ if (cs->thenpart) { // UNTESTED
+ do_indent(indent, "then:\n"); // UNTESTED
+ print_exec(cs->thenpart, indent+1, bracket); // UNTESTED
}
} else {
printf(" ");
t = propagate_types(cs->forpart, c, ok, Tnone, 0);
if (!type_compat(Tnone, t, 0))
- *ok = 0;
+ *ok = 0; // UNTESTED
t = propagate_types(cs->dopart, c, ok, Tnone, 0);
if (!type_compat(Tnone, t, 0))
- *ok = 0;
+ *ok = 0; // UNTESTED
if (cs->dopart) {
t = propagate_types(cs->thenpart, c, ok, Tnone, 0);
if (!type_compat(Tnone, t, 0))
- *ok = 0;
+ *ok = 0; // UNTESTED
}
if (cs->casepart == NULL)
propagate_types(cs->condpart, c, ok, Tbool, 0);
cp && !t; cp = cp->next)
t = propagate_types(cp->value, c, ok, NULL, 0);
if (!t && cs->condpart)
- t = propagate_types(cs->condpart, c, ok, NULL, Rboolok);
+ t = propagate_types(cs->condpart, c, ok, NULL, Rboolok); // UNTESTED
// Now we have a type (I hope) push it down
if (t) {
for (cp = cs->casepart; cp; cp = cp->next)
type = propagate_types(cs->elsepart, c, ok, NULL, rules);
for (cp = cs->casepart;
cp && !type;
- cp = cp->next)
- type = propagate_types(cp->action, c, ok, NULL, rules);
+ cp = cp->next) // UNTESTED
+ type = propagate_types(cp->action, c, ok, NULL, rules); // UNTESTED
if (type) {
if (!cs->dopart)
propagate_types(cs->thenpart, c, ok, type, rules);
if (cs->condpart)
cnd = interp_exec(c, cs->condpart, &cndtype);
else
- cndtype = Tnone;
+ cndtype = Tnone; // UNTESTED
if (!(cndtype == Tnone ||
(cndtype == Tbool && cnd.bool != 0)))
break;
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
| DeclarationList Declaration
Declaration -> ERROR Newlines ${
- tok_err(c,
+ tok_err(c, // UNTESTED
"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
c->parse_error = 1;
else if (v) {
struct value res = interp_exec(c, $5, &v->type);
- v->val = val_alloc(c, v->type, &res);
+ global_alloc(c, v->type, v, &res);
}
} }$
printf("const\n");
target = i;
} else {
+ struct value *val = var_value(&context, v);
printf(" %.*s :: ", v->name->name.len, v->name->name.txt);
type_print(v->type, stdout);
printf(" = ");
if (v->type == Tstr)
printf("\"");
- print_value(v->type, v->val);
+ print_value(v->type, val);
if (v->type == Tstr)
printf("\"");
printf("\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
static int analyse_prog(struct exec *prog, struct parse_context *c)
{
- struct binode *b = cast(binode, prog);
+ struct binode *bp = cast(binode, prog);
+ struct binode *b;
int ok = 1;
+ int arg = 0;
+ struct type *argv_type;
+ struct text argv_type_name = { " argv", 5 };
- if (!b)
+ if (!bp)
return 0; // NOTEST
- do {
- ok = 1;
- propagate_types(b->right, c, &ok, Tnone, 0);
- } while (ok == 2);
- if (!ok)
- return 0;
- for (b = cast(binode, b->left); b; b = cast(binode, b->right)) {
- struct var *v = cast(var, b->left);
- if (!v->var->type) {
- v->var->where_set = b;
- v->var->type = Tstr;
- v->var->val = NULL;
+ 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)) {
+ ok = 1;
+ switch (arg++) {
+ case 0: /* argv */
+ propagate_types(b->left, c, &ok, argv_type, 0);
+ break;
+ default: /* invalid */ // NOTEST
+ propagate_types(b->left, c, &ok, Tnone, 0); // NOTEST
}
}
- b = cast(binode, prog);
+
do {
ok = 1;
- propagate_types(b->right, c, &ok, Tnone, 0);
+ propagate_types(bp->right, c, &ok, Tnone, 0);
} while (ok == 2);
if (!ok)
return 0;
/* Make sure everything is still consistent */
- propagate_types(b->right, c, &ok, Tnone, 0);
- return !!ok;
+ propagate_types(bp->right, c, &ok, Tnone, 0);
+ if (!ok)
+ return 0; // UNTESTED
+ scope_finalize(c);
+ return 1;
}
- static void interp_prog(struct parse_context *c, struct exec *prog, char **argv)
+ static void interp_prog(struct parse_context *c, struct exec *prog,
+ int argc, char **argv)
{
struct binode *p = cast(binode, prog);
struct binode *al;
+ int anum = 0;
struct value v;
struct type *vtype;
al = cast(binode, p->left);
while (al) {
struct var *v = cast(var, al->left);
- struct value *vl = v->var->val;
+ struct value *vl = var_value(c, v->var);
struct value arg;
-
- if (argv[0] == NULL) {
- printf("Not enough args\n");
- exit(1);
- }
- if (v->var->type != Tstr) {
- printf("Arg not string!!\n"); // NOTEST
- exit(2); // NOTEST
- }
- if (!vl) {
- vl = val_alloc(c, v->var->type, NULL);
- v->var->val = vl;
+ struct type *t;
+ mpq_t argcq;
+ int i;
+
+ switch (anum++) {
+ 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++) {
+ struct value *vl2 = vl->array + i * v->var->type->array.member->size;
+
+
+ arg.str.txt = argv[i];
+ arg.str.len = strlen(argv[i]);
+ free_value(Tstr, vl2);
+ dup_value(Tstr, &arg, vl2);
+ }
+ break;
}
-
- arg.str.txt = argv[0];
- arg.str.len = strlen(argv[0]);
- free_value(Tstr, vl);
- dup_value(Tstr, &arg, vl);
-
al = cast(binode, al->right);
- argv++;
}
v = interp_exec(c, p->right, &vtype);
free_value(vtype, &v);
}
###### interp binode cases
- case Program: abort(); // NOTEST
+ case List:
+ case Func: abort(); // NOTEST
## And now to test it out.
name:string
alive:Boolean
- program Astr Bstr:
+ func main
+ argv:[argc::]string
+ do
print "Hello World, what lovely oceans you have!"
print "Are there", five, "?"
print pi, pie, "but", cake
- A := $Astr; B := $Bstr
+ A := $argv[1]; B := $argv[2]
/* When a variable is defined in both branches of an 'if',
* and used afterwards, the variables are merged.