nooc

Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.nihaljere.xyz/nooc
Log | Files | Refs | LICENSE

commit 78d98dffe8fb229857a040fed5e1bc5cee679eb9
parent 40880caeea40aa8fe056da6bad44f5992fe3ed58
Author: Nihal Jere <nihal@nihaljere.xyz>
Date:   Tue,  7 Dec 2021 11:44:06 -0600

add variable reassignment, and some tests

Diffstat:
Mmain.c | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mnooc.h | 14+++++++++++++-
Mprog.nc | 4+++-
Mtest.sh | 10+++++++++-
Atest/assign_before_decl.fail.nooc | 2++
Atest/assign_int.pass.nooc | 2++
Rtest/yes.nooc -> test/yes.pass.nooc | 0
7 files changed, 97 insertions(+), 8 deletions(-)

diff --git a/main.c b/main.c @@ -18,6 +18,7 @@ #include "elf.h" struct decls decls; +struct assgns assgns; struct exprs exprs; #define ADVANCE(n) \ @@ -383,7 +384,7 @@ parse(struct token **tok) while ((*tok)->type != TOK_NONE && (*tok)->type != TOK_RCURLY) { item = (struct item){ 0 }; if ((*tok)->type == TOK_LET) { - struct decl decl; + struct decl decl = { 0 }; item.kind = ITEM_DECL; *tok = (*tok)->next; @@ -414,6 +415,17 @@ parse(struct token **tok) item.idx = decls.len - 1; array_add((&items), item); + } else if ((*tok)->type == TOK_NAME && (*tok)->next && (*tok)->next->type == TOK_EQUAL) { + struct assgn assgn; + item.kind = ITEM_ASSGN; + assgn.s = (*tok)->slice; + + *tok = (*tok)->next->next; + assgn.val = parseexpr(tok); + array_add((&assgns), assgn); + + item.idx = assgns.len - 1; + array_add((&items), item); } else { item.kind = ITEM_EXPR; item.idx = parseexpr(tok); @@ -434,9 +446,10 @@ typecheck(struct block items) { for (size_t i = 0; i < items.len; i++) { struct expr *expr; + struct decl *decl; switch (items.data[i].kind) { case ITEM_DECL: - struct decl *decl = &decls.data[items.data[i].idx]; + decl = &decls.data[items.data[i].idx]; switch (decl->type) { case TYPE_I64: expr = &exprs.data[decl->val]; @@ -452,6 +465,26 @@ typecheck(struct block items) error("unknown decl type"); } break; + case ITEM_ASSGN: + struct assgn *assgn = &assgns.data[items.data[i].idx]; + decl = finddecl(&items, assgn->s); + if (decl == NULL) + error("unknown name"); + switch (decl->type) { + case TYPE_I64: + expr = &exprs.data[assgn->val]; + // FIXME: we should be able to deal with ident or fcalls + if (expr->class != C_INT) error("expected integer expression for integer variable"); + break; + case TYPE_STR: + expr = &exprs.data[assgn->val]; + // FIXME: we should be able to deal with ident or fcalls + if (expr->class != C_STR) error("expected string expression for string variable"); + break; + default: + error("unknown decl type"); + } + break; case ITEM_EXPR: break; default: @@ -585,9 +618,11 @@ genblock(char *buf, struct block *block) case C_INT: // this is sort of an optimization, since we write at compile-time instead of evaluating and storing. should this happen here in the long term? if (expr->kind == EXPR_LIT) { - decls.data[item->idx].addr = data_pushint(expr->d.v.v.i); + if (buf) + decls.data[item->idx].addr = data_pushint(expr->d.v.v.i); } else { - decls.data[item->idx].addr = data_pushint(0); + if (buf) + decls.data[item->idx].addr = data_pushint(0); enum reg reg = getreg(); total += genexpr(buf ? buf + total : NULL, decls.data[item->idx].val, reg); total += mov_m64_r64(buf ? buf + total : NULL, decls.data[item->idx].addr, reg); @@ -596,7 +631,35 @@ genblock(char *buf, struct block *block) break; // FIXME: we assume that any string is a literal, may break if we add binary operands on strings in the future. case C_STR: - decls.data[item->idx].addr = data_pushint(data_push(expr->d.v.v.s.data, expr->d.v.v.s.len)); + if (buf) + decls.data[item->idx].addr = data_pushint(data_push(expr->d.v.v.s.data, expr->d.v.v.s.len)); + break; + default: + error("cannot generate code for unknown expression class"); + } + } else if (item->kind == ITEM_ASSGN) { + struct expr *expr = &exprs.data[assgns.data[item->idx].val]; + struct assgn *assgn = &assgns.data[item->idx]; + struct decl *decl = finddecl(block, assgn->s); + if (decl == NULL) + error("unknown name"); + + if (buf && decl->addr == 0) + error("assignment before declaration"); + + switch (expr->class) { + case C_INT: + // this is sort of an optimization, since we write at compile-time instead of evaluating and storing. should this happen here in the long term? + enum reg reg = getreg(); + total += genexpr(buf ? buf + total : NULL, assgn->val, reg); + total += mov_m64_r64(buf ? buf + total : NULL, decl->addr, reg); + freereg(reg); + break; + // FIXME: we assume that any string is a literal, may break if we add binary operands on strings in the future. + case C_STR: + size_t addr = data_push(expr->d.v.v.s.data, expr->d.v.v.s.len); + total += mov_r_imm(buf ? buf + total : NULL, reg, addr); + total += mov_m64_r64(buf ? buf + total : NULL, decl->addr, reg); break; default: error("cannot generate code for unknown expression class"); diff --git a/nooc.h b/nooc.h @@ -55,6 +55,17 @@ enum type { TYPE_STR }; +struct assgn { + struct slice s; + size_t val; // struct exprs +}; + +struct assgns { + size_t cap; + size_t len; + struct assgn *data; +}; + struct decl { struct slice s; enum type type; @@ -77,7 +88,8 @@ struct data { struct item { enum { ITEM_DECL, - ITEM_EXPR + ITEM_ASSGN, + ITEM_EXPR, } kind; size_t idx; }; diff --git a/prog.nc b/prog.nc @@ -1,6 +1,8 @@ let exit i64 = 60 let write i64 = 1 +let stream i64 = 1 loop { - syscall(write, 0, "hello\n", 6) + syscall(write, stream, "hello\n", 6) + stream = 2 } syscall(exit, 0) diff --git a/test.sh b/test.sh @@ -1,6 +1,14 @@ #!/bin/sh -for file in test/* +for file in test/*.fail.nooc +do + ./nooc $file out && { + printf "test %s unexpectedly passed\n" "$file" + exit 1 + } +done + +for file in test/*.pass.nooc do ./nooc $file out || { printf "test %s failed\n" "$file" diff --git a/test/assign_before_decl.fail.nooc b/test/assign_before_decl.fail.nooc @@ -0,0 +1,2 @@ +foo = 3 +let foo i64 = 0 diff --git a/test/assign_int.pass.nooc b/test/assign_int.pass.nooc @@ -0,0 +1,2 @@ +let i i64 = 10 +i = 5 diff --git a/test/yes.nooc b/test/yes.pass.nooc