diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Makefile | 3 | ||||
| -rw-r--r-- | TODO | 27 | ||||
| m--------- | deps/conts | 0 | ||||
| -rw-r--r-- | examples/fib.fwd | 31 | ||||
| -rw-r--r-- | examples/ptrs.fwd | 7 | ||||
| -rw-r--r-- | examples/sum.fwd | 23 | ||||
| -rw-r--r-- | examples/vec.fwd | 143 | ||||
| -rw-r--r-- | include/fwd/ast.h | 225 | ||||
| -rw-r--r-- | include/fwd/compiler.h | 1 | ||||
| -rw-r--r-- | include/fwd/debug.h | 5 | ||||
| -rw-r--r-- | include/fwd/mod.h | 115 | ||||
| -rw-r--r-- | include/fwd/path.h | 38 | ||||
| -rw-r--r-- | include/fwd/rewrite.h | 9 | ||||
| -rw-r--r-- | include/fwd/scope.h | 12 | ||||
| -rw-r--r-- | lib/fwd.h | 66 | ||||
| -rw-r--r-- | mod/Makefile | 10 | ||||
| -rw-r--r-- | mod/io.c | 31 | ||||
| -rw-r--r-- | mod/mem.c | 50 | ||||
| -rw-r--r-- | mod/util.c | 20 | ||||
| -rwxr-xr-x | scripts/gen-deps | 2 | ||||
| -rw-r--r-- | scripts/makefile | 4 | ||||
| -rw-r--r-- | src/analyze.c | 778 | ||||
| -rw-r--r-- | src/ast.c | 110 | ||||
| -rw-r--r-- | src/compiler.c | 120 | ||||
| -rw-r--r-- | src/debug.c | 69 | ||||
| -rw-r--r-- | src/lexer.l | 21 | ||||
| -rw-r--r-- | src/lower.c | 1238 | ||||
| -rw-r--r-- | src/move.c | 405 | ||||
| -rw-r--r-- | src/parser.y | 250 | ||||
| -rw-r--r-- | src/path.c | 66 | ||||
| -rw-r--r-- | src/rewrite.c | 96 | ||||
| -rw-r--r-- | src/scope.c | 95 |
33 files changed, 3075 insertions, 996 deletions
@@ -2,6 +2,7 @@ deps.mk docs/output build fwd +*.so gen/*.c gen/*.inc !include/fwd @@ -1,6 +1,7 @@ .PHONY: all all: setup $(MAKE) -f scripts/makefile + $(MAKE) -C mod # this kicks all unrecognised targets to the client script. # note that trying to compile individual files, e.g. @@ -17,7 +18,7 @@ all: setup .PHONY: analyze analyze: setup - CC='gcc -fanalyzer' SKIP_ANALYZER='-fno-analyzer' $(MAKE) CROSS_COMPILE= + CFLAGS='$$CFLAGS -fanalyzer' SKIP_ANALYZER='-fno-analyzer' $(MAKE) .PHONY: setup setup: @@ -0,0 +1,27 @@ ++ Some kind of "unreachable" might be nice to help the move checker? for +instance, + + if abc { + release(var); + fwdpanic("!!!"); + } + + use(var) + +might be nicer to write than + + if abc { + release(var); + fwdpanic("!!!"); + } else { + use(var); + } + +or the current alternative + + guard(abc) => { + release(var); + fwdpanic("!!!"); + } => ; + + use (var); diff --git a/deps/conts b/deps/conts -Subproject 56edd66ae3e8661d40d499bd7c72f3ffc7aac4e +Subproject be71a36fd88941a5bc3f25d2e563c8aa0481327 diff --git a/examples/fib.fwd b/examples/fib.fwd index 7084b9d..4583a26 100644 --- a/examples/fib.fwd +++ b/examples/fib.fwd @@ -1,25 +1,30 @@ -/* heh, this technically speaking works, but I guess the compiler can't collapse - * frames so the bifurcating nature of fibonacci just gets mapped to a linear - * sequence of calls, taking up way more stack space than a typical, returning, - * function */ +/* + * Currently the compilation process requires a bit of manual intervention. + * For this particular example, run something like this from the root dir: + * + * ./fwd examples/fib.fwd > /tmp/fib.c + * gcc -Lmod -Iinclude -Ilib -Wl,-rpath=mod -O2 /tmp/fib.c -lfwdio -o /tmp/fib + * /tmp/fib + * + */ -fib(int n, (int) res) +/* modules are just libraries that can be loaded at runtime */ +import "../mod/libfwdio.so" + +fib(i64 n, (i64) res) { if n < 2 { res(1); } else { - fib(n - 1) => int f1; - fib(n - 2) => int f2; + fib(n - 1) => i64 f1; + fib(n - 2) => i64 f2; res(f1 + f2); } } -/* 'extern' println */ -fwd_println(auto n); -fwd_copy(auto n, (auto, auto) n1); - main() { - fib(6) => int n; - fwd_println(n); + fib(42) => i64 n; + fwdprint_i64(n); + fwdprint_nl(); } diff --git a/examples/ptrs.fwd b/examples/ptrs.fwd index a0f8aac..a1efc84 100644 --- a/examples/ptrs.fwd +++ b/examples/ptrs.fwd @@ -1,7 +1,6 @@ -fwd_null(auto x, () null, (&auto) ok); -fwd_copy(auto x, (auto, auto) ok); -fwd_println(auto x); -fwd_intalloc((*int) ok); +extern fwd_copy(auto x, (auto, auto) ok); +extern fwd_println(auto x); +extern fwd_intalloc((*int) ok); main() { diff --git a/examples/sum.fwd b/examples/sum.fwd new file mode 100644 index 0000000..89a2d11 --- /dev/null +++ b/examples/sum.fwd @@ -0,0 +1,23 @@ +print_int(i64 a); +print_nl(); + +sum_inner(i64 s, i64 n, (i64) res) +{ + if n <= 0 { + res(s); + } else { + sum_inner(s + n, n - 1, res); + } +} + +sum(i64 n, (i64) res) +{ + sum_inner(0, n, res); +} + +main() +{ + sum(1000000000) => i64 s; + print_int(s); + print_nl(); +} diff --git a/examples/vec.fwd b/examples/vec.fwd new file mode 100644 index 0000000..22049e0 --- /dev/null +++ b/examples/vec.fwd @@ -0,0 +1,143 @@ +import "../mod/libfwdio.so" +import "../mod/libfwdmem.so" +import "../mod/libfwdutil.so" + +vec { + u64 n; + u64 s; + *i64 buf; +} + +init_vec(u64 n, (vec) ok) +{ + ok([n => n, 0 as u64 => s, * => buf] vec); +} + +next_s_vec(u64 s, (u64) ok) +{ + if s == 0 as u64 { + ok(1 as u64); + } else { + ok(2 as u64 * s); + } +} + +reserve_vec(vec v, u64 c, (vec) ok) +{ + v => [n => n, s => s, buf => buf]; + n + c => nn; + + /* expand if we run out of space */ + if nn > s { + next_s_vec(s) => s; + + /* note that fwdrealloc creates a new closure, meaning that it + * takes ownership of v.buf, so we can't back out and do a + * single ok(v) at the end of the function */ + fwdrealloc(buf, s * sizeof(i64)) => nbuf; + ok([nn => n, s => s, nbuf => buf] vec); + } else { + ok([nn => n, s => s, buf => buf] vec); + } +} + +set_vec(vec v, i64 i, i64 e, (vec) ok) +{ + v => [n => n, s => s, buf => buf]; + buf + (n - 1 as u64) => nbuf; + nil nbuf { + nil nbuf; + fwdfree(buf); + fwdpanic("should never happen"); + } => dst; + + /* perform actual store */ + e => dst*; + + nil nbuf; + ok([n => n, s => s, buf => buf] vec); +} + +n_vec(vec v, (vec, u64) ok) +{ + v => [n => n, s => s, buf => buf]; + ok([n => n, s => s, buf => buf] vec, n); +} + +append_vec(vec v, i64 e, (vec) ok) +{ + n_vec(v) => v, n; + reserve_vec(v, n + 1 as u64) => v; + set_vec(v, n as i64 - 1, e) => v; + ok(v); +} + +at_vec(vec v, u64 i, (vec, &i64) ok) +{ + v => [n => n, s => s, buf => buf]; + guard(i < n) => { + fwdfree(buf); + fwdpanic("bounds error"); + } => ; + + buf + i => *i64 nbuf; + nil nbuf { + nil nbuf; + fwdfree(buf); + fwdpanic("should never happen"); + } => &i64 bufr; + + nil nbuf; + ok([n => n, s => s, buf => buf] vec, bufr); +} + +destroy_vec(vec v) +{ + v => [n => n, s => s, buf => buf]; + fwdfree(buf); + nil n; + nil s; +} + +populate_vec(i64 i, i64 n, vec v, (vec) ok) +{ + if i < n { + append_vec(v, i) => vec v; + populate_vec(i + 1, n, v, ok); + } else { + ok(v); + } +} + +guard(bool c, () err | () ok) +{ + if c { + err(); + } else { + ok(); + } +} + +check_vec(i64 i, i64 n, vec v, (vec) ok) +{ + if i < n { + at_vec(v, i as u64) => v, elem; + + guard(elem* != i) => { + destroy_vec(v); + fwdpanic("vec built wrong"); + } => ; + + check_vec(i + 1, n, v, ok); + } else { + ok(v); + } +} + +main() +{ + init_vec(0 as u64) => vec v; + populate_vec(0, 1000000, v) => vec v; + check_vec(0, 1000000, v) => vec v; + destroy_vec(v); +} diff --git a/include/fwd/ast.h b/include/fwd/ast.h index 36aa8c8..0fd1118 100644 --- a/include/fwd/ast.h +++ b/include/fwd/ast.h @@ -32,10 +32,47 @@ struct src_loc { struct ast; enum type_kind { - TYPE_ID = 1, TYPE_CONSTRUCT, TYPE_REF, TYPE_PTR, TYPE_CLOSURE, - TYPE_PURE_CLOSURE, TYPE_FUNC_PTR, TYPE_VOID, TYPE_ERR + TYPE_ID = 1, TYPE_REF, TYPE_PTR, TYPE_CLOSURE, + TYPE_PURE_CLOSURE, TYPE_FUNC_PTR, TYPE_VOID, TYPE_NIL, + TYPE_I8, TYPE_I16, TYPE_I32, TYPE_I64, + TYPE_U8, TYPE_U16, TYPE_U32, TYPE_U64, + TYPE_BOOL }; +static inline bool is_int_type(enum type_kind k) +{ + switch (k) { + case TYPE_I8: + case TYPE_I16: + case TYPE_I32: + case TYPE_I64: + case TYPE_U8: + case TYPE_U16: + case TYPE_U32: + case TYPE_U64: + return true; + + default: + return false; + } + + return false; +} + +static inline bool is_ptr_type(enum type_kind k) +{ + switch (k) { + case TYPE_PTR: + case TYPE_FUNC_PTR: + return true; + + default: + return false; + } + + return false; +} + struct type { enum type_kind k; @@ -68,24 +105,20 @@ enum ast_kind { AST_IMPORT, /** Creating new variable */ AST_LET, + AST_PUT, + AST_WRITE, /** If statement */ AST_IF, /** Nil check */ + AST_NIL_CHECK, + /** Nil */ AST_NIL, - /* Owning check */ - AST_OWN, - /** Err branch */ - AST_ERR_BRANCH, - /** Error throwing */ - AST_ERROR, /** Call procedure. */ AST_CALL, /** Procedure definition. */ AST_PROC_DEF, /** Variable declaration/definition. */ AST_VAR_DEF, - /** Dot. \c a.b; */ - AST_DOT, /* Closure */ AST_CLOSURE, /** Construct new type, something!{} */ @@ -128,12 +161,30 @@ enum ast_kind { AST_EQ, /** Negation, \c - */ AST_NEG, + /** Bitwise negation, \c ~ */ + AST_BNEG, /** Logical negation, \c ! */ AST_LNOT, /** Bitwise negation, \c ~ */ AST_NOT, /** Reference (note, not addrof!) */ AST_REF, + /** Sizeof */ + AST_SIZEOF, + AST_AS, + AST_CONSTRUCTION, + AST_CONSTRUCT, + AST_DECONSTRUCTION, + AST_DECONSTRUCT, + AST_EXPLODE, + AST_SELF, + AST_FORGET, + AST_PASTE, + AST_IMPLEMENTS, + AST_TEMPLATE, + AST_INSTANCE, + AST_SUPERTEMPLATE, + AST_INSTANCE_CONT, /** Dereferencing. * @todo should references and pointers both use this? */ @@ -152,6 +203,8 @@ enum ast_flag { AST_FLAG_PREANALYZIS = (1 << 1), AST_FLAG_NOMOVES = (1 << 2), AST_FLAG_PUBLIC = (1 << 3), + AST_REQ_FRAME = (1 << 4), + AST_FLAG_LOWERED = (1 << 5) }; struct ast { @@ -273,11 +326,12 @@ static inline bool is_const(struct ast *x) static inline bool is_lvalue(struct ast *node) { switch (node->k) { + case AST_DEREF: case AST_ID: - /** @todo others */ return true; - default: return false; + default: + return false; } return false; @@ -286,18 +340,20 @@ static inline bool is_lvalue(struct ast *node) static inline bool is_trivially_copyable(struct type *type) { switch (type->k) { + case TYPE_I8: + case TYPE_U8: + case TYPE_I16: + case TYPE_I32: + case TYPE_I64: + case TYPE_U16: + case TYPE_U32: + case TYPE_U64: case TYPE_REF: - case TYPE_PTR: - case TYPE_ERR: + case TYPE_BOOL: case TYPE_FUNC_PTR: case TYPE_PURE_CLOSURE: - /** @todo primitive types */ return true; - case TYPE_ID: - /* very special, bad bad bad */ - return strcmp(type->id, "int") == 0; - default: return false; } @@ -314,11 +370,11 @@ static inline bool is_trivially_copyable(struct type *type) #define gen_str(k, s, loc) gen_ast(k, NULL, NULL, NULL, NULL, NULL, s, -1, 0., \ loc) - #define gen4(k, a, b, c, d, loc) gen_ast(k, a, b, c, d, NULL, NULL, -1, 0., loc) #define gen3(k, a, b, c, loc) gen4(k, a, b, c, NULL, loc) #define gen2(k, a, b, loc) gen3(k, a, b, NULL, loc) #define gen1(k, a, loc) gen2(k, a, NULL, loc) +#define gen0(k, loc) gen1(k, NULL, loc) /* kind of hacky but I guess it works, and allows us to check that the type is @@ -336,17 +392,12 @@ static inline bool is_trivially_copyable(struct type *type) #define tgen_void(loc) \ tgen_type(TYPE_VOID, NULL, NULL, loc) -#define tgen_err(loc) \ - tgen_type(TYPE_ERR, NULL, NULL, loc) - #define tid_str(x) return_id(x, TYPE_ID) #define tgen_id(id, loc) \ tgen_type(TYPE_ID, NULL, id, loc) -#define tconstruct_id(x) return_id(x, TYPE_CONSTRUCT) -#define tconstruct_args(x) return_t0(x, TYPE_CONSTRUCT) -#define tgen_construct(id, args, loc) \ - tgen_type(TYPE_CONSTRUCT, args, id, loc) +#define tgen_nil(loc) \ + tgen_type(TYPE_NIL, NULL, NULL, loc) #define tptr_base(x) return_t0(x, TYPE_PTR) #define tgen_ptr(base, loc) \ @@ -360,7 +411,7 @@ static inline bool is_trivially_copyable(struct type *type) #define tgen_closure(args, loc) \ tgen_type(TYPE_CLOSURE, args, NULL, loc) -#define tpure_closure_args(x) return_t0(x, TYPE_CLOSURE) +#define tpure_closure_args(x) return_t0(x, TYPE_PURE_CLOSURE) #define tgen_pure_closure(args, loc) \ tgen_type(TYPE_PURE_CLOSURE, args, NULL, loc) @@ -383,15 +434,67 @@ static inline bool is_trivially_copyable(struct type *type) #define gen_comparison(op, left, right, loc) \ gen2(op, left, right, loc) +#define construction_id(x) return_s(x, AST_CONSTRUCTION) +#define construction_expr(x) return_a0(x, AST_CONSTRUCTION) +#define gen_construction(id, expr, loc) \ + gen_str1(AST_CONSTRUCTION, id, expr, loc) + +#define construct_id(x) return_s(x, AST_CONSTRUCT) +#define construct_members(x) return_a0(x, AST_CONSTRUCT) +#define gen_construct(id, members, loc) \ + gen_str1(AST_CONSTRUCT, id, members, loc) + +#define deconstruction_id(x) return_s(x, AST_DECONSTRUCTION) +#define deconstruction_var(x) return_a0(x, AST_DECONSTRUCTION) +#define gen_deconstruction(id, expr, loc) \ + gen_str1(AST_DECONSTRUCTION, id, expr, loc) + +#define deconstruct_members(x) return_a0(x, AST_DECONSTRUCT) +#define gen_deconstruct(members, loc) \ + gen1(AST_DECONSTRUCT, members, loc) + +#define gen_paste(l, r, loc) \ + gen2(AST_PASTE, l, r, loc) + +#define forget_id(x) return_s(x, AST_FORGET) +#define gen_forget(id, loc) \ + gen_str(AST_FORGET, id, loc) + +#define gen_implements(id, loc) \ + gen_str(AST_IMPLEMENTS, id, loc) + +#define template_type_params(x) return_a0(x, AST_TEMPLATE) +#define template_body(x) return_a2(x, AST_TEMPLATE) +#define gen_template(id, types, params, body, loc) \ + gen_str3(AST_TEMPLATE, id, types, params, body, loc) + +#define gen_supertemplate(id, types, params, inst, body, loc) \ + gen_ast(AST_SUPERTEMPLATE, types, params, inst, body, NULL, id, 0, 0., loc) + +#define gen_self(loc) \ + gen0(AST_SELF, loc) + +#define instance_id(x) return_s(x, AST_INSTANCE) +#define instance_templ(x) return_a1(x, AST_INSTANCE) +#define instance_type_args(x) return_t2(x, AST_INSTANCE) +#define gen_instance(name, templ, types, exprs, loc) \ + gen_ast(AST_INSTANCE, exprs, templ, NULL, NULL, types, name, 0, 0., loc) + +#define gen_instance_cont(name, body, loc) \ + gen_str1(AST_INSTANCE_CONT, name, body, loc) + +#define sizeof_type(x) return_t2(x, AST_SIZEOF) +#define gen_sizeof(type, loc) \ + gen_ast(AST_SIZEOF, NULL, NULL, NULL, NULL, type, NULL, 0, 0., loc) + #define unop_expr(x) ({assert(is_unop(x)); x->a0;}) #define gen_unop(op, expr, loc) \ gen1(op, expr, loc) #define call_expr(x) return_a0(x, AST_CALL) #define call_args(x) return_a1(x, AST_CALL) -#define call_err(x) return_a2(x, AST_CALL) -#define gen_call(expr, args, err, loc) \ - gen3(AST_CALL, expr, args, err, loc) +#define gen_call(expr, args, loc) \ + gen2(AST_CALL, expr, args, loc) #define proc_id(x) return_s(x, AST_PROC_DEF) #define proc_params(x) return_a0(x, AST_PROC_DEF) @@ -400,30 +503,14 @@ static inline bool is_trivially_copyable(struct type *type) #define gen_proc(id, params, rtype, body, loc) \ gen_ast(AST_PROC_DEF, params, body, NULL, NULL, rtype, id, 0, 0., loc) -#define dot_id(x) return_s(x, AST_DOT) -#define dot_expr(x) return_a0(x, AST_DOT) -#define gen_dot(id, expr, loc) \ - gen_str1(AST_DOT, id, expr, loc) - #define var_id(x) return_s(x, AST_VAR_DEF) #define var_type(x) return_t2(x, AST_VAR_DEF) #define gen_var(id, type, loc) \ gen_ast(AST_VAR_DEF, NULL, NULL, NULL, NULL, type, id, 0, 0., loc) -#define err_branch_id(x) return_s(x, AST_ERR_BRANCH) -#define err_branch_body(x) return_a0(x, AST_ERR_BRANCH) -#define gen_err_branch(id, body, loc) \ - gen_ast(AST_ERR_BRANCH, body, NULL, NULL, NULL, NULL, id, 0, 0., loc) - -#define error_str(x) return_s(x, AST_ERROR) -#define error_id(x) return_a0(x, AST_ERROR) -#define gen_error(str, id, loc) \ - gen_ast(AST_ERROR, id, NULL, NULL, NULL, NULL, str, 0, 0., loc) - #define block_body(x) return_a0(x, AST_BLOCK) -#define block_error(x) return_a1(x, AST_BLOCK) -#define gen_block(body, err, loc) \ - gen2(AST_BLOCK, body, err, loc) +#define gen_block(body, loc) \ + gen1(AST_BLOCK, body, loc) #define trait_id(x) return_s(x, AST_TRAIT_DEF) #define trait_body(x) return_a1(x, AST_TRAIT_DEF) @@ -435,10 +522,9 @@ static inline bool is_trivially_copyable(struct type *type) gen_str(AST_TRAIT_DEF, id, loc) #define struct_id(x) return_s(x, AST_STRUCT_DEF) -#define struct_params(x) return_a0(x, AST_STRUCT_DEF) -#define struct_body(x) return_a1(x, AST_STRUCT_DEF) -#define gen_struct(id, params, body, loc) \ - gen_str2(AST_STRUCT_DEF, id, params, body, loc) +#define struct_body(x) return_a0(x, AST_STRUCT_DEF) +#define gen_struct(id, body, loc) \ + gen_str1(AST_STRUCT_DEF, id, body, loc) #define import_file(x) return_s(x, AST_IMPORT) #define gen_import(file, loc) \ @@ -475,6 +561,20 @@ static inline bool is_trivially_copyable(struct type *type) #define gen_let(var, from, loc) \ gen2(AST_LET, var, from, loc) +#define put_dst(x) return_a0(x, AST_PUT) +#define gen_put(dst, loc) \ + gen1(AST_PUT, dst, loc) + +#define write_dst(x) return_a0(x, AST_WRITE) +#define write_src(x) return_a1(x, AST_WRITE) +#define gen_write(dst, src, loc) \ + gen2(AST_WRITE, dst, src, loc) + +#define explode_deconstruct(x) return_a0(x, AST_EXPLODE) +#define explode_expr(x) return_a1(x, AST_EXPLODE) +#define gen_explode(deconstruct, expr, loc) \ + gen2(AST_EXPLODE, deconstruct, expr, loc) + #define init_args(x) return_t2(x, AST_INIT) #define init_body(x) return_a0(x, AST_INIT) #define init_id(x) return_s(x, AST_INIT) @@ -487,11 +587,20 @@ static inline bool is_trivially_copyable(struct type *type) #define gen_if(cond, body, els, loc) \ gen3(AST_IF, cond, body, els, loc) -#define nil_var(x) return_s(x, AST_NIL) -#define nil_body(x) return_a1(x, AST_NIL) -#define nil_ref(x) return_a2(x, AST_NIL) -#define gen_nil(var, body, ref, loc) \ - gen_ast(AST_NIL, NULL, body, ref, NULL, NULL, var, 0, 0., loc) +#define nil_check_expr(x) return_a0(x, AST_NIL_CHECK) +#define nil_check_body(x) return_a1(x, AST_NIL_CHECK) +#define nil_check_ref(x) return_a2(x, AST_NIL_CHECK) +#define nil_check_rest(x) return_a3(x, AST_NIL_CHECK) +#define gen_nil_check(expr, body, ref, loc) \ + gen3(AST_NIL_CHECK, expr, body, ref, loc) + +#define gen_nil(loc) \ + gen0(AST_NIL, loc) + +#define as_expr(x) return_a0(x, AST_AS) +#define as_type(x) return_t2(x, AST_AS) +#define gen_as(expr, type, loc) \ + gen_ast(AST_AS, expr, NULL, NULL, NULL, type, NULL, 0, 0., loc) #define own_id(x) return_s(x, AST_OWN) #define own_body(x) return_a1(x, AST_OWN) diff --git a/include/fwd/compiler.h b/include/fwd/compiler.h index b7f1569..ac4d7f5 100644 --- a/include/fwd/compiler.h +++ b/include/fwd/compiler.h @@ -21,5 +21,6 @@ * @return \c 0 if compilation was succesful, otherwise some non-zero value. */ int compile(const char *input); +struct scope *compile_file(const char *file); #endif /* FWD_COMPILER_H */ diff --git a/include/fwd/debug.h b/include/fwd/debug.h index c3dce63..ac4dfdf 100644 --- a/include/fwd/debug.h +++ b/include/fwd/debug.h @@ -96,6 +96,9 @@ void semantic_warn(struct scope *scope, struct ast *node, const char *fmt, ...); void semantic_error(struct scope *scope, struct ast *node, const char *fmt, ...); +void type_error(struct scope *scope, struct type *type, const char *fmt, + ...); + void loc_error(struct scope *scope, struct src_loc loc, const char *fmt, ...); /** @@ -141,6 +144,6 @@ void type_mismatch(struct scope *scope, struct ast *node, void move_error(struct ast *new_use, struct ast *prev_use); void reference_error(struct ast *new_use, struct ast *prev_use); -const char *type_str(struct type *t); +char *type_str(struct type *t); #endif /* FWD_DEBUG_H */ diff --git a/include/fwd/mod.h b/include/fwd/mod.h new file mode 100644 index 0000000..51aa9c4 --- /dev/null +++ b/include/fwd/mod.h @@ -0,0 +1,115 @@ +#ifndef FWD_MOD_H +#define FWD_MOD_H + +#include <stddef.h> +#include <assert.h> +#include <stdlib.h> + +typedef enum fwd_type { + FWD_VOID, + FWD_I8, + FWD_I16, + FWD_I32, + FWD_I64, + FWD_U8, + FWD_U16, + FWD_U32, + FWD_U64, + FWD_PTR, + /** @todo others */ + FWD_END /* end of types, IDs above this are custom types I guess? */ +} fwd_type_t; + +typedef struct fwd_arg { + fwd_type_t t; + union { + int64_t i64; + void *p; + }; +} fwd_arg_t; + +/** @todo I hate this name */ +typedef struct fwd_extern_args { + size_t argc; + struct fwd_arg *args; +} fwd_extern_args_t; + +typedef struct fwd_state fwd_state_t; +typedef long (*fwd_extern_t)(fwd_extern_args_t args); +typedef long (*fwd_open_t)(fwd_state_t *state); + +extern int fwd_register(struct fwd_state *state, const char *name, fwd_extern_t func, fwd_type_t rtype, ...); + +#define FWD_REGISTER(state, func, rtype, ...) \ + fwd_register(state, #func, func, rtype __VA_OPT__(,) __VA_ARGS__, FWD_END) + +static inline void *fwd_arg(fwd_extern_args_t args, size_t idx, fwd_type_t id) +{ + assert(idx < args.argc); + assert(args.args[idx + 1].t == id); + switch (id) { + case FWD_I64: return &args.args[idx + 1].i64; + default: + } + + return NULL; +} + +#define FWD_ARG(args, idx, type, id) \ + *(type *)fwd_arg(args, idx, id) + +#define FWD_ARG_T(args, idx, type) \ + FWD_ARG(args, idx, type, FWD_T(type)) + +/* unimplemented as of yet */ +#define FWD_RET(id, x) \ + abort() + +static inline fwd_type_t fwd_t_signed(size_t s) +{ + switch (s) { + case 1: return FWD_I8; + case 2: return FWD_I16; + case 4: return FWD_I32; + case 8: return FWD_I64; + } + + abort(); + return FWD_END; +} + +static inline fwd_type_t fwd_t_unsigned(size_t s) +{ + switch (s) { + case 1: return FWD_U8; + case 2: return FWD_U16; + case 4: return FWD_U32; + case 8: return FWD_U64; + } + + abort(); + return FWD_END; +} + +static inline fwd_type_t fwd_t_ptr(size_t s) +{ + (void)s; + return FWD_PTR; +} + +#define FWD_T(type) \ + _Generic((type)(0),\ + signed char : fwd_t_signed, \ + signed short : fwd_t_signed, \ + signed int : fwd_t_signed, \ + signed long : fwd_t_signed, \ + signed long long : fwd_t_signed, \ + unsigned char : fwd_t_unsigned, \ + unsigned short : fwd_t_unsigned, \ + unsigned int : fwd_t_unsigned, \ + unsigned long : fwd_t_unsigned, \ + unsigned long long : fwd_t_signed, \ + default: fwd_t_ptr)(sizeof(type)) + + +#endif /* FWD_MOD_H */ diff --git a/include/fwd/path.h b/include/fwd/path.h new file mode 100644 index 0000000..277b33e --- /dev/null +++ b/include/fwd/path.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +#ifndef FWD_PATH_H +#define FWD_PATH_H + +/** + * @file path.h + * + * Path handling helpers. + */ + +/** + * Get basename of file path. + * E.g. src/some/file.c -> file.c + * + * @param file File path to get basename from. + * @return Basename of \p file. + */ +char *fwd_basename(const char *file); + +/** + * Get directory name of path. + * E.g. src/some/file.c -> src/some + * + * @param file File path to get dirname from. + * @return Dirname of \p file. + */ +char *fwd_dirname(const char *file); + +/** + * Get current working directory. + * + * @return Current working directory. + */ +char *fwd_cwdname(); + +#endif /* FWD_PATH */ diff --git a/include/fwd/rewrite.h b/include/fwd/rewrite.h new file mode 100644 index 0000000..d9c4129 --- /dev/null +++ b/include/fwd/rewrite.h @@ -0,0 +1,9 @@ +#ifndef FWD_REWRITE_H +#define FWD_REWRITE_H + +#include <fwd/ast.h> + +int rewrite_types(struct ast *node, char *orig, char *new); +int rewrite_holes(struct ast *node, char *new); + +#endif /* FWD_REWRITE_H */ diff --git a/include/fwd/scope.h b/include/fwd/scope.h index ebe0261..c3bafa0 100644 --- a/include/fwd/scope.h +++ b/include/fwd/scope.h @@ -24,6 +24,10 @@ enum scope_flags { #define MAP_NAME visible #include <conts/map.h> +#define VEC_NAME mod_vec +#define VEC_TYPE void * +#include <conts/vec.h> + /** * Scope. * Responsible for keeping track of visibilities and @@ -51,9 +55,11 @@ struct scope { /** List of child scopes. */ struct scope *children; + struct visible templates; struct visible symbols; struct visible traits; struct visible types; + struct mod_vec mods; enum scope_flags flags; }; @@ -108,13 +114,11 @@ int scope_add_scratch(struct scope *scope, struct ast *scratch); */ void scope_add_scope(struct scope *parent, struct scope *child); - int scope_add_symbol(struct scope *scope, struct ast *symbol); struct ast *scope_find_symbol(struct scope *scope, char *id); struct ast *file_scope_find_symbol(struct scope *scope, char *id); int scope_add_type(struct scope *scope, struct ast *type); -int scope_add_chain(struct scope *scope, struct ast *type); struct ast *scope_find_type(struct scope *scope, char *id); struct ast *file_scope_find_type(struct scope *scope, char *id); @@ -122,4 +126,8 @@ int scope_add_trait(struct scope *scope, struct ast *trait); struct ast *scope_find_trait(struct scope *scope, char *id); struct ast *file_scope_find_trait(struct scope *scope, char *id); +int scope_add_template(struct scope *scope, struct ast *trait); +struct ast *scope_find_template(struct scope *scope, char *id); +struct ast *file_scope_find_template(struct scope *scope, char *id); + #endif /* SCOPE_H */ diff --git a/lib/fwd.h b/lib/fwd.h new file mode 100644 index 0000000..54d75a2 --- /dev/null +++ b/lib/fwd.h @@ -0,0 +1,66 @@ +#ifndef FWD_H +#define FWD_H + +#include <fwd/mod.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <assert.h> + +#ifndef FWD_FRAME_SIZE +#error "no frame size defined, internal compiler error" +#endif + +/* should probably add some compiler checks but good enough for now */ +#define FWD_MUSTTAIL [[clang::musttail]] + +typedef void *fwd_stack_t; +typedef void *fwd_args_t; +typedef fwd_stack_t (*fwd_call_t)(fwd_stack_t, fwd_args_t); + +typedef struct { + fwd_call_t call; + fwd_args_t args; +} fwd_closure_t; + +typedef struct {} fwd_start_t; + +static inline fwd_stack_t create_fwd_stack() +{ + return NULL; +} + +static inline void *fwd_stack_alloc(fwd_stack_t *top) +{ + if (*top) { + fwd_stack_t *prev = *top; + *top = *prev; + return prev + 1; + } + + fwd_stack_t *new = malloc(FWD_FRAME_SIZE + sizeof(fwd_stack_t)); + *new = NULL; + return new + 1; +} + +static inline void fwd_stack_free(fwd_stack_t *stack, void *loc) +{ + fwd_stack_t *freed = (fwd_stack_t *)loc - 1; + *freed = *stack; + *stack = freed; +} + +static inline void destroy_fwd_stack(fwd_stack_t *stack) +{ + fwd_stack_t *prev = *stack; + while (prev) { + fwd_stack_t *cur = prev; + prev = *cur; + free(cur); + } +} + +#define FWD_CONTAINER_OF(ptr, type, member) \ + (type *)((char *)(ptr) - offsetof(type, member)) + +#endif /* FWD_H */ diff --git a/mod/Makefile b/mod/Makefile new file mode 100644 index 0000000..9121afd --- /dev/null +++ b/mod/Makefile @@ -0,0 +1,10 @@ +all: libfwdio.so libfwdmem.so libfwdutil.so + +libfwdio.so: io.c ../include/fwd/mod.h + $(CC) -I../include -fPIC -O2 -g -Wall -Wextra -shared io.c -o libfwdio.so + +libfwdmem.so: mem.c ../include/fwd/mod.h + $(CC) -I../include -fPIC -O2 -g -Wall -Wextra -shared mem.c -o libfwdmem.so + +libfwdutil.so: util.c ../include/fwd/mod.h + $(CC) -I../include -fPIC -O2 -g -Wall -Wextra -shared util.c -o libfwdutil.so diff --git a/mod/io.c b/mod/io.c new file mode 100644 index 0000000..d376d1c --- /dev/null +++ b/mod/io.c @@ -0,0 +1,31 @@ +#include <stdint.h> +#include <assert.h> +#include <stdio.h> + +#include <fwd/mod.h> + +long fwdprint_nl(fwd_extern_args_t args) +{ + assert(args.argc == 0); + putchar('\n'); + return 0; +} + +long fwdprint_i64(fwd_extern_args_t args) +{ + assert(args.argc == 1); + int64_t n = FWD_ARG_T(args, 0, int64_t); + printf("%lld", (long long)n); + return 0; +} + +int fwdopen(fwd_state_t *state) +{ + FWD_REGISTER(state, fwdprint_nl, + FWD_VOID); + + FWD_REGISTER(state, fwdprint_i64, + FWD_VOID, FWD_T(int64_t)); + + return 0; +} diff --git a/mod/mem.c b/mod/mem.c new file mode 100644 index 0000000..96d6d0e --- /dev/null +++ b/mod/mem.c @@ -0,0 +1,50 @@ +#include <fwd/mod.h> + +long fwdmalloc(fwd_extern_args_t args) +{ + assert(args.argc == 1); + size_t n = FWD_ARG_T(args, 0, size_t); + FWD_RET(FWD_PTR, malloc(n)); + return 0; +} + +long fwdrealloc(fwd_extern_args_t args) +{ + assert(args.argc == 2); + void *ptr = FWD_ARG(args, 0, void *, FWD_PTR); + size_t n = FWD_ARG_T(args, 1, size_t); + FWD_RET(FWD_PTR, realloc(ptr, n)); + return 0; +} + +long fwdfree(fwd_extern_args_t args) +{ + assert(args.argc == 1); + void *ptr = FWD_ARG(args, 0, void *, FWD_PTR); + return 0; +} + +long fwdptradd(fwd_extern_args_t args) +{ + assert(args.argc == 2); + void *ptr = FWD_ARG(args, 0, void *, FWD_PTR); + int64_t add = FWD_ARG_T(args, 1, int64_t); + FWD_RET(FWD_PTR, (char *)ptr + add); + return 0; +} + +int fwdopen(fwd_state_t *state) +{ + FWD_REGISTER(state, fwdmalloc, + FWD_PTR, FWD_T(size_t)); + + FWD_REGISTER(state, fwdrealloc, + FWD_PTR, FWD_PTR, FWD_T(size_t)); + + FWD_REGISTER(state, fwdfree, + FWD_VOID, FWD_PTR); + + FWD_REGISTER(state, fwdptradd, + FWD_PTR, FWD_PTR, FWD_T(int64_t)); + return 0; +} diff --git a/mod/util.c b/mod/util.c new file mode 100644 index 0000000..c967349 --- /dev/null +++ b/mod/util.c @@ -0,0 +1,20 @@ +#include <fwd/mod.h> +#include <stdio.h> + +long fwdpanic(fwd_extern_args_t args) +{ + assert(args.argc == 1); + char *str = FWD_ARG_T(args, 0, char *); + fprintf(stderr, "%s", str); + exit(1); + return 0; +} + +int fwdopen(fwd_state_t *state) +{ + /** @todo passing around strings might be common enough to warrant its + * own type? */ + FWD_REGISTER(state, fwdpanic, + FWD_VOID, FWD_PTR); + return 0; +} diff --git a/scripts/gen-deps b/scripts/gen-deps index f45707c..0073980 100755 --- a/scripts/gen-deps +++ b/scripts/gen-deps @@ -18,7 +18,7 @@ done shift $((OPTIND - 1)) # create all subdirectories -mkdir -p $(echo "${@}" | tr ' ' '\n' | sed "s|[^/]*$||;s|^|${BUILD}/|" | uniq) +mkdir -p $(echo "${@}" | tr ' ' '\n' | sed "s|[^/]*$||;s|^|${BUILD}/|" | sort -u) for s in ${@} do diff --git a/scripts/makefile b/scripts/makefile index 11229bd..ae42506 100644 --- a/scripts/makefile +++ b/scripts/makefile @@ -42,7 +42,9 @@ COMPILER != [ -n "$(CROSS_COMPILE)" ] \ || echo $(CC) -OBFLAGS := -g +# -rdynamic is apparently platform-dependent, not sure if I want to rely on it +# but good enough for now +OBFLAGS := -g -rdynamic WARNFLAGS := -Wall -Wextra COMPILE_FLAGS := $(CFLAGS) $(WARNFLAGS) $(OPTFLAGS) $(OBFLAGS) $(ASSERTFLAGS) \ diff --git a/src/analyze.c b/src/analyze.c index 0c06096..04b7856 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -1,12 +1,23 @@ /* SPDX-License-Identifier: copyleft-next-0.3.1 */ /* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ +#include <fwd/compiler.h> #include <fwd/analyze.h> +#include <fwd/rewrite.h> +#include <fwd/mod.h> + #include <stdlib.h> +#include <stdarg.h> #include <string.h> #include <assert.h> +#include <dlfcn.h> + +enum state_flags { + STATE_PROC_REQ_FRAME = (1 << 0) +}; struct state { + enum state_flags flags; }; static int analyze(struct state *state, struct scope *scope, struct ast *node); @@ -62,7 +73,13 @@ static int analyze_proc(struct state *state, struct scope *scope, if (!proc_body(node)) return 0; - return analyze_known_block(&proc_state, proc_scope, proc_body(node)); + if (analyze_known_block(&proc_state, proc_scope, proc_body(node))) + return -1; + + if (proc_state.flags & STATE_PROC_REQ_FRAME) + ast_set_flags(node, AST_REQ_FRAME); + + return 0; } static int analyze_unop(struct state *state, struct scope *scope, @@ -89,6 +106,19 @@ static int analyze_binop(struct state *state, struct scope *scope, if (analyze(state, scope, rhs)) return -1; + /* allow pointer arithmetic, follows C conventions */ + if (node->k == AST_ADD || node->k == AST_SUB) { + if (is_ptr_type(lhs->t->k) && is_int_type(rhs->t->k)) { + node->t = lhs->t; + return 0; + } + + if (is_ptr_type(rhs->t->k) && is_int_type(lhs->t->k)) { + node->t = rhs->t; + return 0; + } + } + if (!types_match(lhs->t, rhs->t)) { type_mismatch(scope, node, lhs->t, rhs->t); @@ -118,17 +148,9 @@ static int analyze_comparison(struct state *state, struct scope *scope, return -1; } - /** @todo check type is some primitive */ - char *tf = strdup("bool"); - if (!tf) { - internal_error("failed allocating comparison bool str"); - return -1; - } - - node->t = tgen_id(tf, node->loc); + node->t = tgen_type(TYPE_BOOL, NULL, NULL, node->loc); if (!node->t) { internal_error("failed allocating comparison bool type"); - free(tf); return -1; } @@ -144,28 +166,6 @@ static int analyze_known_block(struct state *state, struct scope *scope, struct if (analyze_list(state, scope, block_body(node))) return -1; - struct ast *err = block_error(node); - if (!err) - return 0; - - if (error_str(err)) - return 0; - - struct ast *id = error_id(err); - assert(id); - - struct ast *def = file_scope_find_symbol(scope, id_str(id)); - if (!def) { - semantic_error(scope, id, "no such symbol"); - return -1; - } - - struct type *t = def->t; - if (t->k != TYPE_ERR) { - semantic_error(scope, id, "invalid error variable type"); - return -1; - } - return 0; } @@ -195,10 +195,14 @@ static int analyze_var(struct state *state, struct scope *scope, static int analyze_let(struct state *state, struct scope *scope, struct ast *node) { - if (analyze(state, scope, let_var(node))) + if (analyze(state, scope, let_expr(node))) return -1; - if (analyze(state, scope, let_expr(node))) + struct ast *var = let_var(node); + if (!(var_type(var))) + var_type(var) = (let_expr(node))->t; + + if (analyze(state, scope, var)) return -1; struct type *l = (let_var(node))->t; @@ -221,16 +225,40 @@ static int analyze_init(struct state *state, struct scope *scope, if (analyze(state, scope, init_body(node))) return -1; - /** @todo check that all parameters match, though that requires that the - * type is defined in fwd and not in C++, so this might have to wait a - * bit */ - node->t = tgen_construct(strdup(init_id(node)), init_args(node), - node->loc); - if (!node->t) { - internal_error("failed allocating type for construct"); + internal_error("init unimplemented"); + return -1; +} + +static int deduce_closure_types(struct scope *scope, struct ast *node, struct type *type) +{ + /* a bit of checking duplication here, hmm */ + if (node->k != AST_CLOSURE) + return 0; + + if (type->k != TYPE_CLOSURE && type->k != TYPE_PURE_CLOSURE) { + semantic_error(scope, node, "surprise closure"); + return -1; + } + + struct ast *param = closure_bindings(node); + struct type *t = (type->k == TYPE_CLOSURE) + ? tclosure_args(type) + : tpure_closure_args(type) + ; + + if (ast_list_len(param) != type_list_len(t)) { + semantic_error(scope, node, "expected %zu closure parameters, got %zu", + type_list_len(t), ast_list_len(param)); return -1; } + for (; param && t; param = param->n, t = t->n) { + if (var_type(param)) + continue; + + var_type(param) = t; + } + return 0; } @@ -248,8 +276,6 @@ static int analyze_call(struct state *state, struct scope *scope, struct type *types = callable_types(expr->t); struct ast *args = call_args(node); - if (analyze_list(state, scope, args)) - return -1; size_t expected = type_list_len(types); size_t got = ast_list_len(args); @@ -260,15 +286,24 @@ static int analyze_call(struct state *state, struct scope *scope, } struct type *type = types; - foreach_node(arg, args) { + struct ast *arg = args; + for (; arg && type; type = type->n, arg = arg->n) { + if (deduce_closure_types(scope, arg, type)) + return -1; + + if (analyze(state, scope, arg)) + return -1; + + if (arg->k == AST_CLOSURE) + state->flags |= STATE_PROC_REQ_FRAME; + if (!types_match(type, arg->t)) { - type_mismatch(scope, node, type, arg->t); + type_mismatch(scope, arg, type, arg->t); return -1; } /* clone group info */ arg->t->group = type->group; - type = type->n; } node->t = tgen_void(node->loc); @@ -277,7 +312,7 @@ static int analyze_call(struct state *state, struct scope *scope, return -1; } - return analyze(state, scope, call_err(node)); + return 0; } static int analyze_ref(struct state *state, struct scope *scope, @@ -313,7 +348,7 @@ static int analyze_deref(struct state *state, struct scope *scope, semantic_error(node->scope, node, "deref of raw ptr not allowed"); semantic_info(node->scope, node, - "use fwd_null() to convert to ref"); + "use a nil check to convert to ref"); return -1; } @@ -323,7 +358,7 @@ static int analyze_deref(struct state *state, struct scope *scope, return -1; } - node->t = tptr_base(expr->t); + node->t = tref_base(expr->t); return 0; } @@ -362,7 +397,6 @@ static int analyze_closure(struct state *state, struct scope *scope, } node->scope = closure_scope; - closure_scope->flags |= SCOPE_PROC; scope_add_scope(scope, closure_scope); if (analyze_list(state, closure_scope, closure_bindings(node))) @@ -396,17 +430,10 @@ static int analyze_int(struct state *state, struct scope *scope, (void)state; (void)scope; - /** @todo do this properly, very hacky, bad bad bad */ - char *i = strdup("int"); - if (!i) { - internal_error("failed allocating constant int type string"); - return -1; - } - - node->t = tgen_id(i, node->loc); + /* start with largest possible and work down */ + node->t = tgen_type(TYPE_I64, NULL, NULL, node->loc); if (!node->t) { internal_error("failed allocating constant int type"); - free(i); return -1; } @@ -443,6 +470,189 @@ static int analyze_str(struct state *state, struct scope *scope, return 0; } +static int analyze_template(struct state *state, struct scope *scope, struct ast *node) +{ + struct scope *template_scope = create_scope(); + scope_add_scope(scope, template_scope); + + /* enough for vec example since traits are as of yet unimplemented */ + + node->t = tgen_void(node->loc); + return 0; +} + +static int analyze_instance(struct state *state, struct scope *scope, struct ast *node) +{ + (void)state; + + struct ast *id = instance_templ(node); + struct ast *template = file_scope_find_template(scope, id_str(id)); + if (!template) { + semantic_error(scope, node, "no such template: %s\n", id_str(id)); + return -1; + } + + /** @todo check that type implements trait */ + struct ast *params = template_type_params(template); + struct type *args = instance_type_args(node); + if (ast_list_len(params) != type_list_len(args)) { + semantic_error(scope, node, "expected %zu types, got %zu\n", + ast_list_len(params), type_list_len(args)); + return -1; + } + + /* make a working copy that we modify */ + struct ast *copy = clone_ast(template); + if (!copy) { + internal_error("failed copying template body"); + return -1; + } + + if (rewrite_holes(copy, instance_id(node))) + return -1; + + /* replace type args */ + struct ast *p = params; + struct type *a = args; + for (; p && a; p = p->n, a = a->n) { + if (rewrite_types(copy, var_id(p), tid_str(a))) + return -1; + } + + printf("// --- instance %s ---\n", instance_id(node)); + ast_dump(0, copy); + + /* analyze newly initialized things (might not work for supertemplates?) */ + if (analyze_root(scope, template_body(copy))) + return -1; + + node->t = tgen_void(node->loc); + return 0; +} + +static int analyze_struct(struct state *state, struct scope *scope, struct ast *node) +{ + struct scope *struct_scope = create_scope(); + scope_add_scope(scope, struct_scope); + node->scope = struct_scope; + + if (analyze_list(state, struct_scope, struct_body(node))) + return -1; + + node->t = tgen_void(node->loc); + return 0; +} + +static int analyze_construct(struct state *state, struct scope *scope, struct ast *node) +{ + struct ast *def = file_scope_find_type(scope, construct_id(node)); + if (!def) { + semantic_error(scope, node, "no such type: %s", construct_id(node)); + return -1; + } + + if (def->k != AST_STRUCT_DEF) { + semantic_error(scope, node, "type is not a struct"); + return -1; + } + + struct ast *params = struct_body(def); + struct ast *args = construct_members(node); + + if (ast_list_len(params) != ast_list_len(args)) { + semantic_error(scope, node, "expected %zu args, got %zu\n", + ast_list_len(params), ast_list_len(args)); + return -1; + } + + foreach_node(arg, args) { + struct ast *exists = scope_find_symbol(def->scope, construction_id(arg)); + if (!exists) { + semantic_error(scope, arg, "no member %s in %s\n", + construction_id(arg), construct_id(node)); + return -1; + } + + struct ast *expr = construction_expr(arg); + if (analyze(state, scope, expr)) + return -1; + + if (!types_match(exists->t, expr->t)) { + type_mismatch(scope, arg, exists->t, expr->t); + return -1; + } + + /** @todo check for duplicates, preferably not in O(n^2) or with + * a memory allocation? */ + } + + node->t = tgen_id(strdup(construct_id(node)), node->loc); + return 0; +} + +static int analyze_explode(struct state *state, struct scope *scope, struct ast *node) +{ + if (analyze(state, scope, explode_expr(node))) + return -1; + + struct type *type = (explode_expr(node))->t; + if (type->k != TYPE_ID) { + char *str = type_str(type); + semantic_error(scope, explode_expr(node), + "expected struct type, got %s\n", + str); + free(str); + return -1; + } + + struct ast *def = file_scope_find_type(scope, type->id); + if (!def || def->k != AST_STRUCT_DEF) { + semantic_error(scope, explode_expr(node), + "no such struct type: %s\n", + type->id); + return -1; + } + + struct ast *params = struct_body(def); + struct ast *args = explode_deconstruct(node); + + if (ast_list_len(params) != ast_list_len(args)) { + semantic_error(scope, node, "expected %zu args, got %zu\n", + ast_list_len(params), ast_list_len(args)); + return -1; + } + + foreach_node(arg, args) { + struct ast *var = deconstruction_var(arg); + struct ast *exists = scope_find_symbol(def->scope, deconstruction_id(arg)); + if (!exists) { + semantic_error(scope, arg, "no member %s in %s\n", + deconstruction_id(arg), type->id); + return -1; + } + + if (!var_type(var)) + (var_type(var)) = exists->t; + + if (analyze(state, scope, var)) + return -1; + + if (!types_match(exists->t, var->t)) { + type_mismatch(scope, arg, exists->t, var->t); + return -1; + } + + if (scope_add_symbol(scope, var)) + return -1; + + /** @todo check for duplicates, preferably not in O(n^2) or with + * a memory allocation? */ + } + + node->t = tgen_void(node->loc); + return 0; +} + static int analyze_if(struct state *state, struct scope *scope, struct ast *node) { @@ -464,38 +674,162 @@ static int analyze_if(struct state *state, struct scope *scope, return 0; } -static int analyze_err_branch(struct state *state, struct scope *scope, struct ast *node) +static int analyze_nil(struct state *state, struct scope *scope, struct ast *node) { - struct scope *branch_scope = create_scope(); - if (!branch_scope) { - internal_error("failed allocating err branch scope"); + (void)state; + (void)scope; + + node->t = tgen_nil(node->loc); + return 0; +} + +static bool castable_type(struct type *type) +{ + switch (type->k) { + case TYPE_PTR: + case TYPE_FUNC_PTR: + case TYPE_I8: + case TYPE_I16: + case TYPE_I32: + case TYPE_I64: + case TYPE_U8: + case TYPE_U16: + case TYPE_U32: + case TYPE_U64: + return true; + + default: + return false; + } + + return false; +} + +static int analyze_as(struct state *state, struct scope *scope, struct ast *node) +{ + if (analyze(state, scope, as_expr(node))) + return -1; + + if (analyze_type(state, scope, as_type(node))) + return -1; + + /* for now, any 'register' size type can be cast to any other. Might + * restrict this, like signed to unsigned might be weird if its negative + * etc and should be done via a helper function */ + struct type *expr_type = (as_expr(node))->t; + struct type *type = as_type(node); + if (!castable_type(expr_type)) { + char *s = type_str(expr_type); + semantic_error(scope, as_expr(node), "can't cast from %s\n", s); + free(s); return -1; } - node->t = tgen_void(node->loc); + if (!castable_type(type)) { + char *s = type_str(type); + type_error(scope, type, "can't cast to %s\n", s); + free(s); + return -1; + } - scope_add_scope(scope, branch_scope); - struct ast *var = gen_var(strdup(err_branch_id(node)), NULL, node->loc); - struct type *err_type = tgen_err(node->loc); - var->t = err_type; - scope_add_symbol(branch_scope, var); - return analyze(state, branch_scope, err_branch_body(node)); + node->t = type; + return 0; } -static int analyze_own(struct state *state, struct scope *scope, struct ast *node) +static int analyze_sizeof(struct state *state, struct scope *scope, struct ast *node) { - struct ast *found = file_scope_find_symbol(scope, own_id(node)); - if (!found) { - semantic_error(scope, node, "no symbol named \"%s\"", - own_id(node)); + if (analyze_type(state, scope, sizeof_type(node))) + return -1; + + /* hmm, no built-in size_t */ + node->t = tgen_type(TYPE_U64, NULL, NULL, node->loc); + return 0; +} + +static int analyze_nil_check(struct state *state, struct scope *scope, struct ast *node) +{ + struct ast *expr = nil_check_expr(node); + if (analyze(state, scope, expr)) + return -1; + + if (!is_ptr_type(expr->t->k)) { + char *s = type_str(expr->t); + semantic_error(scope, expr, "expected ptr type, got %s", s); + free(s); return -1; } - if (is_trivially_copyable(found->t)) - semantic_warn(scope, node, "trivially copyable type is never owned"); + if (analyze(state, scope, nil_check_body(node))) + return -1; + + struct ast *var = nil_check_ref(node); + struct type *base = tptr_base(expr->t); + struct type *ref = tgen_ref(clone_type(base), node->loc); + if (!var_type(var)) + var_type(var) = ref; + + if (analyze(state, scope, var)) + return -1; + + if (!types_match(ref, var->t)) { + type_mismatch(scope, var, ref, var->t); + return -1; + } + + if (analyze(state, scope, nil_check_rest(node))) + return -1; + + node->t = tgen_void(node->loc); + return 0; +} + +static int analyze_forget(struct state *state, struct scope *scope, struct ast *node) +{ + struct ast *def = file_scope_find_symbol(scope, forget_id(node)); + if (!def) { + semantic_error(scope, node, "no such symbol"); + return -1; + } node->t = tgen_void(node->loc); - return analyze(state, scope, own_body(node)); + return 0; +} + +static int analyze_put(struct state *state, struct scope *scope, struct ast *put) +{ + struct ast *dst = put_dst(put); + if (analyze(state, scope, dst)) + return -1; + + struct type *type = dst->t; + if (type->k != TYPE_REF) { + char *str = type_str(type); + semantic_error(scope, put, "trying to write to non-ref type %s\n", str); + free(str); + return -1; + } + + put->t = tref_base(type); + return 0; +} + +static int analyze_write(struct state *state, struct scope *scope, struct ast *write) +{ + struct ast *src = write_src(write); + if (analyze(state, scope, src)) + return -1; + + struct ast *dst = write_dst(write); + if (analyze(state, scope, dst)) + return -1; + + if (!types_match(src->t, dst->t)) { + type_mismatch(scope, write, src->t, dst->t); + return -1; + } + + write->t = tgen_void(write->loc); + return 0; } static int analyze(struct state *state, struct scope *scope, struct ast *node) @@ -535,7 +869,6 @@ static int analyze(struct state *state, struct scope *scope, struct ast *node) } switch (node->k) { - case AST_ERR_BRANCH: ret = analyze_err_branch(state, scope, node); break; case AST_CLOSURE: ret = analyze_closure(state, scope, node); break; case AST_PROC_DEF: ret = analyze_proc(state, scope, node); break; case AST_VAR_DEF: ret = analyze_var(state, scope, node); break; @@ -545,17 +878,45 @@ static int analyze(struct state *state, struct scope *scope, struct ast *node) case AST_CALL: ret = analyze_call(state, scope, node); break; case AST_REF: ret = analyze_ref(state, scope, node); break; case AST_LET: ret = analyze_let(state, scope, node); break; - case AST_OWN: ret = analyze_own(state, scope, node); break; case AST_ID: ret = analyze_id(state, scope, node); break; case AST_IF: ret = analyze_if(state, scope, node); break; + case AST_NIL_CHECK: ret = analyze_nil_check(state, scope, node); break; + case AST_CONST_INT: ret = analyze_int(state, scope, node); break; case AST_CONST_STR: ret = analyze_str(state, scope, node); break; + case AST_INSTANCE: ret = analyze_instance(state, scope, node); break; + case AST_TEMPLATE: ret = analyze_template(state, scope, node); break; + + case AST_STRUCT_DEF: ret = analyze_struct(state, scope, node); break; + case AST_CONSTRUCT: ret = analyze_construct(state, scope, node); break; + case AST_EXPLODE: ret = analyze_explode(state, scope, node); break; + + case AST_SIZEOF: ret = analyze_sizeof(state, scope, node); break; + case AST_NIL: ret = analyze_nil(state, scope, node); break; + case AST_AS: ret = analyze_as(state, scope, node); break; + + case AST_WRITE: ret = analyze_write(state, scope, node); break; + case AST_PUT: ret = analyze_put(state, scope, node); break; + + case AST_FORGET: ret = analyze_forget(state, scope, node); break; + + case AST_TRAIT_DEF: + node->t = tgen_void(node->loc); + ret = 0; + break; + case AST_EMPTY: node->t = tgen_void(node->loc); ret = 0; break; + + case AST_IMPORT: + node->t = tgen_void(node->loc); + ret = 0; + break; + default: internal_error("missing ast analysis for %s", ast_str(node->k)); return -1; @@ -585,11 +946,70 @@ static int analyze_list(struct state *state, struct scope *scope, static int analyze_type(struct state *state, struct scope *scope, struct type *type) { - /* for now, let's just say all types are fine as they are, specified by - * the user. */ - (void)state; - (void)scope; - (void)type; + if (type->t0 && analyze_type_list(state, scope, type->t0)) + return -1; + + /* temporary */ + if (!type->id) + return 0; + + char *id = type->id; + + /* handle primitives */ + if (strcmp(id, "i8") == 0) { + type->k = TYPE_I8; + return 0; + } + + if (strcmp(id, "u8") == 0) { + type->k = TYPE_U8; + return 0; + } + + if (strcmp(id, "i16") == 0) { + type->k = TYPE_I16; + return 0; + } + + if (strcmp(id, "u16") == 0) { + type->k = TYPE_U16; + return 0; + } + + if (strcmp(id, "i32") == 0) { + type->k = TYPE_I32; + return 0; + } + + if (strcmp(id, "u32") == 0) { + type->k = TYPE_U32; + return 0; + } + + if (strcmp(id, "i64") == 0) { + type->k = TYPE_I64; + return 0; + } + + if (strcmp(id, "u64") == 0) { + type->k = TYPE_U64; + return 0; + } + + if (strcmp(id, "bool") == 0) { + type->k = TYPE_BOOL; + return 0; + } + + /* check for user-defined types */ + struct ast *def = file_scope_find_type(scope, id); + if (!def) { + type_error(scope, type, "no such type: %s", id); + return -1; + } + + /* don't change type->k since we might want to check up on this again + * later */ return 0; } @@ -604,6 +1024,168 @@ static int analyze_type_list(struct state *state, struct scope *scope, return 0; } +static int copy_scope(bool reexport, struct scope *to, struct scope *from) +{ + foreach(visible, symbol, &from->symbols) { + struct ast *def = symbol->data; + if (!reexport && def->scope != from) + continue; + + if (!ast_flags(def, AST_FLAG_PUBLIC)) + continue; + + if (scope_add_symbol(to, def)) + return -1; + } + + foreach(visible, type, &from->types) { + struct ast *def = type->data; + if (!reexport && def->scope != from) + continue; + + if (!ast_flags(def, AST_FLAG_PUBLIC)) + continue; + + if (scope_add_type(to, def)) + return -1; + } + + foreach(visible, trait, &from->traits) { + struct ast *def = trait->data; + if (!reexport && def->scope != from) + continue; + + if (!ast_flags(def, AST_FLAG_PUBLIC)) + continue; + + if (scope_add_trait(to, def)) + return -1; + } + + foreach(visible, template, &from->templates) { + struct ast *def = template->data; + if (!reexport && def->scope != from) + continue; + + if (!ast_flags(def, AST_FLAG_PUBLIC)) + continue; + + if (scope_add_template(to, def)) + return -1; + } + + return 0; +} + +/* allowed to be noisy */ +static int try_import_file(struct scope *scope, struct ast *node) +{ + const char *file = import_file(node); + struct scope *child = compile_file(file); + if (!child) { + semantic_info(scope, node, "imported here"); + return -1; + } + + if (copy_scope(ast_flags(node, AST_FLAG_PUBLIC), scope, child)) { + semantic_info(scope, node, "imported here"); + return -1; + } + + return 0; +} + +/* should be quiet upon failure */ +static int try_import_mod(struct scope *scope, struct ast *node) +{ + /** @todo cleanup */ + void *mod = dlopen(import_file(node), RTLD_LAZY); + if (!mod) + return -1; + + fwd_open_t fwdopen = dlsym(mod, "fwdopen"); + if (!fwdopen) { + dlclose(mod); + return -1; + } + + mod_vec_append(&scope->mods, mod); + return fwdopen((void *)scope); +} + +static struct type *fwd_type_kind(fwd_type_t type) +{ + switch (type) { + case FWD_VOID: return tgen_void(NULL_LOC()); + case FWD_I64: return tgen_type(TYPE_I64, NULL, NULL, NULL_LOC()); + case FWD_U64: return tgen_type(TYPE_U64, NULL, NULL, NULL_LOC()); + case FWD_PTR: { + /* hack? */ + return tgen_nil(NULL_LOC()); + } + + default: + break; + } + + abort(); + return NULL; +} + +int fwd_register(struct fwd_state *state, const char *name, fwd_extern_t func, fwd_type_t rtype, ...) +{ + struct scope *scope = (void *)state; + struct ast *vars = NULL; + + va_list args; + va_start(args, rtype); + + size_t idx = 0; + while (1) { + fwd_type_t type = va_arg(args, enum fwd_type); + if (type == FWD_END) + break; + + /* eight bytes is likely more than enough, since first three are + * var, then we have four bytes for indexes and one trailing + * NULL */ + char *name = calloc(8, sizeof(char)); + assert(name); + + sprintf(name, "%s%zu", "var", idx); + struct ast *new = gen_var(name, fwd_type_kind(type), NULL_LOC()); + + if (vars) + new->n = vars; + + vars = new; + idx++; + } + va_end(args); + + /* append 'return' as a continuation */ + if (rtype != FWD_VOID) { + char *name = strdup("ret"); + struct ast *new = gen_var(name, + tgen_closure(fwd_type_kind(rtype), NULL_LOC()), + NULL_LOC()); + if (vars) + new->n = vars; + + vars = new; + } + + vars = reverse_ast_list(vars); + + struct ast *def = gen_proc(strdup(name), vars, + fwd_type_kind(rtype), NULL, NULL_LOC()); + + if (scope_add_symbol(scope, def)) + return -1; + + return 0; +} + int analyze_root(struct scope *scope, struct ast *root) { foreach_node(node, root) { @@ -619,8 +1201,7 @@ int analyze_root(struct scope *scope, struct ast *root) break; case AST_STRUCT_CONT: - if (scope_add_chain(scope, node)) - return -1; + abort(); break; case AST_TRAIT_DEF: @@ -628,6 +1209,29 @@ int analyze_root(struct scope *scope, struct ast *root) return -1; break; + case AST_IMPORT: { + if (!try_import_mod(scope, node)) + break; + + if (!try_import_file(scope, node)) + break; + + return -1; + } + + case AST_TEMPLATE: + if (scope_add_template(scope, node)) + return -1; + break; + + case AST_SUPERTEMPLATE: + if (scope_add_template(scope, node)) + return -1; + break; + + case AST_INSTANCE: + break; + default: abort(); } @@ -214,15 +214,13 @@ void ast_dump(int depth, struct ast *n) DUMP(AST_CLOSURE); DUMP(AST_IF); DUMP(AST_NIL); - DUMP(AST_OWN); - DUMP(AST_ERR_BRANCH); - DUMP(AST_ERROR); DUMP(AST_LET); + DUMP(AST_PUT); + DUMP(AST_WRITE); DUMP(AST_INIT); DUMP(AST_CALL); DUMP(AST_PROC_DEF); DUMP(AST_VAR_DEF); - DUMP(AST_DOT); DUMP(AST_BLOCK); DUMP(AST_ID); DUMP(AST_EMPTY); @@ -246,6 +244,23 @@ void ast_dump(int depth, struct ast *n) DUMP(AST_NOT); DUMP(AST_REF); DUMP(AST_DEREF); + DUMP(AST_NIL_CHECK); + DUMP(AST_BNEG); + DUMP(AST_SIZEOF); + DUMP(AST_AS); + DUMP(AST_CONSTRUCTION); + DUMP(AST_CONSTRUCT); + DUMP(AST_DECONSTRUCTION); + DUMP(AST_DECONSTRUCT); + DUMP(AST_EXPLODE); + DUMP(AST_SELF); + DUMP(AST_FORGET); + DUMP(AST_PASTE); + DUMP(AST_IMPLEMENTS); + DUMP(AST_TEMPLATE); + DUMP(AST_INSTANCE); + DUMP(AST_SUPERTEMPLATE); + DUMP(AST_INSTANCE_CONT); DUMP(AST_CONST_INT); DUMP(AST_CONST_CHAR); DUMP(AST_CONST_BOOL); @@ -299,6 +314,9 @@ struct ast *clone_ast(struct ast *n) assert(n->k); struct ast *new = create_empty_ast(); + if (!new) + return NULL; + new->scope = n->scope; new->loc = n->loc; new->k = n->k; @@ -320,6 +338,9 @@ struct ast *clone_ast(struct ast *n) if (n->a3) new->a3 = clone_ast_list(n->a3); + if (n->t2) + new->t2 = clone_type_list(n->t2); + /** @todo rebuild opt group? */ return new; @@ -351,13 +372,14 @@ struct type *clone_type(struct type *type) return NULL; new->k = type->k; - if (type->id && !(new->id = strdup(type->id))) - return NULL; + if (type->id) + new->id = strdup(type->id); - if (type->t0 && !(new->t0 = clone_type_list(type->t0))) - return NULL; + if (type->t0) + new->t0 = clone_type_list(type->t0); new->group = type->group; + new->loc = type->loc; return new; } @@ -548,10 +570,24 @@ struct type *reverse_type_list(struct type *root) void fix_closures(struct ast *root) { while (root) { - if (root->k != AST_CALL) { + if (root->k != AST_CALL && root->k != AST_NIL_CHECK) { root = root->n; continue; } + if (root->k == AST_NIL_CHECK) { + struct ast *next = root->n; + if (!next) + next = gen_empty(root->loc); + + struct ast *block = gen_block(next, root->loc); + nil_check_rest(root) = block; + root->n = NULL; + + root = next; + continue; + } + + /* call */ struct ast *arg = ast_last(call_args(root)); if (!arg) { root = root->n; @@ -574,50 +610,41 @@ void fix_closures(struct ast *root) assert(next); - struct ast *block = gen_block(next, NULL, root->loc); + struct ast *block = gen_block(next, root->loc); closure_body(arg) = block; root->n = NULL; root = next; } } -static bool special_auto_very_bad(struct type *a, struct type *b) -{ - /** @todo massive hack, accept 'auto' as a placeholder and match it - * against anything, will need to be fixed eventually */ - if (a->k == TYPE_ID && strcmp(a->id, "auto") == 0) - return true; - - if (b->k == TYPE_ID && strcmp(b->id, "auto") == 0) - return true; - - return false; -} - bool types_match(struct type *a, struct type *b) { - if (!a && !b) - return true; - if (a && !b) return false; if (!a && b) return false; - if (special_auto_very_bad(a, b)) + /* nil matches any kind of pointer */ + if (is_ptr_type(a->k) && b->k == TYPE_NIL) + return true; + + if (a->k == TYPE_NIL && is_ptr_type(b->k)) return true; if (a->k != b->k) return false; - if (a->id && b->id && strcmp(a->id, b->id) != 0) + if (!a->t0 && !b->t0) + return true; + + if (a->t0 && !b->t0) return false; - if (!type_lists_match(a->t0, b->t0)) + if (!a->t0 && b->t0) return false; - return true; + return type_lists_match(a->t0, b->t0); } bool type_lists_match(struct type *al, struct type *bl) @@ -649,15 +676,13 @@ const char *ast_str(enum ast_kind k) CASE(AST_CLOSURE); CASE(AST_IF); CASE(AST_NIL); - CASE(AST_OWN); - CASE(AST_ERR_BRANCH); - CASE(AST_ERROR); CASE(AST_LET); + CASE(AST_PUT); + CASE(AST_WRITE); CASE(AST_INIT); CASE(AST_CALL); CASE(AST_PROC_DEF); CASE(AST_VAR_DEF); - CASE(AST_DOT); CASE(AST_BLOCK); CASE(AST_ID); CASE(AST_EMPTY); @@ -681,11 +706,28 @@ const char *ast_str(enum ast_kind k) CASE(AST_NOT); CASE(AST_REF); CASE(AST_DEREF); + CASE(AST_NIL_CHECK); + CASE(AST_BNEG); + CASE(AST_SIZEOF); + CASE(AST_AS); + CASE(AST_CONSTRUCTION); + CASE(AST_CONSTRUCT); + CASE(AST_SELF); + CASE(AST_FORGET); + CASE(AST_PASTE); + CASE(AST_IMPLEMENTS); + CASE(AST_TEMPLATE); + CASE(AST_INSTANCE); + CASE(AST_SUPERTEMPLATE); + CASE(AST_INSTANCE_CONT); CASE(AST_CONST_INT); CASE(AST_CONST_CHAR); CASE(AST_CONST_BOOL); CASE(AST_CONST_FLOAT); CASE(AST_CONST_STR); + CASE(AST_DECONSTRUCT); + CASE(AST_DECONSTRUCTION); + CASE(AST_EXPLODE); } #undef CASE diff --git a/src/compiler.c b/src/compiler.c index 9f564d8..5de992d 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -19,8 +19,9 @@ #include <fwd/analyze.h> #include <fwd/parser.h> #include <fwd/debug.h> -#include <fwd/scope.h> #include <fwd/lower.h> +#include <fwd/scope.h> +#include <fwd/path.h> #include <fwd/move.h> /** @@ -65,11 +66,11 @@ static char *read_file(const char *file, FILE *f) * @param file File name to process. * @return \c 0 if processing was succesful, non-zero value otherwise. */ -static int process(struct scope **parent, const char *file) +static int process(struct scope *scope, const char *file) { FILE *f = fopen(file, "rb"); if (!f) { - error("failed opening %s: %s\n", file, strerror(errno)); + error("failed opening %s: %s", file, strerror(errno)); return -1; } @@ -86,30 +87,20 @@ static int process(struct scope **parent, const char *file) } parse(p, file, buf); + struct ast *tree = p->tree; bool failed = p->failed; destroy_parser(p); if (failed) { - free((void*)buf); + free((void *)buf); return -1; } ast_dump_list(0, tree); - struct scope *scope = create_scope(); - if (!scope) { - free((void *)buf); - return -1; - } - scope->fctx.fbuf = buf; scope->fctx.fname = strdup(file); - if (*parent) - scope_add_scope(*parent, scope); - else - *parent = scope; - if (analyze_root(scope, tree)) return -1; @@ -119,24 +110,101 @@ static int process(struct scope **parent, const char *file) return 0; } +#define MAP_KEY const char * +#define MAP_TYPE struct scope * +#define MAP_CMP(a, b) strcmp((a), (b)) +#define MAP_HASH(a) CONTS_MAP_STR_HASH(a) +#define MAP_NAME scopes +#include <conts/map.h> + +/* ugly global for now */ +static struct scopes scopes; + +static void destroy_scopes() +{ + foreach(scopes, n, &scopes) { + destroy_scope(n->data); + free((void *)n->key); + } + + scopes_destroy(&scopes); +} + +struct scope *compile_file(const char *file) +{ + struct scope *scope = NULL; + const char *base = NULL, *dir = NULL, *cwd = NULL, *real = NULL; + if (!(base = fwd_basename(file))) { + error("couldn't get basename of %s", file); + goto out; + } + + if (!(dir = fwd_dirname(file))) { + error("couldn't get dirname of %s", file); + goto out; + } + + if (!(cwd = fwd_cwdname())) { + error("couldn't get current working dir"); + goto out; + } + + if (*dir != 0 && chdir(dir)) { + error("couldn't change to directory %s: %s", dir, + strerror(errno)); + goto out; + } + + if (!(real = realpath(base, NULL))) { + error("no such file: %s", file); + goto out; + } + + struct scope **exists = scopes_find(&scopes, real); + if (exists) { + scope = *exists; + goto out; + } + + scope = create_scope(); + scopes_insert(&scopes, strdup(real), scope); + + if (process(scope, base)) { + scope = NULL; + goto out; + } + + if (chdir(cwd)) { + error("couldn't change back to directory %s: %s", cwd, + strerror(errno)); + goto out; + } + +out: + free((void *)base); + free((void *)dir); + free((void *)cwd); + free((void *)real); + return scope; +} + int compile(const char *input) { + scopes = scopes_create(1); + int ret = -1; - struct scope *root = NULL; - if (process(&root, input)) { - destroy_scope(root); - destroy_allocs(); + struct scope *root = compile_file(input); + if (!root) { error("processing of %s stopped due to errors", input); - return ret; + goto out; } if ((ret = lower(root))) { - destroy_scope(root); - destroy_allocs(); - error("lowering of %s stopped due to errors", input); - return ret; + error("lowering of %s failed due to errors", input); + goto out; } - destroy_scope(root); +out: + destroy_scopes(); destroy_allocs(); - return 0; + return ret; } diff --git a/src/debug.c b/src/debug.c index b13a459..538fdfe 100644 --- a/src/debug.c +++ b/src/debug.c @@ -134,14 +134,27 @@ void semantic_error(struct scope *scope, struct ast *node, va_end(args); } +void type_error(struct scope *scope, struct type *type, + const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + struct src_issue issue; + issue.level = SRC_ERROR; + issue.loc = type->loc; + issue.fctx = scope->fctx; + _issue(issue, fmt, args); + va_end(args); +} + void type_mismatch(struct scope *scope, struct ast *node, struct type *l, struct type *r) { - const char *ls = type_str(l); - const char *rs = type_str(r); + char *ls = type_str(l); + char *rs = type_str(r); semantic_error(scope, node, "type mismatch: %s vs %s\n", ls, rs); - free((void *)ls); - free((void *)rs); + free(ls); + free(rs); } void move_error(struct ast *new_use, struct ast *prev_use) @@ -177,14 +190,50 @@ static void _type_str(FILE *f, struct type *type) return; switch (type->k) { - case TYPE_ERR: - fprintf(f, "err"); + case TYPE_I8: + fprintf(f, "i8"); + break; + + case TYPE_U8: + fprintf(f, "u8"); + break; + + case TYPE_I16: + fprintf(f, "i16"); + break; + + case TYPE_U16: + fprintf(f, "u16"); + break; + + case TYPE_I32: + fprintf(f, "i32"); + break; + + case TYPE_U32: + fprintf(f, "u32"); + break; + + case TYPE_I64: + fprintf(f, "i64"); + break; + + case TYPE_U64: + fprintf(f, "u64"); + break; + + case TYPE_BOOL: + fprintf(f, "bool"); break; case TYPE_VOID: fprintf(f, "void"); break; + case TYPE_NIL: + fprintf(f, "nil"); + break; + case TYPE_PTR: fprintf(f, "*"); _type_list_str(f, tptr_base(type)); @@ -216,16 +265,10 @@ static void _type_str(FILE *f, struct type *type) _type_list_str(f, type->t0); fprintf(f, ")"); break; - - case TYPE_CONSTRUCT: - fprintf(f, "%s![", type->id); - _type_list_str(f, type->t0); - fprintf(f, "]"); - break; } } -const char *type_str(struct type *t) +char *type_str(struct type *t) { if (!t) return strdup("NULL"); diff --git a/src/lexer.l b/src/lexer.l index e6a3f22..4f6598a 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -38,8 +38,7 @@ INT {HEX}|{DEC}|{OCT}|{BIN} HEXF [+-]?0[xX][0-9a-fA-F]+([pP][+-]?[0-9]+) DECF [+-]?[0-9]+[.]([eE]?[+-]?[0-9]+)?[fF]? -ID [_a-zA-Z][_a-zA-Z0-9]* -APPLY {ID}! +ID ((<>)|[_a-zA-Z])[_a-zA-Z0-9]* STRING \"(\\.|[^"\\])*\" @@ -64,7 +63,6 @@ STRING \"(\\.|[^"\\])*\" \n {} } -"::" {return SCOPE;} "(" {return LPAREN;} ")" {return RPAREN;} "{" {return LBRACE;} @@ -122,15 +120,14 @@ STRING \"(\\.|[^"\\])*\" "==" {return EQ;} "=>" {return FATARROW;} -"!>" {return ERRARROW;} "<<" {return LSHIFT;} ">>" {return RSHIFT;} +"as" {return AS;} "if" {return IF;} "else" {return ELSE;} "nil" {return NIL;} -"own" {return OWN;} "pub" {return PUB;} "import" {return IMPORT;} @@ -140,9 +137,9 @@ STRING \"(\\.|[^"\\])*\" "mut" {return MUT;} +"sizeof" {return SIZEOF;} + {STRING} { - /* seems risky, I know, but letting the parser choose when to allocate a - * new string seems to help with syntax error cleanup */ yylval->str = strdup(yytext); return STRING; } @@ -157,16 +154,6 @@ STRING \"(\\.|[^"\\])*\" return ID; } -{APPLY} { - /* strip trailing '!' */ - char *s = yytext + strlen(yytext); - s[-1] = '\0'; - - yylval->str = strdup(yytext); - return APPLY; -} - - [[:space:]]+ {/* skip whitespace */} . { diff --git a/src/lower.c b/src/lower.c index 89d9aca..9689928 100644 --- a/src/lower.c +++ b/src/lower.c @@ -5,18 +5,116 @@ #include <stdlib.h> #include <string.h> #include <stdarg.h> +#include <stdint.h> #include <assert.h> #include <fwd/lower.h> #include <fwd/scope.h> +#include <fwd/mod.h> + +/* stuff to be careful of: + * + * + Really easy to forget to pass the stack argument via reference, since it's + * just a void* we don't get a warning for it + * + * + lower_call() and lower_closure_call() share a lot of the same stuff, might + * want to try and merge them somehow, otherwise too easy to make a change in + * one and forget the other + */ + +/* placeholder, should probably scale according to biggest need */ +#define FWD_FRAME_SIZE 1024 + +static inline void mangle(FILE *f, struct ast *id) +{ + assert(id->k == AST_STRUCT_DEF + || id->k == AST_PROC_DEF + || id->k == AST_VAR_DEF + || id->k == AST_ID); + + assert(id->s && id->scope); + fprintf(f, "%s_s%zu", id->s, id->scope->number); +} + +static inline char *mangle2(struct ast *id) +{ + char *buf = NULL; size_t size = 0; + FILE *f = open_memstream(&buf, &size); + assert(f); + + mangle(f, id); + fclose(f); + assert(buf); + + return buf; +} + +static inline __attribute__((format (printf, 1, 2))) +char *buildstr(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + char *buf = NULL; size_t size = 0; + FILE *f = open_memstream(&buf, &size); + assert(f); + + vfprintf(f, fmt, args); + fclose(f); + + va_end(args); + assert(buf); + return buf; +} /** @todo semantics in this file are a bit unclear, should probably do some kind * of "each function starts and ends on an indented empty line" or something */ +#define VEC_NAME string_vec +#define VEC_TYPE char * +#include <conts/vec.h> + +#define MAP_NAME proc_set +#define MAP_KEY struct ast * +#define MAP_TYPE struct ast * +#define MAP_CMP(a, b) ((uintptr_t)(a) - (uintptr_t)(b)) +#define MAP_HASH(a) ((uintptr_t)(a)) +#include <conts/map.h> + struct state { + char *prefix; long indent; + size_t uniq; + FILE *ctx; + FILE *code; + struct ast *current; + struct string_vec *decls; + struct string_vec *defns; + struct string_vec *types; + struct proc_set *procs; }; +static struct state create_state(struct state *parent) +{ + return (struct state){ + .uniq = parent->uniq, + .prefix = parent->prefix, + .indent = parent->indent, + .current = parent->current, + .ctx = parent->ctx, + .code = parent->code, + .decls = parent->decls, + .defns = parent->defns, + .types = parent->types, + .procs = parent->procs, + }; +} + +static size_t uniq(struct state *state) +{ + return state->uniq++; +} + static void increase_indent(struct state *state) { state->indent++; @@ -30,624 +128,950 @@ static void decrease_indent(struct state *state) static void indent(struct state *state) { if (state->indent != 0) - printf("%*c", (int)(2 * state->indent), ' '); + fprintf(state->code, "%*c", (int)(2 * state->indent), ' '); } -static int lower_var(struct ast *expr); - -static int lower_expr(struct state *state, struct ast *expr); -static int lower_closure(struct state *state, struct ast *closure); - -static int lower_block(struct state *state, struct ast *block, bool ret); -static int lower_statement(struct state *state, struct ast *stmt, bool ret); +static bool proc_lowered(struct state *state, struct ast *proc) +{ + assert(proc->k == AST_PROC_DEF); + return proc_set_find(state->procs, proc) != NULL; +} -static int lower_type(struct type *type); -static int lower_types(struct type *types); +static void add_proc(struct state *state, struct ast *proc) +{ + assert(proc->k == AST_PROC_DEF); + struct ast **p = proc_set_insert(state->procs, proc, proc); + assert(p && *p == proc); +} -static int lower_binop(struct state *state, struct ast *binop) +static void add_decl(struct state *state, char *decl) { - printf("("); - if (lower_expr(state, binop_left(binop))) - return -1; + string_vec_append(state->decls, decl); +} - switch (binop->k) { - case AST_ADD: printf(" + "); break; - case AST_SUB: printf(" - "); break; - case AST_MUL: printf(" * "); break; - case AST_DIV: printf(" / "); break; - case AST_REM: printf(" %% "); break; - case AST_LSHIFT: printf(" << "); break; - case AST_RSHIFT: printf(" >> "); break; - default: - internal_error("missing binop lowering"); - return -1; - } +static void add_defn(struct state *state, char *defn) +{ + string_vec_append(state->defns, defn); +} - if (lower_expr(state, binop_right(binop))) - return -1; +static bool type_lowered(struct ast *type) +{ + assert(type->k == AST_STRUCT_DEF); + return ast_flags(type, AST_FLAG_LOWERED); +} - printf(")"); - return 0; +static void add_type(struct state *state, char *type) +{ + string_vec_append(state->types, type); } -static int lower_comparison(struct state *state, struct ast *comp) +static int lower_stmt_list(struct state *state, struct ast *stmt_list, bool last); +static int lower_block(struct state *state, struct ast *block, bool last); +static int lower_params(struct state *state, struct ast *params); +static int lower_expr(struct state *state, struct ast *expr); +static int lower_proc(struct state *state, struct ast *proc); +static int lower_type_def(struct state *state, struct ast *type); +static char *lower_type_str(struct state *state, struct scope *scope, struct type *type); + +static int _type_str(FILE *f, struct state *state, struct scope *scope, struct type *type) { - printf("("); - if (lower_expr(state, comparison_left(comp))) - return -1; + assert(type); + switch (type->k) { + case TYPE_I8: + fprintf(f, "int8_t"); + break; - switch (comp->k) { - case AST_LT: printf(" < "); break; - case AST_GT: printf(" > "); break; - case AST_LE: printf(" <= "); break; - case AST_GE: printf(" <= "); break; - case AST_NE: printf(" != "); break; - case AST_EQ: printf(" == "); break; - default: - internal_error("missing comparison lowering"); - return -1; - } + case TYPE_U8: + fprintf(f, "uint8_t"); + break; - if (lower_expr(state, comparison_right(comp))) - return -1; + case TYPE_I16: + fprintf(f, "int16_t"); + break; - printf(")"); - return 0; -} + case TYPE_U16: + fprintf(f, "uint16_t"); + break; -static int lower_exprs(struct state *state, struct ast *exprs) -{ - if (!exprs) - return 0; + case TYPE_I32: + fprintf(f, "int32_t"); + break; - if (lower_expr(state, exprs)) - return -1; + case TYPE_U32: + fprintf(f, "uint32_t"); + break; - foreach_node(expr, exprs->n) { - printf(", "); - if (lower_expr(state, expr)) + case TYPE_I64: + fprintf(f, "int64_t"); + break; + + case TYPE_U64: + fprintf(f, "uint64_t"); + break; + + case TYPE_CLOSURE: + fprintf(f, "fwd_closure_t"); + break; + + case TYPE_PURE_CLOSURE: + fprintf(f, "fwd_closure_t"); + break; + + case TYPE_ID: { + struct ast *def = file_scope_find_type(scope, type_str(type)); + + if (!ast_flags(def, AST_FLAG_LOWERED)) + if (lower_type_def(state, def)) return -1; + + char *name = mangle2(def); + fprintf(f, "%s", name); + free(name); + break; + } + + case TYPE_PTR: { + char *rest = lower_type_str(state, scope, tptr_base(type)); + fprintf(f, "%s*", rest); + free(rest); + break; + } + + default: + internal_error("unhandled type lowering for %s", type_str(type)); + abort(); + break; } return 0; } -static int lower_type_construct(struct type *type) +static char *lower_type_str(struct state *state, struct scope *scope, struct type *type) { - printf("%s", tconstruct_id(type)); - printf("<"); + assert(type); - if (lower_types(tconstruct_args(type))) - return -1; + char *type_buf = NULL; size_t type_len = 0; + FILE *f = open_memstream(&type_buf, &type_len); + assert(f); - printf(">"); - return 0; + int r = _type_str(f, state, scope, type); + fclose(f); + assert(type_buf); + + if (r) { + free(type_buf); + return NULL; + } + + return type_buf; } -static int lower_type_callable(struct type *type) +static int lower_closure_call(struct state *state, struct ast *call, struct ast *def, bool last) { - /* std::function has a slight overhead compared to just using auto here, - * but auto doesn't play well with recursive templates like with our - * fib.fwd example, so use std::function for now. Eventually I might - * instead write a C backend or something to have more control over the - * exact semantics, but for now this is good enough. */ - printf("std::function<fwd_err_t("); + char *q = buildstr("%s_call%zu", state->prefix, uniq(state)); + char *args = mangle2(def); - if (lower_types(type->t0)) - return -1; + char *ctx_buf = NULL; size_t ctx_size = 0; + FILE *ctx = open_memstream(&ctx_buf, &ctx_size); + fprintf(ctx, "struct %s {\n fwd_start_t start;\n", q); - printf(")>"); - return 0; -} + indent(state); + fprintf(state->code, "fwd_call_t %s = ctx->%s.call;\n", q, args); -static int lower_type_ref(struct type *type) -{ - if (lower_type(tref_base(type))) - return -1; + indent(state); + fprintf(state->code, "struct %s *%s_ctx = ctx->%s.args;\n", q, q, args); - printf("&"); - return 0; -} + int ret = 0; + size_t idx = 0; + bool returning = false; + foreach_node(a, call_args(call)) { + returning |= a->k == AST_CLOSURE; -static int lower_type_ptr(struct type *type) -{ - /* would I need parentheses in some cases? */ - if (lower_type(tptr_base(type))) - return -1; + char *type = lower_type_str(state, a->scope, a->t); + fprintf(ctx, " %s a%zu;\n", type, idx); + free(type); - printf("*"); - return 0; -} + indent(state); + fprintf(state->code, "%s_ctx->a%zu = ", q, idx); + int ret = lower_expr(state, a); + fprintf(state->code, ";\n"); + if (ret) + goto out; -static int lower_type(struct type *type) -{ - switch (type->k) { - case TYPE_ID: printf("%s", tid_str(type)); return 0; - case TYPE_CONSTRUCT: return lower_type_construct(type); - case TYPE_CLOSURE: return lower_type_callable(type); - case TYPE_FUNC_PTR: return lower_type_callable(type); - case TYPE_PURE_CLOSURE: return lower_type_callable(type); - case TYPE_REF: return lower_type_ref(type); - case TYPE_PTR: return lower_type_ptr(type); - default: - internal_error("missing type lowering"); - return -1; + idx++; } - return 0; +out: + if (!returning && last && ast_flags(state->current, AST_REQ_FRAME)) { + indent(state); + fprintf(state->code, "fwd_stack_free(&stack, ctx);\n"); + } + + indent(state); + if (last) { + fprintf(state->code, "FWD_MUSTTAIL return %s(stack, %s_ctx);\n", + q, q); + } + else { + fprintf(state->code, "stack = %s(stack, %s_ctx);\n", + q, q); + } + + fprintf(ctx, "};\n\n"); + fclose(ctx); + assert(ctx_buf); + + add_type(state, ctx_buf); + free(args); + free(q); + return ret; } -static int lower_types(struct type *types) +static int lower_closure(struct state *state, struct ast *closure, char **name_out, char **args_out) { - if (!types) - return 0; + char *name = buildstr("%s_closure%zu", state->prefix, uniq(state)); + char *proto = buildstr("static fwd_stack_t %s(fwd_stack_t stack, fwd_args_t args)", name); + char *decl = buildstr("%s;\n", proto); + char *start_of = buildstr("%s_start", name); + add_decl(state, decl); - if (lower_type(types)) - return -1; + char *code_buf = NULL; + size_t code_size = 0; - foreach_type(type, types->n) { - printf(", "); - if (lower_type(type)) - return -1; - } + struct state new_state = create_state(state); + new_state.code = open_memstream(&code_buf, &code_size); + new_state.indent = 0; + assert(new_state.code); - return 0; + fprintf(new_state.code, "%s\n{\n", proto); + + /** @todo unsure if this has the same address as the first element in + * the frame or the last in the previous, I guess we shall soon see */ + fprintf(new_state.ctx, " fwd_start_t %s;\n", start_of); + + int ret = lower_params(&new_state, closure_bindings(closure)); + assert(ret == 0); + + increase_indent(&new_state); + indent(&new_state); + fprintf(new_state.code, "struct %s_ctx *ctx = FWD_CONTAINER_OF(args, struct %s_ctx, %s);\n", + state->prefix, state->prefix, start_of); + + struct ast *block = closure_body(closure); + ret = lower_stmt_list(&new_state, block_body(block), true); + + fprintf(new_state.code, "}\n\n"); + fclose(new_state.code); + assert(code_buf); + + add_defn(state, code_buf); + free(proto); + + assert(name_out); + assert(args_out); + *name_out = name; + *args_out = start_of; + return ret; } -static int lower_init(struct state *state, struct ast *init) +static int lower_comparison(struct state *state, struct ast *expr) { - printf("%s", init_id(init)); + if (lower_expr(state, comparison_left(expr))) + return -1; - if (init_args(init)) { - printf("<"); - if (lower_types(init_args(init))) - return -1; + switch (expr->k) { + case AST_LE: + fprintf(state->code, " <= "); + break; - printf(">"); - } + case AST_GE: + fprintf(state->code, " >= "); + break; - printf("{"); + case AST_LT: + fprintf(state->code, " < "); + break; - if (lower_exprs(state, init_body(init))) - return -1; + case AST_GT: + fprintf(state->code, " > "); + break; - printf("}"); - return 0; + case AST_EQ: + fprintf(state->code, " == "); + break; + + case AST_NE: + fprintf(state->code, " != "); + break; + + default: + internal_error("unhandled lowering for comparison %s", ast_str(expr->k)); + abort(); + } + + return lower_expr(state, comparison_right(expr)); } -static int lower_unop(struct state *state, struct ast *expr) +static int lower_binop(struct state *state, struct ast *expr) { + if (lower_expr(state, binop_left(expr))) + return -1; + switch (expr->k) { - case AST_LNOT: printf("-"); break; - case AST_NOT: printf("~"); break; - case AST_NEG: printf("-"); break; + case AST_SUB: + fprintf(state->code, " - "); + break; + + case AST_ADD: + fprintf(state->code, " + "); + break; + + case AST_MUL: + fprintf(state->code, " * "); + break; + default: - internal_error("missing unop lowering"); - return -1; + internal_error("unhandled binop %s", ast_str(expr->k)); + abort(); } - return lower_expr(state, unop_expr(expr)); + return lower_expr(state, binop_right(expr)); } static int lower_expr(struct state *state, struct ast *expr) { - if (is_unop(expr)) - return lower_unop(state, expr); + if (is_comparison(expr)) + return lower_comparison(state, expr); if (is_binop(expr)) return lower_binop(state, expr); - if (is_comparison(expr)) - return lower_comparison(state, expr); - switch (expr->k) { - case AST_ID: printf("%s", id_str(expr)); break; - case AST_CONST_INT: printf("%lld", int_val(expr)); break; - case AST_CONST_FLOAT: printf("%f", float_val(expr)); break; - case AST_CONST_BOOL: printf("%s", bool_val(expr) ? "true" : "false"); + case AST_CLOSURE: { + char *name = NULL, *args = NULL; + if (lower_closure(state, expr, &name, &args)) + return -1; + + fprintf(state->code, "(fwd_closure_t){%s, &ctx->%s}", name, args); + free(name); + free(args); break; - case AST_CONST_STR: printf("\"%s\"", str_val(expr)); break; - case AST_CONST_CHAR: printf("'%c'", (char)char_val(expr)); break; - case AST_INIT: return lower_init(state, expr); - case AST_CLOSURE: return lower_closure(state, expr); - case AST_REF: return lower_expr(state, ref_base(expr)); - case AST_DEREF: return lower_expr(state, deref_base(expr)); - default: - internal_error("missing expr lowering"); - return -1; } - return 0; -} + case AST_ID: { + struct ast *def = file_scope_find_symbol(expr->scope, id_str(expr)); + char *name = mangle2(def); + fprintf(state->code, "ctx->%s", name); + free(name); + break; + } -static int lower_move(struct state *state, struct ast *move) -{ - if (move->k == AST_ID) { - /** @todo once I start messing about with references, moves - * should only be outputted for parameters that take ownership - */ - printf("move(%s)", id_str(move)); - return 0; + case AST_CONST_INT: { + fprintf(state->code, "%lld", (long long)int_val(expr)); + break; } - return lower_expr(state, move); -} + case AST_NIL: { + fprintf(state->code, "NULL"); + break; + } -static int lower_moves(struct state *state, struct ast *moves) -{ - if (!moves) - return 0; + case AST_CONSTRUCT: { + char *name = lower_type_str(state, expr->scope, expr->t); + fprintf(state->code, "(%s){", name); + free(name); - if (lower_move(state, moves)) - return -1; + foreach_node(n, construct_members(expr)) { + fprintf(state->code, ".%s = ", construction_id(n)); + + if (lower_expr(state, construction_expr(n))) + return -1; + + fprintf(state->code, ", "); + } + fprintf(state->code, "}"); + break; + } + + case AST_AS: { + char *name = lower_type_str(state, expr->scope, expr->t); + fprintf(state->code, "(%s)(", name); + free(name); - foreach_node(move, moves->n) { - printf(", "); - if (lower_move(state, move)) + if (lower_expr(state, as_expr(expr))) return -1; + + fprintf(state->code, ")"); + break; + } + + default: + internal_error("unhandled expr lowering: %s", ast_str(expr->k)); + abort(); } return 0; } -static int lower_err_branch(struct state *state, struct ast *err) +static int lower_call(struct state *state, struct ast *call, bool last) { - if (lower_block(state, err_branch_body(err), false)) + struct ast *expr = call_expr(call); + assert(expr->k == AST_ID); + + struct ast *def = file_scope_find_symbol(call->scope, id_str(expr)); + assert(def); + + if (def->k == AST_VAR_DEF) + return lower_closure_call(state, call, def, last); + + if (lower_proc(state, def)) return -1; - printf("\n"); - return 0; -} + char *q = buildstr("%s_call%zu", state->prefix, uniq(state)); -static int lower_mark_moved(struct state *state, struct ast *moves) -{ - if (!moves) - return 0; + struct state ctx_state = create_state(state); + + char *ctx_buf = NULL; size_t ctx_size = 0; + FILE *ctx = open_memstream(&ctx_buf, &ctx_size); + fprintf(ctx, "struct %s {\n fwd_start_t start;\n", q); - foreach_node(move, moves) { - if (move->k != AST_ID) - continue; + indent(state); + fprintf(state->code, "struct %s *%s = ctx->global_args;\n", q, q); - if (is_trivially_copyable(move->t)) - continue; + int ret = 0; + size_t idx = 0; + bool returning = false; + foreach_node(a, call_args(call)) { + returning |= a->k == AST_CLOSURE; - if (is_callable(move->t)) - continue; + char *type = lower_type_str(state, a->scope, a->t); + fprintf(ctx, " %s a%zu;\n", type, idx); + free(type); - printf("%s_owned = false;\n", id_str(move)); - indent(state); + indent(&ctx_state); + fprintf(ctx_state.code, "%s->a%zu = ", q, idx); + int ret = lower_expr(&ctx_state, a); + fprintf(ctx_state.code, ";\n"); + + if (ret) + goto out; + + idx++; } - return 0; -} +out: + if (!returning && last && ast_flags(state->current, AST_REQ_FRAME)) { + /** @todo unsure if this applies to all cases but seems to work + * for the simple examples I've tried so far */ + indent(&ctx_state); + fprintf(ctx_state.code, "fwd_stack_free(&stack, ctx);\n"); + } -/** @todo this is probably more complicated than it really needs to be, maybe - * refactor into lower_checked_call and lower_implicit_call or something for - * explicit and implicit error handling cases? */ -static int lower_call(struct state *state, struct ast *call, bool ret) -{ - struct ast *err = call_err(call); - /** @todo better default error name? */ - const char *err_str = err ? err_branch_id(err) : "_fwd_err"; + char *target = mangle2(def); + indent(&ctx_state); + if (last) { + fprintf(ctx_state.code, "FWD_MUSTTAIL return %s(stack, %s);\n", + target, q); + } + else { + fprintf(ctx_state.code, "stack = %s(stack, %s);\n", + target, q); + } - if (lower_mark_moved(state, call_args(call))) - return -1; + fprintf(ctx, "};\n\n"); + fclose(ctx); + assert(ctx_buf); - bool direct_ret = ret && !err; - if (direct_ret) - printf("return "); - else - printf("if (auto %s = ", err_str); + add_type(state, ctx_buf); + free(target); + free(q); + return ret; +} - if (lower_expr(state, call_expr(call))) +static int lower_if(struct state *state, struct ast *stmt, bool last) +{ + indent(state); + fprintf(state->code, "if ("); + if (lower_expr(state, if_cond(stmt))) return -1; - printf("("); - - if (lower_moves(state, call_args(call))) + fprintf(state->code, ")\n"); + if (lower_block(state, if_body(stmt), last)) return -1; - if (direct_ret) { - printf(");\n"); + if (!if_else(stmt)) return 0; - } - printf("))"); - if (err) { - if (lower_err_branch(state, err)) - return -1; + indent(state); + fprintf(state->code, "else\n"); + return lower_block(state, if_else(stmt), last); +} - if (ret) { - printf("\n"); - indent(state); - printf("return nullptr;\n"); - } +static int lower_explode(struct state *state, struct ast *stmt, bool last) +{ + /* not significant to us */ + (void)last; - return 0; - } + int u = uniq(state); + struct ast *expr = explode_expr(stmt); - printf("\n"); + char *type = lower_type_str(state, expr->scope, expr->t); - increase_indent(state); indent(state); - decrease_indent(state); + fprintf(state->code, "%s explode_%d = ", type, u); + free(type); + + if (lower_expr(state, expr)) + return -1; + + fprintf(state->code, ";\n"); + + foreach_node(node, explode_deconstruct(stmt)) { + struct ast *var = deconstruction_var(node); - printf("return %s;\n", err_str); + char *type = lower_type_str(state, var->scope, var->t); + char *name = mangle2(var); - if (ret) { indent(state); - printf("return nullptr;\n"); + fprintf(state->code, "%s %s = explode_%d.%s;\n", + type, name, u, deconstruction_id(node)); + + free(type); + free(name); } return 0; } -static int lower_let(struct state *state, struct ast *let, bool ret) +static int lower_let(struct state *state, struct ast *stmt, bool last) { - if (lower_var(let_var(let))) - return -1; + /* not relevant */ + (void)last; + + struct ast *var = let_var(stmt); + char *type = lower_type_str(state, var->scope, var->t); + char *name = mangle2(var); + + indent(state); + fprintf(state->code, "%s %s = ", type, name); - printf(" = "); + free(type); + free(name); - if (lower_expr(state, let_expr(let))) + if (lower_expr(state, let_expr(stmt))) return -1; - printf(";\n"); - if (ret) { - indent(state); - printf("return nullptr;\n"); + fprintf(state->code, ";\n"); + return 0; +} + +static int lower_stmt(struct state *state, struct ast *stmt, bool last) +{ + switch (stmt->k) { + case AST_CALL: return lower_call(state, stmt, last); + case AST_IF: return lower_if(state, stmt, last); + case AST_EXPLODE: return lower_explode(state, stmt, last); + case AST_LET: return lower_let(state, stmt, last); + case AST_EMPTY: break; + default: + internal_error("unhandled statement kind %s", ast_str(stmt->k)); + abort(); + break; + } + return 0; +} + +static int lower_stmt_list(struct state *state, struct ast *stmt_list, bool last) +{ + foreach_node(stmt, stmt_list) { + bool req_ret = last && !stmt->n; + if (lower_stmt(state, stmt, req_ret)) + return -1; } return 0; } -static int lower_if(struct state *state, struct ast *stmt, bool ret) +static int lower_block(struct state *state, struct ast *block, bool last) { - printf("if ("); - if (lower_expr(state, if_cond(stmt))) - return -1; + indent(state); + fprintf(state->code, "{\n"); - printf(") "); + increase_indent(state); + int ret = lower_stmt_list(state, block_body(block), last); + decrease_indent(state); - if (lower_block(state, if_body(stmt), ret)) - return -1; + indent(state); + fprintf(state->code, "}\n"); + return ret; +} - if (!if_else(stmt)) { - printf("\n"); - return 0; - } +static int lower_param(struct state *state, struct ast *param) +{ + char *type = lower_type_str(state, param->scope, param->t); + char *name = mangle2(param); - printf(" else "); - if (lower_block(state, if_else(stmt), ret)) - return -1; + fprintf(state->ctx, " %s %s;\n", type, name); - printf("\n"); + free(type); + free(name); return 0; } -static int lower_error(struct ast *err) +static int lower_params(struct state *state, struct ast *params) { - assert(error_str(err) || error_id(err)); - if (error_str(err)) { - printf("return %s;\n", error_str(err)); - return 0; + foreach_node(p, params) { + if (lower_param(state, p)) + return -1; } - struct ast *id = error_id(err); - printf("return %s;\n", id_str(id)); return 0; } -static int lower_own(struct state *state, struct ast *stmt, bool ret) +static size_t fwd_typeid(struct type *t) { - /** @todo name mangling */ - printf("if (!%s_owned) ", own_id(stmt)); - if (lower_block(state, own_body(stmt), ret)) - return -1; + /** @todo this should maybe be cached somewhere earlier? */ + switch (t->k) { + case TYPE_I64: return FWD_I64; + case TYPE_PTR: return FWD_PTR; + default: + abort(); + } - printf("\n"); return 0; } -static int lower_statement(struct state *state, struct ast *stmt, bool ret) +static const char *fwd_typeparam(struct type *t) { - switch (stmt->k) { - case AST_OWN: return lower_own(state, stmt, ret); - case AST_LET: return lower_let(state, stmt, ret); - case AST_CALL: return lower_call(state, stmt, ret); - case AST_IF: return lower_if(state, stmt, ret); - case AST_EMPTY: - if (ret) - printf("return nullptr;\n"); - else - printf("\n"); - - return 0; + switch (t->k) { + case TYPE_I64: return "i64"; + case TYPE_PTR: return "p"; default: - internal_error("missing statement lowering"); - return -1; + abort(); } return 0; } -static int lower_block_vars(struct state *state, struct ast *block) +static int lower_extern_proc(struct state *state, struct ast *proc) { - struct scope *scope = block->scope; + /* only void external functions supported atm */ + struct type *rtype = proc_rtype(proc); + assert(rtype->k == TYPE_VOID); + + add_proc(state, proc); + char *name = mangle2(proc); + char *proto = buildstr("static fwd_stack_t %s(fwd_stack_t stack, fwd_args_t args)", + name); + + char *decl = buildstr("%s;\n", proto); + add_decl(state, decl); + + char *code_buf = NULL, *ctx_buf = NULL; + size_t code_size = 0, ctx_size = 0; + + struct state new_state = create_state(state); + new_state.indent = 0; + new_state.prefix = name; + new_state.current = proc; + new_state.uniq = 0; + new_state.code = open_memstream(&code_buf, &code_size); + new_state.ctx = open_memstream(&ctx_buf, &ctx_size); + assert(new_state.code); + + fprintf(new_state.ctx, "struct %s_ctx {\n", name); + fprintf(new_state.code, "%s\n{\n", proto); + increase_indent(&new_state); + + indent(&new_state); + fprintf(new_state.code, "extern long %s(fwd_extern_args_t);\n", proc_id(proc)); + + /* for now, allocate a new frame for arguments. Might want to + * simplify this by always using the 'external' format for argument + * passing, even internally */ + indent(&new_state); + fprintf(new_state.code, "struct %s_ctx *ctx = args;\n", name); + + indent(&new_state); + fprintf(new_state.code, "struct fwd_arg *extern_args = fwd_stack_alloc(&stack);\n"); + + size_t idx = 0; + foreach_node(p, proc_params(proc)) { + indent(&new_state); + /* leave place for return value */ + fprintf(new_state.code, "extern_args[%zu] = (fwd_arg_t){%zu, " + "{.%s = ctx->a%zu}};\n", + idx + 1, fwd_typeid(p->t), fwd_typeparam(p->t), idx); + + + char *type_str = lower_type_str(state, p->scope, p->t); + fprintf(new_state.ctx, " %s a%zu;\n", type_str, idx); + free(type_str); + idx++; + } - bool populated = false; - foreach(visible, n, &scope->symbols) { - struct ast *def = n->data; - if (def->k != AST_VAR_DEF) - continue; + indent(&new_state); + fprintf(new_state.code, "%s((fwd_extern_args_t){.argc = %zu, .args = extern_args});\n", + proc_id(proc), idx); - if (is_trivially_copyable(def->t)) - continue; - if (is_callable(def->t)) - continue; + indent(&new_state); + fprintf(new_state.code, "fwd_stack_free(&stack, extern_args);\n"); - if (!populated) { - indent(state); - printf("[[maybe_unused]] bool %s_owned = true", - var_id(def)); + indent(&new_state); + fprintf(new_state.code, "return stack;\n"); - populated = true; - continue; - } + fprintf(new_state.code, "}\n\n"); + fprintf(new_state.ctx, "};\n\n"); - printf(", %s_owned = true", var_id(def)); - } + fclose(new_state.code); + assert(code_buf); + add_defn(state, code_buf); - if (populated) - printf(";\n\n"); + fclose(new_state.ctx); + assert(ctx_buf); + add_type(state, ctx_buf); + free(proto); + free(name); return 0; } -static int lower_block(struct state *state, struct ast *block, bool ret) +static int lower_param_copy(struct state *state, struct ast *param, FILE *f, size_t idx) { - printf("{\n"); - increase_indent(state); + char *type = lower_type_str(state, param->scope, param->t); + fprintf(f, " %s a%zu;\n", type, idx); + free(type); - if (lower_block_vars(state, block)) - return -1; + indent(state); - foreach_node(stmt, block_body(block)) { - indent(state); + char *p = mangle2(param); + fprintf(state->code, "ctx->%s = params->a%zu;\n", p, idx); + free(p); + return 0; +} - bool returning = block_error(block) ? false : ret && !stmt->n; - if (lower_statement(state, stmt, returning)) +static int lower_param_copies(struct state *state, struct ast *params) +{ + char *param_buf = NULL; size_t param_size = 0; + FILE *f = open_memstream(¶m_buf, ¶m_size); + fprintf(f, "struct %s_params {\n fwd_start_t start;\n", state->prefix); + + size_t idx = 0; + foreach_node(p, params) { + if (lower_param_copy(state, p, f, idx)) { + fclose(f); + free(param_buf); return -1; - } + } - if (block_error(block)) { - indent(state); - if (lower_error(block_error(block))) - return -1; - } - else if (!block_body(block)) { - indent(state); - printf("return nullptr;\n"); + idx++; } - decrease_indent(state); - indent(state); - printf("}"); - return 0; -} + fprintf(f, "};\n\n"); + fclose(f); + assert(param_buf); -static int lower_var(struct ast *var) -{ - if (lower_type(var_type(var))) - return -1; - - printf(" %s", var_id(var)); + add_type(state, param_buf); return 0; } -static int lower_vars(struct ast *vars) +static int lower_type_def(struct state *state, struct ast *type) { - if (!vars) + assert(type->k == AST_STRUCT_DEF); + if (type_lowered(type)) return 0; - if (lower_var(vars)) - return -1; + ast_flags(type, AST_FLAG_LOWERED); - foreach_node(var, vars->n) { - printf(", "); - if (lower_var(var)) - return -1; - } + char *decl_buf = NULL; size_t decl_len = 0; + FILE *decl = open_memstream(&decl_buf, &decl_len); + assert(decl); - return 0; -} + char *name = mangle2(type); + fprintf(decl, "struct %s;\n", name); + fclose(decl); -static int lower_closure(struct state *state, struct ast *closure) -{ - printf("[&]("); - if (lower_vars(closure_bindings(closure))) - return -1; + char *defn_buf = NULL; size_t defn_len = 0; + FILE *defn = open_memstream(&defn_buf, &defn_len); + assert(defn); - printf(")"); + fprintf(defn, "typedef struct %s {\n", name); - if (lower_block(state, closure_body(closure), true)) - return -1; + foreach_node(n, struct_body(type)) { + assert(n->k == AST_VAR_DEF); + char *t = lower_type_str(state, n->scope, var_type(n)); + fprintf(defn, "\t%s %s;\n", t, var_id(n)); + free(t); + } + + fprintf(defn, "} %s;\n", name); + + fclose(defn); + free(name); + add_type(state, decl_buf); + add_type(state, defn_buf); return 0; } -static int lower_proto(struct ast *proc) +static int lower_proc(struct state *state, struct ast *proc) { - /* 'extern' functions should be provided to us by whatever framework the - * user is using */ - if (!proc_body(proc)) + if (proc_lowered(state, proc)) return 0; - printf("fwd_err_t "); - if (strcmp("main", proc_id(proc)) == 0) - printf("fwd_main("); - else - printf("%s(", proc_id(proc)); + if (!proc_body(proc)) + return lower_extern_proc(state, proc); + + add_proc(state, proc); + char *name = mangle2(proc); + char *proto = buildstr("static fwd_stack_t %s(fwd_stack_t stack, fwd_args_t args)", name); + char *decl = buildstr("%s;\n", proto); + add_decl(state, decl); + + char *code_buf = NULL, *ctx_buf = NULL; + size_t code_size = 0, ctx_size = 0; + + struct state new_state = create_state(state); + new_state.indent = 0; + new_state.prefix = name; + new_state.current = proc; + new_state.uniq = 0; + new_state.code = open_memstream(&code_buf, &code_size); + new_state.ctx = open_memstream(&ctx_buf, &ctx_size); + assert(new_state.code); + assert(new_state.ctx); + + char *start_of = buildstr("%s_start", name); + + fprintf(new_state.code, "%s\n{\n", proto); + fprintf(new_state.ctx, "struct %s_ctx {\n", name); + fprintf(new_state.ctx, " fwd_args_t global_args;\n fwd_start_t %s;\n", start_of); + + increase_indent(&new_state); + indent(&new_state); + fprintf(new_state.code, "static_assert(FWD_FRAME_SIZE >= sizeof(struct %s_ctx), \"context exceeds frame size\");\n", + name); + + if (ast_flags(proc, AST_REQ_FRAME)) { + indent(&new_state); + fprintf(new_state.code, "struct %s_ctx *ctx " + "= fwd_stack_alloc(&stack);\n", + name); + } + else { + indent(&new_state); + fprintf(new_state.code, "struct %s_ctx ctx_buf;\n", name); + indent(&new_state); + fprintf(new_state.code, "struct %s_ctx *ctx = &ctx_buf;\n", name); + } + if (proc_params(proc)) { + indent(&new_state); + fprintf(new_state.code, "struct %s_params *params = (struct %s_params *)args;\n", + name, name); + } - if (lower_vars(proc_params(proc))) - return -1; + /* allocates parameter slots */ + int ret = lower_params(&new_state, proc_params(proc)); + assert(ret == 0); - printf(");\n\n"); - return 0; -} + /* actually copies values into parameter slots */ + ret = lower_param_copies(&new_state, proc_params(proc)); + assert(ret == 0); -static int lower_proc(struct ast *proc) -{ - if (!proc_body(proc)) - return 0; + indent(&new_state); + fprintf(new_state.code, "ctx->global_args = args;\n"); - printf("fwd_err_t "); - if (strcmp("main", proc_id(proc)) == 0) - printf("fwd_main("); - else - printf("%s(", proc_id(proc)); + struct ast *block = proc_body(proc); + ret = lower_stmt_list(&new_state, block_body(block), true); - if (lower_vars(proc_params(proc))) - return -1; + fprintf(new_state.code, "}\n\n"); + fprintf(new_state.ctx, "};\n\n"); - printf(")\n"); + fclose(new_state.code); + fclose(new_state.ctx); + assert(code_buf); + assert(ctx_buf); - struct state state = {0}; - if (lower_block(&state, proc_body(proc), true)) - return -1; + add_defn(state, code_buf); + add_type(state, ctx_buf); - printf("\n\n"); - return 0; + free(start_of); + free(proto); + free(name); + return ret; } int lower(struct scope *root) { - printf("#include <fwdlib.hpp>\n"); + struct ast *main = file_scope_find_symbol(root, "main"); + if (!main) { + error("no main"); + return -1; + } - foreach(visible, visible, &root->symbols) { - struct ast *proc = visible->data; - assert(proc->k == AST_PROC_DEF); - if (lower_proto(proc)) - return -1; + if (main->k != AST_PROC_DEF) { + error("main is not a procedure"); + return -1; } - foreach(visible, visible, &root->symbols) { - struct ast *proc = visible->data; - if (lower_proc(proc)) - return -1; + struct string_vec defns = string_vec_create(0); + struct string_vec decls = string_vec_create(0); + struct string_vec types = string_vec_create(0); + struct proc_set procs = proc_set_create(0); + + struct state state = { + .indent = 0, + .ctx = NULL, + .code = NULL, + .defns = &defns, + .decls = &decls, + .types = &types, + .procs = &procs + }; + + if (lower_proc(&state, main)) + return -1; + + /* placeholder, should really be calculated to be some maximum frame + * size or something */ + printf("#define FWD_FRAME_SIZE %d\n", 1024); + printf("#include <fwd.h>\n\n"); + + foreach(string_vec, s, state.types) { + puts(*s); + free(*s); } + foreach(string_vec, s, state.decls) { + puts(*s); + free(*s); + } + + foreach(string_vec, s, state.defns) { + puts(*s); + free(*s); + } - puts("int main()"); - puts("{"); - puts(" fwd_err_t err = fwd_main();"); - puts(" if (err) {"); - puts(" fprintf(stderr, \"%s\", err);"); - puts(" return -1;"); - puts(" }"); - puts("}"); + char *name = mangle2(main); + printf("int main()\n"); + printf("{\n"); + printf(" fwd_stack_t stack = create_fwd_stack();\n"); + printf(" void *args = fwd_stack_alloc(&stack);\n"); + printf(" stack = %s(stack, args);\n", name); + printf(" fwd_stack_free(&stack, args);\n"); + printf(" destroy_fwd_stack(&stack);\n\n"); + printf("}\n"); + + /* modules require a register function of some kind, implement a stub */ + printf("int fwd_register(struct fwd_state *state, const char *name, fwd_extern_t func, fwd_type_t rtype, ...) {return 0;}\n"); + + string_vec_destroy(&defns); + string_vec_destroy(&decls); + string_vec_destroy(&types); + proc_set_destroy(&procs); + free(name); return 0; } @@ -45,38 +45,6 @@ static struct state create_state(struct state *parent) static void destroy_state(struct state *state) { moved_destroy(&state->moved); - moved_destroy(&state->queued); - moved_destroy(&state->referenced); -} - -struct rm_move { - struct ast_pair data; - struct state *owner; -}; - -static struct rm_move remove_move(struct state *state, struct ast *def) -{ - if (!state) - return (struct rm_move){.data = {}, .owner = NULL}; - - struct ast_pair search = {.def = def}; - struct ast_pair *found = moved_find(&state->moved, search); - if (found) { - moved_remove_found(&state->moved, found); - struct rm_move r = {.data = *found, .owner = state}; - moved_free_found(&state->moved, found); - return r; - } - - return remove_move(state->parent, def); -} - -static void reinsert_move(struct rm_move move) -{ - if (!move.owner) - return; - - moved_insert(&move.owner->moved, move.data); } static struct ast_pair *find_move(struct state *state, struct ast *def) @@ -164,42 +132,15 @@ static void push_up(struct state *state) merge(parent, state); } -static void mark_queued(struct state *state) -{ - if (moved_len(&state->moved) == 0) - return; - - foreach(moved, m, &state->moved) { - moved_insert(&state->queued, *m); - } - - /* empty out moves now that they're all queued */ - moved_destroy(&state->moved); - state->moved = moved_create(); -} - -static void mark_unqueued(struct state *state) -{ - if (moved_len(&state->queued) == 0) - return; - - foreach(moved, q, &state->queued) { - moved_insert(&state->moved, *q); - } - - moved_destroy(&state->queued); - state->queued = moved_create(); -} - static void forget_references(struct state *state) { moved_destroy(&state->referenced); state->referenced = moved_create(); } -static int mvcheck(struct state *state, struct ast *node); -static int mvcheck_list(struct state *state, struct ast *nodes); -static int mvcheck_statements(struct state *state, struct ast *nodes); +static int mvcheck_expr(struct state *state, struct ast *node); +static int mvcheck_expr_list(struct state *state, struct ast *nodes); +static int mvcheck_statements(struct state *state, struct ast *nodes, bool last); static int refcheck_id(struct state *state, struct ast *node) { @@ -251,20 +192,7 @@ static int mvcheck_deref(struct state *state, struct ast *node) { /** @todo good enough for now but probably won't hold when we start * doing element lookups in structs etc. */ - return mvcheck(state, deref_base(node)); -} - -static int mvcheck_proc(struct state *state, struct ast *node) -{ - /* extern, can't really do anything so just say it's fine */ - if (!proc_body(node)) - return 0; - - struct state new_state = create_state(state); - /* we don't need to merge things into the parent state */ - int ret = mvcheck(&new_state, proc_body(node)); - destroy_state(&new_state); - return ret; + return mvcheck_expr(state, deref_base(node)); } static int total_check_single(struct state *state, struct scope *scope) @@ -295,47 +223,51 @@ static int total_check_single(struct state *state, struct scope *scope) static int total_check_proc(struct state *state, struct scope *scope) { int ret = total_check_single(state, scope); - if (scope->flags & SCOPE_PROC) + if (!scope->parent) return ret; - if (!scope->parent) + if (scope->flags & SCOPE_PROC) return ret; return ret | total_check_proc(state, scope->parent); } -static int mvcheck_block(struct state *state, struct ast *node) +static int mvcheck_block(struct state *state, struct ast *node, bool last) { + assert(node->k == AST_BLOCK); struct state new_state = create_state(state); - int ret = mvcheck_statements(&new_state, block_body(node)); + int ret = mvcheck_statements(&new_state, block_body(node), last); if (ret) { destroy_state(&new_state); return ret; } - if (block_error(node)) { - if (total_check_proc(&new_state, node->scope)) - semantic_info(node->scope, node, "at end of block"); - - /* mark us queued so moves are not visible for the next scopes, - * but appear to the caller function if we're in a closure */ - mark_queued(&new_state); - push_up(&new_state); - destroy_state(&new_state); - return 0; - } + if (last && total_check_proc(&new_state, node->scope)) + semantic_info(node->scope, node, "at end of block"); - if (total_check_single(&new_state, node->scope)) + else if (!last && total_check_single(&new_state, node->scope)) semantic_info(node->scope, node, "at end of block"); + /** @todo add exit analysis and run total_check_proc on those blocks */ push_up(&new_state); destroy_state(&new_state); return 0; } -static int mvcheck_call(struct state *state, struct ast *node) +static int mvcheck_closure(struct state *state, struct ast *node, bool last) +{ + struct state new_state = create_state(state); + new_state.pure = ast_flags(node, AST_FLAG_NOMOVES); + + int ret = mvcheck_block(&new_state, closure_body(node), last); + push_up(&new_state); + destroy_state(&new_state); + return ret; +} + +static int mvcheck_call(struct state *state, struct ast *node, bool last) { - if (mvcheck(state, call_expr(node))) + if (mvcheck_expr(state, call_expr(node))) return -1; struct ast *args = call_args(node); @@ -347,10 +279,35 @@ static int mvcheck_call(struct state *state, struct ast *node) if (arg->k == AST_CLOSURE) continue; - if (mvcheck(state, arg)) + if (mvcheck_expr(state, arg)) return -1; } + /* count how many closure groups call has. If the call is an exit point + * (last == true), then a single closure group must also be an exit + * point */ + size_t groups = 0; + foreach_node(arg, call_args(node)) { + if (arg->k != AST_CLOSURE) + continue; + + groups++; + struct ast *next = arg->n; + while (next && next->t->group == arg->t->group) + next = next->n; + + if (!next) + break; + + arg = next; + } + + if (!last && (groups > 0)) { + semantic_error(node->scope, node, + "calls with closures must currently be exit points, sorry!"); + return -1; + } + /* check into closures */ int ret = 0; struct state buffer_state = create_state(state); @@ -360,8 +317,7 @@ static int mvcheck_call(struct state *state, struct ast *node) continue; struct state arg_state = create_state(state); - ret = mvcheck(&arg_state, arg); - mark_unqueued(&arg_state); + ret = mvcheck_closure(&arg_state, arg, last && (groups == 1)); merge(&group_state, &arg_state); destroy_state(&arg_state); @@ -382,56 +338,8 @@ static int mvcheck_call(struct state *state, struct ast *node) destroy_state(&group_state); - /* the next section looks a bit weird, but it kind of makes sense. We - * don't know when the error branch might get taken, so we first check - * it 'before' the moves from any possible closures take place to see if - * there's a possibility that some variable gets leaked. The second time - * around we take the closure moves into account to see if the error - * branch might accidentally move an already moved variable, i.e. the - * user would need to add `own` blocks. */ - - /** @todo this is more or less what needs to happen for correctness, but - * might mean that warnings from leaks in the first check get reprinted - * in the second round, should probably add in some mechanism to check - * against that */ - if (call_err(node)) { - struct state err_state = create_state(state); - ret |= mvcheck(&err_state, call_err(node)); - destroy_state(&err_state); - } - else if (total_check_proc(state, node->scope)) { - semantic_info(node->scope, node, "in implicit err branch"); - } - push_up(&buffer_state); destroy_state(&buffer_state); - - if (call_err(node)) { - struct state err_state = create_state(state); - ret |= mvcheck(&err_state, call_err(node)); - - /* store results of this check */ - push_up(&err_state); - destroy_state(&err_state); - } - /* no need to check implicit error branch since it by definition can't - * move already moved variables */ - - return ret; -} - -static int mvcheck_closure(struct state *state, struct ast *node) -{ - struct state new_state = create_state(state); - new_state.pure = ast_flags(node, AST_FLAG_NOMOVES); - - int ret = mvcheck(&new_state, closure_body(node)); - push_up(&new_state); - destroy_state(&new_state); - - if (total_check_single(state, node->scope)) - semantic_info(node->scope, node, "in closure"); - return ret; } @@ -500,29 +408,36 @@ static int mvcheck_id(struct state *state, struct ast *node) static int mvcheck_let(struct state *state, struct ast *node) { - return mvcheck(state, let_expr(node)); + return mvcheck_expr(state, let_expr(node)); } static int mvcheck_init(struct state *state, struct ast *node) { - return mvcheck_list(state, init_body(node)); + return mvcheck_expr_list(state, init_body(node)); } -static int mvcheck_if(struct state *state, struct ast *node) +static int mvcheck_if(struct state *state, struct ast *node, bool last) { - if (mvcheck(state, if_cond(node))) + if (!last) { + semantic_error(node->scope, node, + "`if` statements must currently be exit points, sorry!"); return -1; + } + + assert(if_else(node)); + + /* don't check cond since it can't take ownership of anything */ struct state body_state = create_state(state); struct state else_state = create_state(state); - if (mvcheck(&body_state, if_body(node))) { + if (mvcheck_block(&body_state, if_body(node), last)) { destroy_state(&body_state); destroy_state(&else_state); return -1; } - if (mvcheck(&else_state, if_else(node))) { + if (mvcheck_block(&else_state, if_else(node), last)) { destroy_state(&body_state); destroy_state(&else_state); return -1; @@ -536,107 +451,124 @@ static int mvcheck_if(struct state *state, struct ast *node) return 0; } -static int mvcheck_unop(struct state *state, struct ast *node) +static int mvcheck_expr_list(struct state *state, struct ast *nodes) { - return mvcheck(state, unop_expr(node)); + foreach_node(node, nodes) { + if (mvcheck_expr(state, node)) + return -1; + } + + return 0; } -static int mvcheck_binop(struct state *state, struct ast *node) +static int mvcheck_construct(struct state *state, struct ast *node) { - if (mvcheck(state, binop_left(node))) - return -1; + foreach_node(expr, construct_members(node)) { + if (mvcheck_expr(state, expr)) + return -1; + } - return mvcheck(state, binop_right(node)); + return 0; } -static int mvcheck_comparison(struct state *state, struct ast *node) +static int mvcheck_construction(struct state *state, struct ast *node) { - if (mvcheck(state, comparison_left(node))) - return -1; - - return mvcheck(state, comparison_right(node)); + return mvcheck_expr(state, construction_expr(node)); } -static int mvcheck_list(struct state *state, struct ast *nodes) +static int mvcheck_as(struct state *state, struct ast *as) { - foreach_node(node, nodes) { - if (mvcheck(state, node)) - return -1; - } - - return 0; + return mvcheck_expr(state, as_expr(as)); } -static int mvcheck_statements(struct state *state, struct ast *nodes) +static int mvcheck_forget(struct state *state, struct ast *node) { - foreach_node(node, nodes) { - struct state new_state = create_state(state); - if (mvcheck(&new_state, node)) { - destroy_state(&new_state); - return -1; - } - - forget_references(&new_state); - push_up(&new_state); - destroy_state(&new_state); - } + struct ast *def = file_scope_find_symbol(node->scope, forget_id(node)); + assert(def); + /* act as if a move has happened */ + insert_move(state, def, node); return 0; } -static int mvcheck_err_branch(struct state *state, struct ast *node) +static int mvcheck_explode(struct state *state, struct ast *node) { - return mvcheck(state, err_branch_body(node)); + return mvcheck_expr(state, explode_expr(node)); } -static int mvcheck_own(struct state *state, struct ast *node) +static int mvcheck_write(struct state *state, struct ast *node) { - struct ast *def = file_scope_find_symbol(node->scope, own_id(node)); - assert(def); + return mvcheck_expr(state, write_src(node)); +} - if (is_callable(def->t)) { +static int mvcheck_nil_check(struct state *state, struct ast *node, bool last) +{ + if (!last) { + /** @todo would this be an internal error? */ semantic_error(node->scope, node, - "ownership of callable cannot be checked"); + "`nil check` must be exit point, sorry"); return -1; } - struct rm_move prev = remove_move(state, def); - int ret = mvcheck(state, own_body(node)); + assert(nil_check_rest(node)); - /* insert back */ - reinsert_move(prev); - return ret; + struct state body_state = create_state(state); + struct state rest_state = create_state(state); + + if (mvcheck_block(&body_state, nil_check_body(node), last)) { + destroy_state(&body_state); + destroy_state(&rest_state); + return -1; + } + + if (mvcheck_block(&rest_state, nil_check_rest(node), last)) { + destroy_state(&body_state); + destroy_state(&rest_state); + return -1; + } + + push_up(&body_state); + push_up(&rest_state); + + destroy_state(&body_state); + destroy_state(&rest_state); + return 0; } -static int mvcheck(struct state *state, struct ast *node) +static int mvcheck_expr(struct state *state, struct ast *node) { + /* unary, binary and comparison operators (a kind of binary operator, + * fair enough) must operate on primitive types, and as such don't need + * to be move checked as none of them can change ownership or a value */ if (is_unop(node)) - return mvcheck_unop(state, node); + return 0; if (is_binop(node)) - return mvcheck_binop(state, node); + return 0; if (is_comparison(node)) - return mvcheck_comparison(state, node); + return 0; switch (node->k) { - case AST_PROC_DEF: return mvcheck_proc (state, node); - case AST_BLOCK: return mvcheck_block (state, node); - case AST_CALL: return mvcheck_call (state, node); - case AST_CLOSURE: return mvcheck_closure (state, node); - case AST_ERR_BRANCH: return mvcheck_err_branch(state, node); - case AST_OWN: return mvcheck_own (state, node); - case AST_LET: return mvcheck_let (state, node); case AST_ID: return mvcheck_id (state, node); case AST_INIT: return mvcheck_init (state, node); - case AST_IF: return mvcheck_if (state, node); case AST_REF: return mvcheck_ref (state, node); case AST_DEREF: return mvcheck_deref (state, node); + case AST_CONSTRUCT: return mvcheck_construct (state, node); + case AST_AS: return mvcheck_as (state, node); + case AST_FORGET: return mvcheck_forget (state, node); + case AST_EXPLODE: return mvcheck_explode (state, node); + case AST_WRITE: return mvcheck_write (state, node); + case AST_CONSTRUCTION: return mvcheck_construction(state, node); + case AST_SIZEOF: + case AST_STRUCT_DEF: case AST_EMPTY: + case AST_IMPORT: case AST_CONST_INT: case AST_CONST_STR: case AST_CONST_FLOAT: case AST_CONST_CHAR: + case AST_NIL: return 0; default: break; } @@ -645,11 +577,72 @@ static int mvcheck(struct state *state, struct ast *node) return -1; } +static int mvcheck_statement(struct state *state, struct ast *node, bool last) +{ + switch (node->k) { + case AST_CALL: return mvcheck_call (state, node, last); + case AST_IF: return mvcheck_if (state, node, last); + case AST_NIL_CHECK: return mvcheck_nil_check(state, node, last); + case AST_EXPLODE: return mvcheck_explode (state, node); + case AST_LET: return mvcheck_let (state, node); + case AST_WRITE: return mvcheck_write (state, node); + case AST_FORGET: return mvcheck_forget (state, node); + case AST_EMPTY: return 0; + default: break; + } + + internal_error("unhandled move statement: %s", ast_str(node->k)); + return -1; +} + +static int mvcheck_statements(struct state *state, struct ast *nodes, bool last) +{ + foreach_node(node, nodes) { + struct state new_state = create_state(state); + if (mvcheck_statement(&new_state, node, last && !node->n)) { + destroy_state(&new_state); + return -1; + } + + forget_references(&new_state); + push_up(&new_state); + destroy_state(&new_state); + } + + return 0; +} + +static int mvcheck_proc(struct state *state, struct ast *node) +{ + /* extern, can't really do anything so just say it's fine */ + if (!proc_body(node)) + return 0; + + struct state new_state = create_state(state); + /* we don't need to merge things into the parent state */ + int ret = mvcheck_block(&new_state, proc_body(node), true); + destroy_state(&new_state); + return ret; +} + +static int mvcheck_top(struct state *state, struct ast *node) +{ + switch (node->k) { + case AST_PROC_DEF: return mvcheck_proc(state, node); + case AST_IMPORT: return 0; + case AST_STRUCT_DEF: return 0; + default: break; + } + + internal_error("missing top move check for %s", ast_str(node->k)); + return -1; +} + int mvcheck_root(struct ast *root) { foreach_node(node, root) { struct state state = create_state(NULL); - if (mvcheck(&state, node)) + if (mvcheck_top(&state, node)) return -1; destroy_state(&state); diff --git a/src/parser.y b/src/parser.y index 7b2c651..dad3210 100644 --- a/src/parser.y +++ b/src/parser.y @@ -36,7 +36,6 @@ %token <dbl> FLOAT %token <str> STRING %token <str> ID -%token <str> APPLY %token QUESTION "?" %token SQUOTE "'" @@ -70,19 +69,17 @@ %token RBRACE "}" %token LBRACKET "[" %token RBRACKET "]" +%token AS "as" %token IF "if" %token ELSE "else" %token NIL "nil" -%token OWN "own" %token PUB "pub" %token MUT "mut" %token CONTINUE "continue" %token IMPORT "import" %token ERROR "error" %token DOT "." -%token SCOPE "::" %token FATARROW "=>" -%token ERRARROW "!>" %right "[" "]" /* precedence */ @@ -97,24 +94,31 @@ %left "*" "/" "%" %left "as" "sizeof" %right "'" "!" "~" +%right ".." %left "." "=>" "(" ")" -%left "::" -%nterm <node> top unit proc proc_decl call closure var expr statement body -%nterm <node> vars exprs statements closures err trailing_closure -%nterm <node> opt_vars opt_exprs opt_statements opt_trailing_closure opt_err opt_error -%nterm <node> rev_vars rev_exprs rev_closures rev_statements -%nterm <node> let if nil own unop binop construct import +%nterm <node> top unit proc proc_decl call closure expr statement body +%nterm <node> exprs statements closures trailing_closure +%nterm <node> opt_exprs opt_statements opt_trailing_closure +%nterm <node> rev_exprs rev_closures rev_statements +%nterm <node> let put if nil unop binop construct import + +%nterm <node> opt_vars vars rev_vars var +%nterm <node> opt_params params rev_params param %nterm <node> struct struct_cont trait %nterm <node> type_param type_params opt_type_params %nterm <node> behaviour behaviours opt_behaviours %nterm <node> member members opt_members +%nterm <node> opt_elements elements rev_elements element +%nterm <node> instance template supertemplate +%nterm <node> opt_constructions constructions construction +%nterm <node> opt_deconstructions deconstructions deconstruction +%nterm <node> id impl forget +%nterm <node> explode %nterm <type> type rev_types types opt_types - - %{ /** Modifies the signature of yylex to fit our parser better. */ @@ -191,12 +195,29 @@ static char *strip(const char *s); %start input; %% -var +param : type ID { $$ = gen_var($2, $1, src_loc(@$)); } +rev_params + : rev_params "," param { $$ = $3; $$->n = $1; } + | rev_params "|" param { $$ = $3; $$->n = $1; opt_group($1, $3); } + | param + +params + : rev_params { $$ = reverse_ast_list($1); } + | rev_params "," { $$ = reverse_ast_list($1); } + +opt_params + : params + | {$$ = NULL;} + + +var + : param + | ID { $$ = gen_var($1, NULL, src_loc(@$)); } + rev_vars : rev_vars "," var { $$ = $3; $$->n = $1; } - | rev_vars "|" var { $$ = $3; $$->n = $1; opt_group($1, $3); } | var vars @@ -208,13 +229,13 @@ opt_vars | {$$ = NULL;} proc - : ID "(" opt_vars ")" body { - $$ = gen_proc($[ID], $[opt_vars], NULL, $[body], src_loc(@$)); + : ID "(" opt_params ")" body { + $$ = gen_proc($[ID], $[opt_params], NULL, $[body], src_loc(@$)); } proc_decl - : ID "(" opt_vars ")" ";" { - $$ = gen_proc($[ID], $[opt_vars], NULL, NULL, src_loc(@$)); + : ID "(" opt_params ")" ";" { + $$ = gen_proc($[ID], $[opt_params], NULL, NULL, src_loc(@$)); } binop @@ -231,16 +252,15 @@ binop | expr ">=" expr { $$ = gen_comparison(AST_GE, $1, $3, src_loc(@$)); } | expr "!=" expr { $$ = gen_comparison(AST_NE, $1, $3, src_loc(@$)); } | expr "==" expr { $$ = gen_comparison(AST_EQ, $1, $3, src_loc(@$)); } + | id "~" id { $$ = gen_paste($1, $3, src_loc(@$)); } unop : "-" expr { $$ = gen_unop(AST_NEG, $2, src_loc(@$)); } - | "!" expr { $$ = gen_unop(AST_LNOT, $2, src_loc(@$)); } + | "!" expr { $$ = gen_unop(AST_LNOT, $2, src_loc(@$)); } + | "~" expr { $$ = gen_unop(AST_BNEG, $2, src_loc(@$)); } type - : ID { $$ = tgen_id($[ID], src_loc(@$)); } - | APPLY "[" opt_types "]" { - $$ = tgen_construct($[APPLY], $[opt_types], src_loc(@$)); - } + : ID { $$ = tgen_id($1, src_loc(@$)); } | "(" opt_types ")" { $$ = tgen_closure($2, src_loc(@$)); } | "&&" type { if ($2->k == TYPE_CLOSURE) { @@ -278,26 +298,54 @@ opt_types : types | { $$ = NULL; } -construct - : APPLY "{" opt_exprs "}" { - $$ = gen_init($[APPLY], NULL, $[opt_exprs], src_loc(@$)); +construction + : expr "=>" ID { + $$ = gen_construction($3, $1, src_loc(@$)); } - | APPLY "[" opt_types "]" "{" opt_exprs "}" { - $$ = gen_init($[APPLY], $[opt_types], $[opt_exprs], src_loc(@$)); + +constructions + : constructions "," construction { $$ = $3; $$->n = $1; } + | construction + +opt_constructions + : constructions + | { $$ = NULL; } + +deconstruction + : ID "=>" var { + $$ = gen_deconstruction($1, $3, src_loc(@$)); + } + +deconstructions + : deconstructions "," deconstruction { $$ = $3; $$->n = $1; } + | deconstruction + +opt_deconstructions + : deconstructions + | { $$ = NULL; } + +construct + : "[" opt_constructions "]" ID { + $$ = gen_construct($4, $2, src_loc(@$)); } +id + : ID { $$ = gen_id($1, src_loc(@$)); } + expr - : expr "." ID { $$ = gen_dot($3, $1, src_loc(@$)); } + : "sizeof" "(" type ")" { $$ = gen_sizeof($3, src_loc(@$)); } | STRING { $$ = gen_const_str(strip($1), src_loc(@$)); } | FLOAT { $$ = gen_const_float($1, src_loc(@$)); } | BOOL { $$ = gen_const_bool($1, src_loc(@$)); } | CHAR { $$ = gen_const_char($1, src_loc(@$)); } | INT { $$ = gen_const_int($1, src_loc(@$)); } - | ID { $$ = gen_id($1, src_loc(@$)); } + | "*" { $$ = gen_nil(src_loc(@$)); } | "(" expr ")" { $$ = $2; } | expr "&" { $$ = gen_ref($1, src_loc(@$)); } | expr "*" { $$ = gen_deref($1, src_loc(@$)); } + | expr "as" type { $$ = gen_as($1, $3, src_loc(@$)); } | construct + | id | binop | unop @@ -346,64 +394,58 @@ closures } } -err - : "!>" ID body { - $$ = gen_err_branch($2, $3, src_loc(@$)); - } - -opt_err - : err - | { $$ = NULL; } - call - : expr "(" opt_exprs ")" ";" opt_err { - $$ = gen_call($1, $[opt_exprs], $[opt_err], src_loc(@$)); + : expr "(" opt_exprs ")" { + $$ = gen_call($1, $[opt_exprs], src_loc(@$)); } - | expr "(" opt_exprs ")" "=>" opt_vars ";" opt_err { + | expr "(" opt_exprs ")" "=>" opt_vars ";" { /* the rest of the function body is our closure, gets fixed up * later */ struct ast *closure = gen_closure($[opt_vars], NULL, src_loc(@$)); ast_append(&$[opt_exprs], closure); - $$ = gen_call($[expr], $[opt_exprs], $[opt_err], src_loc(@$)); + $$ = gen_call($[expr], $[opt_exprs], src_loc(@$)); } - | expr "(" opt_exprs ")" closures opt_err { + | expr "(" opt_exprs ")" closures { ast_append(&$[opt_exprs], $[closures]); - $$ = gen_call($[expr], $[opt_exprs], $[opt_err], src_loc(@$)); + $$ = gen_call($[expr], $[opt_exprs], src_loc(@$)); } +put + : put "*" { $$ = gen_put($1, src_loc(@$)); } + | id "*" { $$ = gen_put($1, src_loc(@$)); } + let : expr "=>" var ";" { $$ = gen_let($3, $1, src_loc(@$)); } + | expr "=>" put ";" { $$ = gen_write($3, $1, src_loc(@$)); } + +explode + : expr "=>" "[" opt_deconstructions "]" { + $$ = gen_explode($4, $1, src_loc(@$)); + } if - : "if" expr body { $$ = gen_if($2, $3, NULL, src_loc(@$)); } - | "if" expr body "else" body { $$ = gen_if($2, $3, $5, src_loc(@$)); } + : "if" expr body "else" body { $$ = gen_if($2, $3, $5, src_loc(@$)); } | "if" expr body "else" if { $$ = gen_if($2, $3, $5, src_loc(@$)); } nil - : "nil" ID body "=>" var { - $$ = gen_nil($2, $3, $5, src_loc(@$)); + : "nil" expr body "=>" var ";" { + /** @todo should nil check define new scope for created + * variable? */ + $$ = gen_nil_check($2, $3, $5, src_loc(@$)); } -own - : "own" ID body { - $$ = gen_own($2, $3, src_loc(@$)); +forget + : "nil" ID ";" { + $$ = gen_forget($2, src_loc(@$)); } -opt_error - : "error" STRING { - $$ = gen_error($2, NULL, src_loc(@$)); - } - | "error" ID { - $$ = gen_error(NULL, gen_id($2, src_loc(@$)), src_loc(@$)); - } - | { $$ = NULL; } - statement : call + | forget | body + | explode | let | nil - | own | if | ";" { $$ = gen_empty(src_loc(@$)); } @@ -419,14 +461,14 @@ opt_statements | { $$ = NULL; } body - : "{" opt_statements opt_error "}" { - $$ = gen_block($2, $3, src_loc(@$)); + : "{" opt_statements "}" { + $$ = gen_block($2, src_loc(@$)); } behaviour - : APPLY ";" { $$ = gen_trait_apply($1, src_loc(@$)); } - | proc_decl ";" + : proc_decl | proc + | impl behaviours : behaviours behaviour { $$ = $1; $1->n = $2; } @@ -436,16 +478,18 @@ opt_behaviours : behaviours | { $$ = NULL; } +impl + : ID "!" { $$ = gen_implements($1, src_loc(@$)); } + member - : var ";" - | behaviour + : param members - : members member { $$ = $1; $1->n = $2; } + : members ";" member { $$ = $3; $$->n = $1; } | member opt_members - : members + : members ";" | { $$ = NULL; } type_param @@ -464,39 +508,77 @@ opt_type_params : type_params | { $$ = NULL; } +element + : proc + | struct + | impl + | var ";" + +rev_elements + : rev_elements element { $$ = $2; $$->n = $1; } + | element + +elements + : rev_elements { $$ = reverse_ast_list($1); } + +opt_elements + : elements + | { $$ = NULL; } + +template + : ID "[" opt_type_params "]" "(" opt_vars ")" "{" opt_elements "}" { + $$ = gen_template($1, $3, $6, $9, src_loc(@$)); + } + +supertemplate + : ID "[" opt_type_params "]" "(" opt_vars ")" "=" + id "[" opt_types "]" "(" opt_exprs ")" "{" opt_elements "}" { + struct ast *pseudoinst = gen_instance(NULL, $9, $11, $14, src_loc(@$)); + $$ = gen_supertemplate($1, $3, $6, pseudoinst, $17, src_loc(@$)); + } + struct - : ID "[" opt_type_params "]" "{" opt_members "}" { - $$ = gen_struct($1, $3, $6, src_loc(@$)); + : ID "{" opt_members "}" { + $$ = gen_struct($1, $3, src_loc(@$)); } struct_cont - : "continue" ID "[" opt_type_params "]" "{" opt_behaviours "}" { - $$ = gen_struct_cont($2, $4, $7, src_loc(@$)); + : "continue" ID "{" opt_behaviours "}" { + $$ = gen_instance_cont($2, $4, src_loc(@$)); } trait - : ID "{" opt_behaviours "}" { - $$ = gen_trait($1, $3, src_loc(@$)); + : ID "=" "{" opt_behaviours "}" { + $$ = gen_trait($1, $4, src_loc(@$)); + } + +instance + : ID "=" id "[" opt_types "]" "(" opt_exprs ")" { + $$ = gen_instance($1, $3, $5, $8, src_loc(@$)); } import : "import" STRING { - $$ = gen_import($2, src_loc(@$)); + $$ = gen_import(strip($2), src_loc(@$)); } top : proc - | proc_decl | struct | struct_cont | import | trait - | "pub" proc { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } - | "pub" proc_decl { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } - | "pub" struct { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } - | "pub" struct_cont { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } - | "pub" import { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } - | "pub" trait { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } + | instance + | template + | supertemplate + | "pub" proc { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } + | "pub" struct { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } + | "pub" struct_cont { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } + | "pub" import { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } + | "pub" trait { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } + | "pub" instance { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } + | "pub" template { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } + | "pub" supertemplate { $$ = $2; ast_set_flags($$, AST_FLAG_PUBLIC); } | error { $$ = gen_empty(src_loc(@$)); parser->failed = true; diff --git a/src/path.c b/src/path.c new file mode 100644 index 0000000..87cd26b --- /dev/null +++ b/src/path.c @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +/** + * @file path.c + * + * Path handling helper implementations. + */ + +#include <stddef.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> + +#include <fwd/path.h> +#include <fwd/debug.h> + +char *fwd_basename(const char *file) +{ + size_t l = strlen(file); + size_t n = l - 1; + while (--n) { + if (file[n] == '/') + break; + } + + if (n == 0) + return strdup(file); + + return strndup(file + n + 1, l - n); +} + +char *fwd_dirname(const char *file) +{ + size_t l = strlen(file); + size_t n = l - 1; + while (--n) { + if (file[n] == '/') + break; + } + + return strndup(file, n); +} + +char *fwd_cwdname() +{ + size_t size; + long path_max = pathconf(".", _PC_PATH_MAX); + if (path_max == -1) + size = 1024; + else + size = (size_t)path_max; + + char *buf = malloc(size); + if (!buf) + return NULL; + + if (!getcwd(buf, size)) { + error("%s\n", strerror(errno)); + free(buf); + return NULL; + } + + return buf; +} diff --git a/src/rewrite.c b/src/rewrite.c new file mode 100644 index 0000000..e3cea01 --- /dev/null +++ b/src/rewrite.c @@ -0,0 +1,96 @@ +#include <fwd/rewrite.h> +#include <stddef.h> +#include <stdlib.h> + +struct type_helper { + char *orig; + char *new; +}; + +/* types are simple enough (for now) that this works fine */ +static int rewrite_type_ids(struct type *type, char *orig, char *new) +{ + if (type->k == TYPE_ID && strcmp(type->id, orig) == 0) { + char *r = strdup(new); + free(type->id); + type->id = r; + } + + if (type->t0 && rewrite_type_ids(type->t0, orig, new)) + return -1; + + if (type->n && rewrite_type_ids(type->n, orig, new)) + return -1; + + return 0; +} + +static int rewrite_type_visit(struct ast *node, struct type_helper *helper) +{ + if (node->t2) + return rewrite_type_ids(node->t2, helper->orig, helper->new); + + return 0; +} + +int rewrite_types(struct ast *node, char *orig, char *new) +{ + struct type_helper helper = {.orig = orig, .new = new}; + return ast_visit((ast_callback_t)rewrite_type_visit, NULL, node, &helper); +} + +/* not the fastest thing in the world but should work well enough for now */ +static char *rewrite_hole(char *old, char *new) +{ + /* skip "<>" */ + size_t on = strlen(old) - 2; + size_t nn = strlen(new); + + /* +1 for null terminator */ + char *r = malloc(on + nn + 1); + + memcpy(r, new, nn); + + /* +2 to skip "<>", +1 for null terminator */ + memcpy(r + nn, old + 2, on + 1); + return r; +} + +static int rewrite_type_holes(struct type *type, char *new) +{ + if (type->id && strncmp(type->id, "<>", 2) == 0) { + char *r = rewrite_hole(type->id, new); + free(type->id); + type->id = r; + } + + if (type->t0 && rewrite_type_holes(type->t0, new)) + return -1; + + if (type->n && rewrite_type_holes(type->n, new)) + return -1; + + return 0; +} + +static int rewrite_holes_visit(struct ast *node, char *new) +{ + if (node->k == AST_CONST_STR) + return 0; + + if (node->s && strncmp(node->s, "<>", 2) == 0) { + char *r = rewrite_hole(node->s, new); + free(node->s); + node->s = r; + } + + if (node->t2 && rewrite_type_holes(node->t2, new)) + return -1; + + return 0; +} + +int rewrite_holes(struct ast *node, char *new) +{ + return ast_visit((ast_callback_t)rewrite_holes_visit, NULL, node, new); +} diff --git a/src/scope.c b/src/scope.c index fe35d58..e49b828 100644 --- a/src/scope.c +++ b/src/scope.c @@ -8,10 +8,11 @@ #include <stddef.h> #include <string.h> -#include <stdio.h> #include <stdarg.h> #include <stdlib.h> #include <assert.h> +#include <stdio.h> +#include <dlfcn.h> #include <fwd/debug.h> #include <fwd/scope.h> @@ -29,8 +30,10 @@ struct scope *create_scope() scope->number = counter++; scope->symbols = visible_create(16); /* arbitrary number */ - scope->traits = visible_create(1); - scope->types = visible_create(1); + scope->templates = visible_create(0); + scope->traits = visible_create(0); + scope->types = visible_create(0); + scope->mods = mod_vec_create(0); return scope; } @@ -44,7 +47,14 @@ void destroy_scope(struct scope *scope) free((void *)scope->fctx.fname); } + foreach(mod_vec, m, &scope->mods) { + dlclose(*m); + } + + mod_vec_destroy(&scope->mods); + visible_destroy(&scope->symbols); + visible_destroy(&scope->templates); visible_destroy(&scope->traits); visible_destroy(&scope->types); @@ -100,7 +110,7 @@ struct ast *file_scope_find_symbol(struct scope *scope, char *id) int scope_add_type(struct scope *scope, struct ast *type) { assert(type->k == AST_STRUCT_DEF); - struct ast **exists = visible_insert(&scope->symbols, type->s, type); + struct ast **exists = visible_insert(&scope->types, type->s, type); if (!exists) { internal_error("failed inserting type into scope"); return -1; @@ -115,42 +125,6 @@ int scope_add_type(struct scope *scope, struct ast *type) return 0; } -int scope_add_chain(struct scope *scope, struct ast *type) -{ - assert(type->k == AST_STRUCT_CONT); - struct ast *exists = file_scope_find_type(scope, type->s); - if (!exists) { - semantic_error(scope, type, "no previous definition"); - return -1; - } - - assert(exists->k == AST_STRUCT_DEF || exists->k == AST_STRUCT_CONT); - if (ast_flags(exists, AST_FLAG_PUBLIC) || !ast_flags(type, AST_FLAG_PUBLIC)) { - type->chain = exists; - struct ast **v = visible_insert(&scope->types, type->s, type); - if (!v) { - internal_error("failed inserting type into chain"); - return -1; - } - - /* overwrite root */ - if (*v != type) - *v = type; - - return 0; - } - - struct ast *next = exists->chain; - while (next->k == AST_STRUCT_CONT && !ast_flags(next, AST_FLAG_PUBLIC)) { - exists = next; - next = next->chain; - } - - exists->chain = type; - type->chain = exists; - return 0; -} - struct ast *scope_find_type(struct scope *scope, char *id) { struct ast **v = visible_find(&scope->types, id); @@ -175,7 +149,7 @@ struct ast *file_scope_find_type(struct scope *scope, char *id) int scope_add_trait(struct scope *scope, struct ast *trait) { assert(trait->k == AST_TRAIT_DEF); - struct ast **exists = visible_insert(&scope->symbols, trait->s, trait); + struct ast **exists = visible_insert(&scope->traits, trait->s, trait); if (!exists) { internal_error("failed inserting trait into scope"); return -1; @@ -189,6 +163,7 @@ int scope_add_trait(struct scope *scope, struct ast *trait) return 0; } + struct ast *scope_find_trait(struct scope *scope, char *id) { struct ast **v = visible_find(&scope->traits, id); @@ -210,6 +185,44 @@ struct ast *file_scope_find_trait(struct scope *scope, char *id) return file_scope_find_trait(scope->parent, id); } +int scope_add_template(struct scope *scope, struct ast *template) +{ + struct ast **exists = visible_insert(&scope->templates, template->s, template); + if (!exists) { + internal_error("failed inserting template into scope"); + return -1; + } + + if (*exists != template) { + semantic_error(scope, template, "template redefined"); + semantic_info(scope, *exists, "previously here"); + return -1; + } + + return 0; +} + +struct ast *scope_find_template(struct scope *scope, char *id) +{ + struct ast **v = visible_find(&scope->templates, id); + if (!v) + return NULL; + + return *v; +} + +struct ast *file_scope_find_template(struct scope *scope, char *id) +{ + if (!scope) + return NULL; + + struct ast *found = scope_find_template(scope, id); + if (found) + return found; + + return file_scope_find_template(scope->parent, id); +} + void scope_add_scope(struct scope *parent, struct scope *child) { assert(parent); |
