aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore9
-rw-r--r--.gitmodules3
-rw-r--r--Makefile8
-rw-r--r--TODO51
m---------deps/conts0
m---------deps/covsrv0
-rw-r--r--examples/callback.fwd16
-rw-r--r--examples/err.fwd17
-rw-r--r--examples/fib.fwd25
-rw-r--r--examples/guard.fwd40
-rw-r--r--examples/own.fwd33
-rw-r--r--examples/ptrs.fwd22
-rw-r--r--examples/pure_move.fwd18
-rw-r--r--examples/references.fwd24
-rw-r--r--examples/uniq.fwd52
-rw-r--r--include/fwd/ast.h292
-rw-r--r--include/fwd/compiler.h1
-rw-r--r--include/fwd/coverage.h21
-rw-r--r--include/fwd/debug.h5
-rw-r--r--include/fwd/mod.h192
-rw-r--r--include/fwd/path.h38
-rw-r--r--include/fwd/rewrite.h9
-rw-r--r--include/fwd/scope.h12
-rw-r--r--include/fwd/tracker.h7
-rw-r--r--lib/fwd.h66
-rw-r--r--mod/Makefile10
-rw-r--r--mod/io.c42
-rw-r--r--mod/mem.c51
-rw-r--r--mod/util.c20
-rwxr-xr-xscripts/coverage34
-rwxr-xr-xscripts/gen-deps2
-rw-r--r--scripts/makefile12
-rw-r--r--src/analyze.c905
-rw-r--r--src/ast.c245
-rw-r--r--src/compiler.c141
-rw-r--r--src/debug.c87
-rw-r--r--src/lexer.l38
-rw-r--r--src/lower.c1436
-rw-r--r--src/main.c5
-rw-r--r--src/move.c433
-rw-r--r--src/parser.y394
-rw-r--r--src/path.c86
-rw-r--r--src/rewrite.c108
-rw-r--r--src/scope.c105
-rw-r--r--src/tracker.c22
-rw-r--r--tests/Makefile41
-rw-r--r--tests/fib/fib.fwd24
-rw-r--r--tests/fib/test.mk1
-rw-r--r--tests/guard/guard.fwd21
-rw-r--r--tests/guard/test.mk1
-rw-r--r--tests/opt_group/opt_group.fwd (renamed from examples/opt_group.fwd)0
-rw-r--r--tests/opt_group/test.mk1
-rw-r--r--tests/ptrs/ptrs.fwd8
-rw-r--r--tests/ptrs/test.mk1
-rwxr-xr-xtests/scripts/gen-xfail23
-rwxr-xr-xtests/scripts/gen-xpass12
-rw-r--r--tests/scripts/makefile27
-rwxr-xr-xtests/scripts/run-xfail58
-rwxr-xr-xtests/scripts/run-xpass53
-rw-r--r--tests/sum/sum.fwd28
-rw-r--r--tests/sum/test.mk1
-rw-r--r--tests/type_mismatch/test.mk1
-rw-r--r--tests/type_mismatch/type_mismatch.fwd5
-rw-r--r--tests/vec/test.mk1
-rw-r--r--tests/vec/vec.fwd153
-rw-r--r--uncrustify.conf2
66 files changed, 4223 insertions, 1376 deletions
diff --git a/.gitignore b/.gitignore
index 20d3be2..a6392b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,15 @@ deps.mk
docs/output
build
fwd
+*.so
gen/*.c
gen/*.inc
!include/fwd
+reports
+coverage
+!scripts/coverage
+tests.mk
+vgcore.*
+*.gcda
+*.gcno
+*.d
diff --git a/.gitmodules b/.gitmodules
index bf47852..ffde00b 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
[submodule "deps/conts"]
path = deps/conts
url = https://metanimi.dy.fi/git/conts
+[submodule "deps/covsrv"]
+ path = deps/covsrv
+ url = https://metanimi.dy.fi/cgit/covsrv
diff --git a/Makefile b/Makefile
index 7708e24..1f20ab0 100644
--- a/Makefile
+++ b/Makefile
@@ -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,13 +18,17 @@ 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:
@echo -n > deps.mk
@./scripts/gen-deps -p FWD -c COMPILE_FWD -b fwd "$(FWD_SOURCES)"
+.PHONY: check
+check: all
+ $(MAKE) -C tests -k check
+
CLEANUP := build deps.mk fwd
CLEANUP_CMD :=
FWD_SOURCES :=
@@ -50,6 +55,7 @@ RM = rm
.PHONY: clean
clean:
+ $(MAKE) -C tests clean
$(RM) -rf $(CLEANUP)
.PHONY: clean_docs
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..9368f6f
--- /dev/null
+++ b/TODO
@@ -0,0 +1,51 @@
++ 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);
+
+
++ Hmm, some kind of way to say 'this thing owns this reference' would be good.
+At the moment, I'm thinking something that parameter lists can be extended with
+ownership rules, so for example
+
+ at_vec(vec v, u64 i, (vec > &i64) ok)
+
+would return a 'new' vector and a reference into the vector. If the 'vec' is
+moved, the &i64 is marked invalidated, but the &i64 is allowed to be passed
+around as long as 'vec' is not moved.
+
+For more references, something like
+
+ hmm((owner > thing1 | thing2))
+
+is three parameters, thing1 and thing2 are both
+invalidated if owner is moved (getting two indexes from a vector to swap them?
+not sure). Not sure how complex this system should be, could there for example
+be a situation where
+
+ hmm((owner > thing1 > thing2))
+
+might make sense? For now, the parser only accepts the first form, (vec > &i64).
diff --git a/deps/conts b/deps/conts
-Subproject 56edd66ae3e8661d40d499bd7c72f3ffc7aac4e
+Subproject be71a36fd88941a5bc3f25d2e563c8aa0481327
diff --git a/deps/covsrv b/deps/covsrv
new file mode 160000
+Subproject 5ff642c98760e0aec4de6b334a187cb9b4484aa
diff --git a/examples/callback.fwd b/examples/callback.fwd
deleted file mode 100644
index f750b8b..0000000
--- a/examples/callback.fwd
+++ /dev/null
@@ -1,16 +0,0 @@
-fwd_println(auto s);
-
-callee()
-{
- fwd_println("Hello from callback!");
-}
-
-caller(*() callback)
-{
- callback();
-}
-
-main()
-{
- caller(callee);
-}
diff --git a/examples/err.fwd b/examples/err.fwd
deleted file mode 100644
index f2833e2..0000000
--- a/examples/err.fwd
+++ /dev/null
@@ -1,17 +0,0 @@
-do_something((auto, auto) ok);
-
-/* consume can fail */
-consume(auto a);
-
-main()
-{
- do_something() => auto a, auto b;
- consume(a);
- /* try commenting out error handler or consume(b); */
- !> e {
- consume(b);
- error "a failed"
- }
-
- consume(b);
-}
diff --git a/examples/fib.fwd b/examples/fib.fwd
deleted file mode 100644
index 7084b9d..0000000
--- a/examples/fib.fwd
+++ /dev/null
@@ -1,25 +0,0 @@
-/* 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 */
-
-fib(int n, (int) res)
-{
- if n < 2 {
- res(1);
- } else {
- fib(n - 1) => int f1;
- fib(n - 2) => int 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);
-}
diff --git a/examples/guard.fwd b/examples/guard.fwd
deleted file mode 100644
index df42ef3..0000000
--- a/examples/guard.fwd
+++ /dev/null
@@ -1,40 +0,0 @@
-/* 'auto' is a special type that currently is in place of generics, will
- * eventually be replaced but this is good enough to play around with */
-fwd_println(auto s);
-
-/* this is a hack, just creates two copies of whatever is passed to it.
- * Primitive types should probably be excluded from the move checking, and more
- * complex types would presumably implement some kind of .copy(), but at the
- * moment that's still TODO */
-fwd_copy(auto n, (auto, auto) n1);
-
-guard(bool cond, () err, () ok)
-{
- if cond {
- err();
- } else {
- ok();
- }
-}
-
-try_print_one(int a)
-{
- fwd_copy(a) => int a1, int a2;
- guard(a1 < 1) => {
- fwd_println("smaller than 1");
- } => ;
-
- fwd_copy(a2) => int a3, int a4;
- guard(a3 > 1) => {
- fwd_println("larger than 1");
- } => ;
-
- fwd_println(a4);
-}
-
-main()
-{
- try_print_one(0);
- try_print_one(1);
- try_print_one(2);
-}
diff --git a/examples/own.fwd b/examples/own.fwd
deleted file mode 100644
index 6634886..0000000
--- a/examples/own.fwd
+++ /dev/null
@@ -1,33 +0,0 @@
-do_something((auto) a);
-consume(auto a);
-
-main()
-{
- do_something() => auto a;
- do_something() => auto b;
- !> e {
- /* if do_something() fails, a is still alive, so we must kill
- * it. However, we might enter this error block after the
- * callback to do_something() was run, and a might've already
- * been moved. own just checks if we still own the resource and
- * executes the block.
- *
- * admittedly, purely syntactically it would look like b is
- * already alive at this point, but since it's actually within
- * the implicit closure, it is not. Might play around with ordering
- * the error block to come before implicit closures...
- */
- own a {consume(a);}
- error e
- }
-
- consume(a);
- !> e {
- /* if consume fails, b is still alive, but either this error
- * block runs (and throws an error) or the next consume is run,
- * so own is not necessary here */
- consume(b);
- error e
- }
- consume(b);
-}
diff --git a/examples/ptrs.fwd b/examples/ptrs.fwd
deleted file mode 100644
index a0f8aac..0000000
--- a/examples/ptrs.fwd
+++ /dev/null
@@ -1,22 +0,0 @@
-fwd_null(auto x, () null, (&auto) ok);
-fwd_copy(auto x, (auto, auto) ok);
-fwd_println(auto x);
-fwd_intalloc((*int) ok);
-
-main()
-{
- fwd_intalloc() => *int i;
- fwd_copy(i) => *int i1, *int i2;
-
- /* convert raw pointer to reference, unsure if this should be a
- * built-in or a library feature */
- fwd_null(i1) => {
- fwd_println("Pointer was null");
- /* error out or something */
- } => &int i;
-
- fwd_println(i);
-
- /* Try uncommenting, deref of raw pointer is not allowed! */
- // i* + 20 => int something;
-}
diff --git a/examples/pure_move.fwd b/examples/pure_move.fwd
deleted file mode 100644
index d32b9d5..0000000
--- a/examples/pure_move.fwd
+++ /dev/null
@@ -1,18 +0,0 @@
-requires_pure(&() p)
-{
- p();
- /* ok since closure is pure */
- p();
-}
-
-main()
-{
- 20 => int twenty;
- requires_pure() &=> {
- /* Try uncommenting!
- * Not allowed in pure context (though primitives should maybe
- * be excluded just to make people's lives easier?)
- */
- // twenty + 10 => int thirty;
- }
-}
diff --git a/examples/references.fwd b/examples/references.fwd
deleted file mode 100644
index 5b73aaf..0000000
--- a/examples/references.fwd
+++ /dev/null
@@ -1,24 +0,0 @@
-references(&int a, &int b, () r)
-{
- /* don't have assignment and not quite sure if I want to have to
- * dereference references so I guess we can't really do much here at the
- * moment, heh */
- r();
-}
-
-main()
-{
- 20 => int twenty;
- 30 => int thirty;
- references(twenty&, thirty&) => {
- 20 + 30 => int fifty; /* ok */
- };
-
- /* references are not active anymore so we can reference again */
- references(twenty&, thirty&) => {
- /* Try uncommenting!
- * Not ok since twenty/thirty is actively borrowed
- */
- // twenty + thirty => int fifty;
- };
-}
diff --git a/examples/uniq.fwd b/examples/uniq.fwd
deleted file mode 100644
index cc6e0af..0000000
--- a/examples/uniq.fwd
+++ /dev/null
@@ -1,52 +0,0 @@
-/* not entirely sure about the final syntax just yet but something like this */
-
-/* 'extern' functions (though some should probably be generic?) */
-fwd_getline(
- (optional![string]) next);
-
-fwd_some(optional![string] o,
- (string) something | () nothing);
-
-fwd_insert(unordered_set![string] set, string line,
- (unordered_set![string]) next);
-
-fwd_destroy(auto a);
-
-/* at some point I'll probably add in a type system as well, but for now let's
- * pretend we're static-dynamic (or dynamic at compiletime? dunno) */
-readlines(unordered_set![string] set, (unordered_set![string]) next)
-{
- fwd_getline() => optional![string] line;
- !> e {
- own set {fwd_destroy(set);}
- error e
- }
-
- fwd_some(line) => string line {
- /* we had something in our option */
- fwd_insert(set, line) => unordered_set![string] set;
-
- /* at the moment the only supported looping construct is
- * recursion */
- readlines(set, next);
- } => {
- /* option was illegal, we've read all input there is */
- next(set);
- }
-}
-
-fwd_foreach(unordered_set![string] set,
- (string) callback);
-
-fwd_println(string s);
-
-main()
-{
- /* fwdlib.hpp uses namespace std, not good practice but allows us to do
- * stuff like this without really caring about implementing "::" */
- unordered_set![string]{} => unordered_set![string] set;
- readlines(set) => unordered_set![string] set;
- fwd_foreach(set) => string node {
- fwd_println(node);
- }
-}
diff --git a/include/fwd/ast.h b/include/fwd/ast.h
index 36aa8c8..9b474c9 100644
--- a/include/fwd/ast.h
+++ b/include/fwd/ast.h
@@ -9,6 +9,8 @@
#include <assert.h>
#include <stdbool.h>
+#include <fwd/coverage.h>
+
/**
* @file ast.h
*
@@ -32,10 +34,65 @@ 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;
+}
+
+static inline bool is_closure_type(enum type_kind k)
+{
+ switch (k) {
+ case TYPE_CLOSURE:
+ case TYPE_PURE_CLOSURE:
+ return true;
+ default:
+ return false;
+ }
+
+ return false;
+}
+
+static inline bool is_c_type(enum type_kind k)
+{
+ return is_int_type(k) || is_ptr_type(k);
+}
+
struct type {
enum type_kind k;
@@ -48,6 +105,15 @@ struct type {
/* next */
struct type *n;
+ /* used in parameter lists; this param owns the next one
+ *
+ * Is a pointer necessary here? A bool could work as well, as long as we're
+ * just doing 'this owns the next element in this list', but if we
+ * eventually support more complex relationships, like 'this owns these
+ * next params and one of the next params owns at least one more' or
+ * something along those lines, but that's an unsure TODO) */
+ struct type *or;
+
/* opt group */
long group;
@@ -68,24 +134,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 +190,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?
*/
@@ -151,7 +231,9 @@ enum ast_flag {
AST_FLAG_ANALYZED = (1 << 0),
AST_FLAG_PREANALYZIS = (1 << 1),
AST_FLAG_NOMOVES = (1 << 2),
- AST_FLAG_PUBLIC = (1 << 3),
+ AST_FLAG_PUBLIC = (1 << 3),
+ AST_REQ_FRAME = (1 << 4),
+ AST_FLAG_LOWERED = (1 << 5)
};
struct ast {
@@ -273,11 +355,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,39 +369,49 @@ 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;
}
return false;
}
-#define gen_str_type1(k, s, t, a, loc) gen_ast(k, a, NULL, NULL, NULL, \
- t, s, -1, 0., loc)
+#define gen_str_type1(k, s, t, a, loc) \
+ gen_ast(k, a, NULL, NULL, NULL, \
+ t, s, -1, 0., loc \
+ )
+
#define gen_str_type(k, s, t, loc) gen_str_type1(k, s, t, NULL, loc)
-#define gen_str3(k, s, a, b, c, loc) gen_ast(k, a, b, c, NULL, NULL, s, -1, 0., \
- loc)
+#define gen_str3(k, s, a, b, c, loc) \
+ gen_ast(k, a, b, c, NULL, NULL, s, \
+ -1, 0., loc \
+ )
+
#define gen_str2(k, s, a, b, loc) gen_str3(k, s, a, b, NULL, loc)
#define gen_str1(k, s, a, loc) gen_str2(k, s, a, NULL, loc)
-#define gen_str(k, s, loc) gen_ast(k, NULL, NULL, NULL, NULL, NULL, s, -1, 0., \
- loc)
-
+#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 +429,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 +448,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 +471,69 @@ 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 +542,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 +561,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 +600,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 +626,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)
@@ -519,6 +667,12 @@ struct ast *clone_ast_list(struct ast *l);
struct type *clone_type(struct type *type);
struct type *clone_type_list(struct type *types);
+#define clone_astc(...) cover_ptr(clone_ast, __VA_ARGS__)
+#define clone_ast_listc(...) cover_ptr(clone_ast_list, __VA_ARGS__)
+
+#define clone_typec(...) cover_ptr(clone_type, __VA_ARGS__)
+#define clone_type_listc(...) cover_ptr(clone_type_list, __VA_ARGS__)
+
void ast_dump_list(int depth, struct ast *root);
void ast_dump(int depth, struct ast *node);
void ast_append(struct ast **list, struct ast *elem);
@@ -532,8 +686,10 @@ struct ast *ast_prepend(struct ast *list, struct ast *elem);
typedef int (*ast_callback_t)(struct ast *, void *);
typedef int (*type_callback_t)(struct type *, void *);
-int ast_visit(ast_callback_t before, ast_callback_t after, struct ast *node,
- void *data);
+
+int ast_visit(ast_callback_t before, ast_callback_t after,
+ struct ast *node, void *data);
+
int ast_visit_list(ast_callback_t before, ast_callback_t after,
struct ast *node, void *data);
@@ -572,6 +728,7 @@ int equiv_node_lists(struct ast *c1, struct ast *c2);
struct ast *reverse_ast_list(struct ast *root);
struct type *reverse_type_list(struct type *root);
+struct ast *filter_empty(struct ast *root);
void fix_closures(struct ast *root);
#define foreach_node(iter, nodes) \
@@ -599,5 +756,6 @@ static inline struct type *callable_types(struct type *t)
}
void opt_group(struct ast *a, struct ast *b);
+void own_type_group(struct type *owner, struct type *sub);
#endif /* AST_H */
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/coverage.h b/include/fwd/coverage.h
new file mode 100644
index 0000000..3ec0b64
--- /dev/null
+++ b/include/fwd/coverage.h
@@ -0,0 +1,21 @@
+#ifndef FWD_COVERAGE_H
+#define FWD_COVERAGE_H
+
+#include <stdlib.h>
+
+#ifdef COVERAGE
+#include <covsrv/covsrv.h>
+# define cover_ptr(name, ...) ({covsrv_die() ? NULL : name (__VA_ARGS__);})
+#else
+# define cover_ptr(name, ...) name (__VA_ARGS__)
+#endif
+
+#define mallocc(...) cover_ptr(malloc, __VA_ARGS__)
+#define callocc(...) cover_ptr(calloc, __VA_ARGS__)
+#define reallocc(...) cover_ptr(realloc, __VA_ARGS__)
+
+#define strdupc(...) cover_ptr(strdup, __VA_ARGS__)
+#define strndupc(...) cover_ptr(strndup, __VA_ARGS__)
+
+
+#endif /* FWD_COVERAGE_H */
diff --git a/include/fwd/debug.h b/include/fwd/debug.h
index c3dce63..8927580 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..1a48130
--- /dev/null
+++ b/include/fwd/mod.h
@@ -0,0 +1,192 @@
+#ifndef FWD_MOD_H
+#define FWD_MOD_H
+
+#include <stdbool.h>
+#include <stdint.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 {
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ int8_t i8;
+ int16_t i16;
+ int32_t i32;
+ 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_I8: return &args.args[idx + 1].i8;
+ case FWD_I16: return &args.args[idx + 1].i16;
+ case FWD_I32: return &args.args[idx + 1].i32;
+ case FWD_I64: return &args.args[idx + 1].i64;
+ case FWD_U8: return &args.args[idx + 1].u8;
+ case FWD_U16: return &args.args[idx + 1].u16;
+ case FWD_U32: return &args.args[idx + 1].u32;
+ case FWD_U64: return &args.args[idx + 1].u64;
+ case FWD_PTR: return &args.args[idx + 1].p;
+ default: abort();
+ }
+
+ 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))
+
+static inline fwd_arg_t fwd_ret_u8(uint8_t x)
+{
+ return (fwd_arg_t){FWD_U8, {.u8 = x}};
+}
+
+static inline fwd_arg_t fwd_ret_u16(uint16_t x)
+{
+ return (fwd_arg_t){FWD_U16, {.u16 = x}};
+}
+
+static inline fwd_arg_t fwd_ret_u32(uint32_t x)
+{
+ return (fwd_arg_t){FWD_U32, {.u32 = x}};
+}
+
+static inline fwd_arg_t fwd_ret_u64(uint64_t x)
+{
+ return (fwd_arg_t){FWD_U64, {.u64 = x}};
+}
+
+static inline fwd_arg_t fwd_ret_i8(int8_t x)
+{
+ return (fwd_arg_t){FWD_I8, {.i8 = x}};
+}
+
+static inline fwd_arg_t fwd_ret_i16(int16_t x)
+{
+ return (fwd_arg_t){FWD_I16, {.i16 = x}};
+}
+
+static inline fwd_arg_t fwd_ret_i32(int32_t x)
+{
+ return (fwd_arg_t){FWD_I32, {.i32 = x}};
+}
+
+static inline fwd_arg_t fwd_ret_i64(int64_t x)
+{
+ return (fwd_arg_t){FWD_I64, {.i64 = x}};
+}
+
+static inline fwd_arg_t fwd_ret_ptr(void *x)
+{
+ return (fwd_arg_t){FWD_PTR, {.p = x}};
+}
+
+/* unimplemented as of yet */
+#define FWD_RET(t, a, x) \
+ (a).args[0] = _Generic((t)(0), \
+ int8_t: fwd_ret_i8, \
+ int16_t: fwd_ret_i16, \
+ int32_t: fwd_ret_i32, \
+ int64_t: fwd_ret_i64, \
+ uint8_t: fwd_ret_u8, \
+ uint16_t: fwd_ret_u16, \
+ uint32_t: fwd_ret_u32, \
+ uint64_t: fwd_ret_u64, \
+ default: fwd_ret_ptr \
+ )((x));
+
+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/include/fwd/tracker.h b/include/fwd/tracker.h
new file mode 100644
index 0000000..94c657e
--- /dev/null
+++ b/include/fwd/tracker.h
@@ -0,0 +1,7 @@
+#ifndef FWD_TRACKER_H
+#define FWD_TRACKER_H
+
+void *track_ptr(void *p);
+void free_tracked_ptrs();
+
+#endif /* FWD_TRACKER_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..208a089
--- /dev/null
+++ b/mod/io.c
@@ -0,0 +1,42 @@
+#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;
+}
+
+long fwdprint_str(fwd_extern_args_t args)
+{
+ assert(args.argc == 1);
+ const char *s = FWD_ARG_T(args, 0, const char *);
+ printf("%s", s);
+ 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));
+
+ FWD_REGISTER(state, fwdprint_str,
+ FWD_VOID, FWD_T(const char *));
+
+ return 0;
+}
diff --git a/mod/mem.c b/mod/mem.c
new file mode 100644
index 0000000..93360fb
--- /dev/null
+++ b/mod/mem.c
@@ -0,0 +1,51 @@
+#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(void *, args, 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(void *, args, 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);
+ free(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(char *, args, (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/coverage b/scripts/coverage
new file mode 100755
index 0000000..0648355
--- /dev/null
+++ b/scripts/coverage
@@ -0,0 +1,34 @@
+#!/bin/sh
+set -eu
+
+# build covsrv binary
+make -C deps/covsrv
+
+# not super fantastic but most likely good enough
+export COVSRV_SOCKET=$(mktemp -u)
+
+# server program should always be killed at the end of a test run
+cleanup() {
+ ./deps/covsrv/build/covsrv quit
+}
+
+# kill server program even if user interrupted us or something else exceptional
+# happened
+trap interrupt INT HUP TERM QUIT EXIT
+interrupt () {
+ cleanup
+ exit 1
+}
+
+# start coverage server, should create a unix socket at COVSRV_SOCKET that test
+# programs can connect to
+./deps/covsrv/build/covsrv &
+
+# run tests, pass any flags like -j to make
+make COVERAGEFLAGS="--coverage -DCOVERAGE=1" check "$@"
+
+mkdir -p coverage
+lcov --capture --directory . --out coverage/covsrv.info
+genhtml --ignore-errors range coverage/covsrv.info --out coverage
+
+exit 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..08a7a88 100644
--- a/scripts/makefile
+++ b/scripts/makefile
@@ -42,13 +42,15 @@ 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) \
- $(DEBUGFLAGS)
+ $(DEBUGFLAGS) $(COVERAGEFLAGS)
-INCLUDE_FLAGS := -I include -I deps/conts/include
+INCLUDE_FLAGS := -I include -I deps/conts/include -I deps/covsrv/include
COMPILE = $(COMPILER) \
$(COMPILE_FLAGS) $(DEPFLAGS) $(INCLUDE_FLAGS)
@@ -60,8 +62,8 @@ COMPILE_FWD = $(COMPILE) $(FWD_FLAGS)
-include deps.mk
-fwd: $(FWD_OBJS) build/gen/parser.o
- $(COMPILE_FWD) $(FWD_OBJS) build/gen/parser.o -o $@
+fwd: $(FWD_OBJS) deps/covsrv/src/client.c build/gen/parser.o
+ $(COMPILE_FWD) $(FWD_OBJS) deps/covsrv/src/client.c build/gen/parser.o -o $@
# might lint some common things twice
diff --git a/src/analyze.c b/src/analyze.c
index 0c06096..df00b2f 100644
--- a/src/analyze.c
+++ b/src/analyze.c
@@ -1,16 +1,29 @@
/* SPDX-License-Identifier: copyleft-next-0.3.1 */
/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+#include <fwd/compiler.h>
+#include <fwd/tracker.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);
-static int analyze_known_block(struct state *state, struct scope *scope, struct ast *node);
+static int analyze_known_block(struct state *state, struct scope *scope,
+ struct ast *node);
static int analyze_list(struct state *state, struct scope *scope,
struct ast *nodes);
@@ -53,16 +66,25 @@ static int analyze_proc(struct state *state, struct scope *scope,
group++; /* start of new group */
/* otherwise same or ending group, don't increment */
+ struct type *ct = clone_type(param->t);
+ if (!ct)
+ return -1;
param->t->group = group;
- type_append(&proc_type->t0, clone_type(param->t));
+ type_append(&proc_type->t0, ct);
}
node->t = proc_type;
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 +111,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,24 +153,17 @@ 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;
}
return 0;
}
-static int analyze_known_block(struct state *state, struct scope *scope, struct ast *node)
+static int analyze_known_block(struct state *state, struct scope *scope,
+ struct ast *node)
{
assert(node && node->k == AST_BLOCK);
@@ -144,28 +172,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 +201,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 +231,48 @@ 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;
}
+ /* use same loop to deduce ownership rules */
+ for (; param && t; param = param->n, t = t->n) {
+ /* if this param owns the next, reflect that in our variables */
+ if (t->or)
+ param->or = param->n;
+
+ if (var_type(param))
+ continue;
+
+ var_type(param) = t;
+ }
+
return 0;
}
@@ -248,27 +290,35 @@ 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);
if (expected != got) {
semantic_error(scope, node, "expected %d params, got %d",
- expected, got);
+ expected, got
+ );
return -1;
}
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 +327,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,
@@ -311,19 +361,22 @@ static int analyze_deref(struct state *state, struct scope *scope,
if (expr->t->k == TYPE_PTR) {
semantic_error(node->scope, node,
- "deref of raw ptr not allowed");
+ "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;
}
if (expr->t->k != TYPE_REF) {
semantic_error(node->scope, node,
- "deref of something not a reference");
+ "deref of something not a reference"
+ );
return -1;
}
- node->t = tptr_base(expr->t);
+ node->t = tref_base(expr->t);
return 0;
}
@@ -333,7 +386,8 @@ static int analyze_id(struct state *state, struct scope *scope,
struct ast *found = file_scope_find_symbol(scope, id_str(node));
if (!found) {
semantic_error(scope, node, "no symbol named \"%s\"",
- id_str(node));
+ id_str(node)
+ );
return -1;
}
@@ -362,7 +416,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)))
@@ -383,7 +436,11 @@ static int analyze_closure(struct state *state, struct scope *scope,
}
foreach_node(binding, closure_bindings(node)) {
- type_append(&callable->t0, clone_type(binding->t));
+ struct type *ct = clone_type(binding->t);
+ if (!ct)
+ return -1;
+
+ type_append(&callable->t0, ct);
}
node->t = callable;
@@ -396,17 +453,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;
}
@@ -420,16 +470,9 @@ static int analyze_str(struct state *state, struct scope *scope,
(void)scope;
/** @todo do this properly, very hacky, bad bad bad */
- char *i = strdup("char");
- if (!i) {
- internal_error("failed allocating constant char type string");
- return -1;
- }
-
- struct type *ch = tgen_id(i, node->loc);
+ struct type *ch = tgen_type(TYPE_U8, NULL, NULL, node->loc);
if (!ch) {
internal_error("failed allocating constant char type");
- free(i);
return -1;
}
@@ -443,6 +486,211 @@ 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(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 +712,175 @@ 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)
+{
+ (void)state;
+ (void)scope;
+
+ node->t = tgen_nil(node->loc);
+ return 0;
+}
+
+static bool castable_type(struct type *type)
{
- struct scope *branch_scope = create_scope();
- if (!branch_scope) {
- internal_error("failed allocating err branch scope");
+ 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 (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 *ct = clone_type(base);
+ if (!ct)
+ return -1;
+
+ struct type *ref = tgen_ref(ct, 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 (is_trivially_copyable(found->t))
- semantic_warn(scope, node, "trivially copyable type is never owned");
+ if (analyze(state, scope, nil_check_rest(node)))
+ return -1;
node->t = tgen_void(node->loc);
- return analyze(state, scope, own_body(node));
+ 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 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 +920,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 +929,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 +997,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,32 +1075,238 @@ 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_I8: return tgen_type(TYPE_I8, NULL, NULL, NULL_LOC());
+ case FWD_I16: return tgen_type(TYPE_I16, NULL, NULL, NULL_LOC());
+ case FWD_I32: return tgen_type(TYPE_I32, NULL, NULL, NULL_LOC());
+ case FWD_I64: return tgen_type(TYPE_I64, NULL, NULL, NULL_LOC());
+ case FWD_U8: return tgen_type(TYPE_U8, NULL, NULL, NULL_LOC());
+ case FWD_U16: return tgen_type(TYPE_U16, NULL, NULL, NULL_LOC());
+ case FWD_U32: return tgen_type(TYPE_U32, NULL, NULL, NULL_LOC());
+ case FWD_U64: return tgen_type(TYPE_U64, NULL, NULL, NULL_LOC());
+ case FWD_PTR: 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 = callocc(8, sizeof(char));
+ if (!name) {
+ internal_error("failed allocating ext var name");
+ return -1;
+ }
+
+ track_ptr(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) {
+ struct ast *new = gen_var("ret",
+ tgen_closure(
+ fwd_type_kind(rtype),
+ NULL_LOC()
+ ),
+ NULL_LOC()
+ );
+ if (vars)
+ new->n = vars;
+
+ vars = new;
+ }
+
+ vars = reverse_ast_list(vars);
+
+ char *cname = strdupc(name);
+ if (!cname) {
+ internal_error("failed allocating ext name copy");
+ return -1;
+ }
+
+ track_ptr(cname);
+
+ struct ast *def = gen_proc(cname, 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) {
switch (node->k) {
case AST_PROC_DEF:
- if (scope_add_symbol(scope, node))
- return -1;
- break;
+ if (scope_add_symbol(scope, node))
+ return -1;
+ break;
case AST_STRUCT_DEF:
- if (scope_add_type(scope, node))
- return -1;
- break;
+ if (scope_add_type(scope, node))
+ return -1;
+ break;
case AST_STRUCT_CONT:
- if (scope_add_chain(scope, node))
- return -1;
- break;
+ abort();
+ break;
case AST_TRAIT_DEF:
- if (scope_add_trait(scope, node))
- return -1;
- break;
+ if (scope_add_trait(scope, node))
+ 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();
+ abort();
}
}
diff --git a/src/ast.c b/src/ast.c
index 427c4c4..bb8fb2b 100644
--- a/src/ast.c
+++ b/src/ast.c
@@ -16,6 +16,7 @@
#include <fwd/ast.h>
#include <fwd/scope.h>
+#include <fwd/tracker.h>
#define VEC_NAME ast_vec
#define VEC_TYPE struct ast *
@@ -25,77 +26,29 @@
#define VEC_TYPE struct type *
#include <conts/vec.h>
-static struct ast_vec nodes;
-static struct type_vec types;
-
-static void destroy_ast_node(struct ast *n)
-{
- if (!n)
- return;
-
- if (n->s)
- free(n->s);
-
- free(n);
-}
-
-static void destroy_type(struct type *n)
-{
- if (!n)
- return;
-
- if (n->id)
- free(n->id);
-
- free(n);
-}
-
-static void destroy_ast_nodes()
-{
- foreach(ast_vec, n, &nodes) {
- destroy_ast_node(*n);
- }
-
- ast_vec_destroy(&nodes);
-}
-
-static void destroy_types()
-{
- foreach(type_vec, t, &types) {
- destroy_type(*t);
- }
-
- type_vec_destroy(&types);
-}
-
-void destroy_allocs()
-{
- destroy_ast_nodes();
- destroy_types();
-}
-
static struct ast *create_empty_ast()
{
- if (ast_vec_uninit(&nodes))
- nodes = ast_vec_create(1);
-
- struct ast *n = calloc(1, sizeof(struct ast));
- if (!n)
+ struct ast *n = callocc(1, sizeof(struct ast));
+ if (!n) {
+ internal_error("failed allocating empty ast node");
return NULL;
+ }
/* just to be safe */
n->k = AST_EMPTY;
- ast_vec_append(&nodes, n);
+ track_ptr(n);
return n;
}
static struct type *create_empty_type()
{
- if (type_vec_uninit(&types))
- types = type_vec_create(1);
+ struct type *n = callocc(1, sizeof(struct type));
+ if (!n) {
+ internal_error("failed allocating empty type node");
+ return NULL;
+ }
- struct type *n = calloc(1, sizeof(struct type));
- type_vec_append(&types, n);
+ track_ptr(n);
return n;
}
@@ -123,6 +76,8 @@ struct ast *gen_ast(enum ast_kind kind,
n->s = s;
n->v = v;
n->d = d;
+ n->or = NULL;
+ n->ol = NULL;
n->loc = loc;
return n;
}
@@ -139,6 +94,7 @@ struct type *tgen_type(enum type_kind kind,
n->k = kind;
n->t0 = t0;
n->id = id;
+ n->or = NULL;
n->loc = loc;
return n;
}
@@ -214,15 +170,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 +200,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,26 +270,43 @@ 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;
new->v = n->v;
new->d = n->d;
- if (n->s)
- new->s = strdup(n->s);
+ /* names are handled by pointer tracker, so we can just copy a reference
+ * to the string directly */
+ new->s = n->s;
- if (n->a0)
- new->a0 = clone_ast_list(n->a0);
+ if (n->a0 && !(new->a0 = clone_ast_listc(n->a0))) {
+ internal_error("failed cloning ast a0");
+ return NULL;
+ }
- if (n->a1)
- new->a1 = clone_ast_list(n->a1);
+ if (n->a1 && !(new->a1 = clone_ast_listc(n->a1))) {
+ internal_error("failed cloning ast a1");
+ return NULL;
+ }
- if (n->a2)
- new->a2 = clone_ast_list(n->a2);
+ if (n->a2 && !(new->a2 = clone_ast_listc(n->a2))) {
+ internal_error("failed cloning ast a2");
+ return NULL;
+ }
- if (n->a3)
- new->a3 = clone_ast_list(n->a3);
+ if (n->a3 && !(new->a3 = clone_ast_listc(n->a3))) {
+ internal_error("failed cloning ast a3");
+ return NULL;
+ }
+
+ if (n->t2 && !(new->t2 = clone_type_listc(n->t2))) {
+ internal_error("failed cloning ast t2");
+ return NULL;
+ }
/** @todo rebuild opt group? */
@@ -329,7 +317,9 @@ struct ast *clone_ast_list(struct ast *root)
{
struct ast *n = root, *new_root = NULL, *prev = NULL;
while (n) {
- struct ast *new = clone_ast(n);
+ struct ast *new = clone_astc(n);
+ if (!new)
+ return NULL;
if (prev) prev->n = new;
else new_root = new;
@@ -351,13 +341,17 @@ struct type *clone_type(struct type *type)
return NULL;
new->k = type->k;
- if (type->id && !(new->id = strdup(type->id)))
- return NULL;
+ /* names are tracked by the tracker, so no need to duplicate */
+ new->id = type->id;
- if (type->t0 && !(new->t0 = clone_type_list(type->t0)))
+ if (type->t0 && !(new->t0 = clone_type_listc(type->t0))) {
+ internal_error("failed cloning type t0");
return NULL;
+ }
+ new->or = type->or;
new->group = type->group;
+ new->loc = type->loc;
return new;
}
@@ -365,7 +359,9 @@ struct type *clone_type_list(struct type *root)
{
struct type *n = root, *new_root = NULL, *prev = NULL;
while (n) {
- struct type *new = clone_type(n);
+ struct type *new = clone_typec(n);
+ if (!new)
+ return NULL;
if (prev) prev->n = new;
else new_root = new;
@@ -545,13 +541,40 @@ struct type *reverse_type_list(struct type *root)
return new_root;
}
+struct ast *filter_empty(struct ast *root)
+{
+ struct ast *ret = root;
+ while (root) {
+ if (root->n && root->n->k == AST_EMPTY)
+ root->n = root->n->n;
+
+ root = root->n;
+ }
+
+ return ret;
+}
+
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 +597,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 +663,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 +693,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
@@ -697,3 +726,11 @@ void opt_group(struct ast *a, struct ast *b)
a->or = b;
b->ol = a;
}
+
+void own_type_group(struct type *owner, struct type *sub)
+{
+ /* sub does not get a reference to owner so if owner is moved, only the
+ * sub is marked as used. sub is still allowed to be passed around
+ * freely */
+ owner->or = sub;
+}
diff --git a/src/compiler.c b/src/compiler.c
index 9f564d8..f72ab1c 100644
--- a/src/compiler.c
+++ b/src/compiler.c
@@ -16,11 +16,14 @@
#include <errno.h>
#include <fwd/compiler.h>
+#include <fwd/coverage.h>
#include <fwd/analyze.h>
+#include <fwd/tracker.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>
/**
@@ -45,13 +48,16 @@ static char *read_file(const char *file, FILE *f)
fseek(f, 0, SEEK_SET);
- char *buf = malloc((size_t)(s + 1));
- if (!buf)
+ char *buf = mallocc((size_t)(s + 1));
+ if (!buf) {
+ internal_error("failed allocating fread buffer");
return NULL;
+ }
fread(buf, (size_t)(s + 1), 1, f);
/* remember terminating null */
buf[s] = 0;
+ track_ptr(buf);
return buf;
}
@@ -65,11 +71,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;
}
@@ -80,36 +86,30 @@ static int process(struct scope **parent, const char *file)
return -1;
struct parser *p = create_parser();
- if (!p) {
- free((void *)buf);
+ if (!p)
return -1;
- }
parse(p, file, buf);
+
struct ast *tree = p->tree;
bool failed = p->failed;
destroy_parser(p);
- if (failed) {
- free((void*)buf);
+ if (failed)
return -1;
- }
ast_dump_list(0, tree);
- struct scope *scope = create_scope();
- if (!scope) {
- free((void *)buf);
+ char *fname = strdupc(file);
+ if (!fname) {
+ internal_error("failed allocating fname");
return -1;
}
- scope->fctx.fbuf = buf;
- scope->fctx.fname = strdup(file);
- if (*parent)
- scope_add_scope(*parent, scope);
- else
- *parent = scope;
+ track_ptr(fname);
+ scope->fctx.fbuf = buf;
+ scope->fctx.fname = fname;
if (analyze_root(scope, tree))
return -1;
@@ -119,24 +119,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);
+ }
+
+ 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);
+ return NULL;
+ }
+
+ if (!(dir = fwd_dirname(file))) {
+ error("couldn't get dirname of %s", file);
+ return NULL;
+ }
+
+ if (!(cwd = fwd_cwdname())) {
+ error("couldn't get current working dir");
+ return NULL;
+ }
+
+ if (*dir != 0 && chdir(dir)) {
+ error("couldn't change to directory %s: %s", dir,
+ strerror(errno)
+ );
+ return NULL;
+ }
+
+ if (!(real = realpath(base, NULL))) {
+ error("no such file: %s", file);
+ return NULL;
+ }
+
+ /* const cast, ech */
+ track_ptr((void *)real);
+
+ struct scope **exists = scopes_find(&scopes, real);
+ if (exists) {
+ scope = *exists;
+ return NULL;
+ }
+
+ scope = create_scope();
+ scopes_insert(&scopes, real, scope);
+
+ if (process(scope, base)) {
+ scope = NULL;
+ return NULL;
+ }
+
+ if (chdir(cwd)) {
+ error("couldn't change back to directory %s: %s", cwd,
+ strerror(errno)
+ );
+
+ return NULL;
+ }
+
+ 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);
- destroy_allocs();
- return 0;
+out:
+ destroy_scopes();
+ free_tracked_ptrs();
+ return ret;
}
diff --git a/src/debug.c b/src/debug.c
index b13a459..273fbb0 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -14,6 +14,7 @@
#include <string.h>
#include <stdarg.h>
+#include <fwd/tracker.h>
#include <fwd/debug.h>
#include <fwd/scope.h>
@@ -74,7 +75,8 @@ static void _issue(struct src_issue issue, const char *fmt, va_list args)
{
/* get start and end of current line in buffer */
const char *line_start = find_lineno(issue.fctx.fbuf,
- (size_t)issue.loc.first_line);
+ (size_t)issue.loc.first_line
+ );
const char *line_end = strchr(line_start, '\n');
if (!line_end)
line_end = strchr(line_start, 0);
@@ -84,7 +86,8 @@ static void _issue(struct src_issue issue, const char *fmt, va_list args)
fprintf(stderr, "%s:%i:%i: %s: ", issue.fctx.fname,
issue.loc.first_line,
issue.loc.first_col,
- issue_level_str(issue.level));
+ issue_level_str(issue.level)
+ );
vfprintf(stderr, fmt, args);
fputc('\n', stderr);
@@ -134,14 +137,25 @@ 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);
}
void move_error(struct ast *new_use, struct ast *prev_use)
@@ -177,14 +191,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,19 +266,20 @@ 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");
+ if (!t) {
+ char *s = strdupc("NULL");
+ if (!s) {
+ internal_error("failed allocating null string");
+ return NULL;
+ }
+
+ return s;
+ }
char *buf = NULL; size_t size = 0;
FILE *memstream = open_memstream(&buf, &size);
@@ -238,6 +289,8 @@ const char *type_str(struct type *t)
_type_str(memstream, t);
fclose(memstream);
+
+ track_ptr(buf);
return buf;
}
diff --git a/src/lexer.l b/src/lexer.l
index e6a3f22..1f4c520 100644
--- a/src/lexer.l
+++ b/src/lexer.l
@@ -4,6 +4,7 @@
%option reentrant noyywrap nounput noinput nodefault
%{
#define FROM_LEXER
+#include <fwd/tracker.h>
#include <fwd/parser.h>
#include <fwd/debug.h>
@@ -38,8 +39,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 +64,6 @@ STRING \"(\\.|[^"\\])*\"
\n {}
}
-"::" {return SCOPE;}
"(" {return LPAREN;}
")" {return RPAREN;}
"{" {return LBRACE;}
@@ -122,15 +121,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,10 +138,16 @@ 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);
+ yylval->str = strdupc(yytext);
+ if (!yylval->str) {
+ internal_error("failed allocating lexed string");
+ return ERROR;
+ }
+
+ track_ptr(yylval->str);
return STRING;
}
@@ -153,20 +157,16 @@ STRING \"(\\.|[^"\\])*\"
}
{ID} {
- yylval->str = strdup(yytext);
- return ID;
-}
-
-{APPLY} {
- /* strip trailing '!' */
- char *s = yytext + strlen(yytext);
- s[-1] = '\0';
+ yylval->str = strdupc(yytext);
+ if (!yylval->str) {
+ internal_error("failed allocating lexed ID");
+ return ERROR;
+ }
- yylval->str = strdup(yytext);
- return APPLY;
+ track_ptr(yylval->str);
+ return ID;
}
-
[[:space:]]+ {/* skip whitespace */}
. {
diff --git a/src/lower.c b/src/lower.c
index 89d9aca..f6d6154 100644
--- a/src/lower.c
+++ b/src/lower.c
@@ -5,18 +5,119 @@
#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)
+{
+ (void)state;
+ static size_t q = 0;
+ return q++;
+}
+
static void increase_indent(struct state *state)
{
state->indent++;
@@ -27,627 +128,1170 @@ static void decrease_indent(struct state *state)
state->indent--;
}
-static void indent(struct state *state)
+static void indent_codeline(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 void write_codeline(struct state *state, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
-static int lower_expr(struct state *state, struct ast *expr);
-static int lower_closure(struct state *state, struct ast *closure);
+ indent_codeline(state);
+ vfprintf(state->code, fmt, args);
-static int lower_block(struct state *state, struct ast *block, bool ret);
-static int lower_statement(struct state *state, struct ast *stmt, bool ret);
+ va_end(args);
+}
-static int lower_type(struct type *type);
-static int lower_types(struct type *types);
+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_binop(struct state *state, struct ast *binop)
+static void add_proc(struct state *state, struct ast *proc)
{
- printf("(");
- if (lower_expr(state, binop_left(binop)))
- return -1;
+ assert(proc->k == AST_PROC_DEF);
+ struct ast **p = proc_set_insert(state->procs, proc, proc);
+ assert(p && *p == proc);
+}
- 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_decl(struct state *state, char *decl)
+{
+ string_vec_append(state->decls, decl);
+}
- if (lower_expr(state, binop_right(binop)))
- return -1;
+static void add_defn(struct state *state, char *defn)
+{
+ string_vec_append(state->defns, defn);
+}
- printf(")");
- return 0;
+static bool type_lowered(struct ast *type)
+{
+ assert(type->k == AST_STRUCT_DEF);
+ return ast_flags(type, AST_FLAG_LOWERED);
}
-static int lower_comparison(struct state *state, struct ast *comp)
+static void add_type(struct state *state, char *type)
{
- printf("(");
- if (lower_expr(state, comparison_left(comp)))
- return -1;
+ string_vec_append(state->types, type);
+}
- 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;
+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)
+{
+ assert(type);
+ switch (type->k) {
+ case TYPE_I8:
+ fprintf(f, "int8_t");
+ break;
+
+ case TYPE_U8:
+ fprintf(f, "uint8_t");
+ break;
+
+ case TYPE_I16:
+ fprintf(f, "int16_t");
+ break;
+
+ case TYPE_U16:
+ fprintf(f, "uint16_t");
+ break;
+
+ case TYPE_I32:
+ fprintf(f, "int32_t");
+ break;
+
+ case TYPE_U32:
+ fprintf(f, "uint32_t");
+ break;
+
+ 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;
}
- if (lower_expr(state, comparison_right(comp)))
- return -1;
+ case TYPE_PTR: {
+ char *rest = lower_type_str(state, scope, tptr_base(type));
+ fprintf(f, "%s*", rest);
+ free(rest);
+ break;
+ }
- printf(")");
- return 0;
-}
+ case TYPE_REF: {
+ char *rest = lower_type_str(state, scope, tref_base(type));
+ fprintf(f, "%s*", rest);
+ free(rest);
+ break;
+ }
-static int lower_exprs(struct state *state, struct ast *exprs)
-{
- if (!exprs)
- return 0;
+ case TYPE_BOOL: {
+ fprintf(f, "bool");
+ break;
+ }
- if (lower_expr(state, exprs))
- return -1;
+ case TYPE_NIL: {
+ fprintf(f, "void*");
+ break;
+ }
- foreach_node(expr, exprs->n) {
- printf(", ");
- if (lower_expr(state, expr))
- return -1;
+ 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;
-}
+ write_codeline(state, "fwd_call_t %s = ctx->%s.call;\n", q, args);
+ write_codeline(state, "struct %s *%s_ctx = ctx->%s.args;\n", q, q,
+ args
+ );
-static int lower_type_ref(struct type *type)
-{
- if (lower_type(tref_base(type)))
- return -1;
+ int ret = 0;
+ size_t idx = 0;
+ bool returning = false;
+ foreach_node(a, call_args(call)) {
+ returning |= a->k == AST_CLOSURE;
- printf("&");
- return 0;
-}
+ char *type = lower_type_str(state, a->scope, a->t);
+ fprintf(ctx, " %s a%zu;\n", type, idx);
+ free(type);
-static int lower_type_ptr(struct type *type)
-{
- /* would I need parentheses in some cases? */
- if (lower_type(tptr_base(type)))
- return -1;
+ write_codeline(state, "%s_ctx->a%zu = ", q, idx);
- printf("*");
- return 0;
-}
+ 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)) {
+ write_codeline(state, "fwd_stack_free(&stack, ctx);\n");
+ }
+
+ if (last) {
+ write_codeline(state,
+ "FWD_MUSTTAIL return %s(stack, %s_ctx);\n",
+ q, q
+ );
+ }
+ else {
+ write_codeline(state, "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
+ );
- if (lower_type(types))
- return -1;
+ char *decl = buildstr("%s;\n", proto);
+ char *start_of = buildstr("%s_start", name);
+ add_decl(state, decl);
- foreach_type(type, types->n) {
- printf(", ");
- if (lower_type(type))
- return -1;
- }
+ char *code_buf = NULL;
+ size_t code_size = 0;
- return 0;
+ struct state new_state = create_state(state);
+ new_state.code = open_memstream(&code_buf, &code_size);
+ new_state.indent = 0;
+ assert(new_state.code);
+
+ write_codeline(&new_state, "%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);
+ write_codeline(&new_state,
+ "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);
+
+ if (lower_expr(state, as_expr(expr)))
+ return -1;
- foreach_node(move, moves->n) {
- printf(", ");
- if (lower_move(state, move))
+ fprintf(state->code, ")");
+ break;
+ }
+
+ case AST_SIZEOF: {
+ char *type = lower_type_str(state, expr->scope,
+ sizeof_type(expr)
+ );
+ fprintf(state->code, "sizeof(%s)", type);
+ free(type);
+ break;
+ }
+
+ case AST_PUT: {
+ fprintf(state->code, "*(");
+ if (lower_expr(state, put_dst(expr)))
return -1;
+
+ fprintf(state->code, ")");
+ break;
+ }
+
+ case AST_DEREF: {
+ fprintf(state->code, "*(");
+ if (lower_expr(state, deref_base(expr)))
+ return -1;
+
+ fprintf(state->code, ")");
+ break;
+ }
+
+ case AST_CONST_STR: {
+ fprintf(state->code, "\"%s\"", str_val(expr));
+ 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;
+ write_codeline(state, "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);
+ write_codeline(&ctx_state, "%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 */
+ write_codeline(&ctx_state, "fwd_stack_free(&stack, ctx);\n");
+ }
+
+ char *target = mangle2(def);
+ if (last) {
+ write_codeline(&ctx_state,
+ "FWD_MUSTTAIL return %s(stack, %s);\n",
+ target, q
+ );
+ }
+ else {
+ write_codeline(&ctx_state, "stack = %s(stack, %s);\n",
+ target, q
+ );
+ }
+
+ fprintf(ctx, "};\n\n");
+ fclose(ctx);
+ assert(ctx_buf);
+
+ add_type(state, ctx_buf);
+ free(target);
+ free(q);
+ return ret;
}
-/** @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)
+static int lower_if(struct state *state, struct ast *stmt, bool last)
{
- struct ast *err = call_err(call);
- /** @todo better default error name? */
- const char *err_str = err ? err_branch_id(err) : "_fwd_err";
+ write_codeline(state, "if (");
+ if (lower_expr(state, if_cond(stmt)))
+ return -1;
- if (lower_mark_moved(state, call_args(call)))
+ fprintf(state->code, ")\n");
+ if (lower_block(state, if_body(stmt), last))
return -1;
- bool direct_ret = ret && !err;
- if (direct_ret)
- printf("return ");
- else
- printf("if (auto %s = ", err_str);
+ if (!if_else(stmt))
+ return 0;
+
+ write_codeline(state, "else\n");
+ return lower_block(state, if_else(stmt), last);
+}
- if (lower_expr(state, call_expr(call)))
+static int lower_nil_check(struct state *state, struct ast *stmt, bool last)
+{
+ struct ast *var = nil_check_ref(stmt);
+ char *type = lower_type_str(state, var->scope, var->t);
+ char *name = mangle2(var);
+
+ fprintf(state->ctx, " %s %s;\n", type, name);
+
+ write_codeline(state, "ctx->%s = ", name);
+ if (lower_expr(state, nil_check_expr(stmt)))
return -1;
- printf("(");
+ fprintf(state->code, ";\n");
- if (lower_moves(state, call_args(call)))
+ write_codeline(state, "if (!ctx->%s)", name);
+ if (lower_block(state, nil_check_body(stmt), last))
return -1;
- if (direct_ret) {
- printf(");\n");
- return 0;
- }
+ write_codeline(state, "else\n");
- printf("))");
- if (err) {
- if (lower_err_branch(state, err))
- return -1;
+ free(type);
+ free(name);
- if (ret) {
- printf("\n");
- indent(state);
- printf("return nullptr;\n");
- }
+ return lower_block(state, nil_check_rest(stmt), last);
+}
- return 0;
- }
+static int lower_explode(struct state *state, struct ast *stmt, bool last)
+{
+ /* not significant to us */
+ (void)last;
- printf("\n");
+ int u = uniq(state);
+ struct ast *expr = explode_expr(stmt);
- increase_indent(state);
- indent(state);
- decrease_indent(state);
+ char *type = lower_type_str(state, expr->scope, expr->t);
+
+ write_codeline(state, "%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");
+ write_codeline(state, "ctx->%s = explode_%d.%s;\n",
+ name, u, deconstruction_id(node)
+ );
+
+ fprintf(state->ctx, " %s %s;\n", type, name);
+
+ 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;
+ struct ast *var = let_var(stmt);
+ char *type = lower_type_str(state, var->scope, var->t);
+ char *name = mangle2(var);
- printf(" = ");
+ fprintf(state->ctx, " %s %s;\n", type, name);
- if (lower_expr(state, let_expr(let)))
+ write_codeline(state, "ctx->%s = ", name);
+
+ free(type);
+ free(name);
+
+ if (lower_expr(state, let_expr(stmt)))
return -1;
- printf(";\n");
- if (ret) {
- indent(state);
- printf("return nullptr;\n");
+ fprintf(state->code, ";\n");
+
+ if (last) {
+ write_codeline(state, "return stack;\n");
}
return 0;
}
-static int lower_if(struct state *state, struct ast *stmt, bool ret)
+static int lower_write(struct state *state, struct ast *stmt)
{
- printf("if (");
- if (lower_expr(state, if_cond(stmt)))
+ indent_codeline(state);
+ if (lower_expr(state, write_dst(stmt)))
return -1;
- printf(") ");
-
- if (lower_block(state, if_body(stmt), ret))
+ fprintf(state->code, " = ");
+ if (lower_expr(state, write_src(stmt)))
return -1;
- if (!if_else(stmt)) {
- printf("\n");
- return 0;
- }
+ fprintf(state->code, ";\n");
+ return 0;
+}
- printf(" else ");
- if (lower_block(state, if_else(stmt), ret))
- return -1;
+static int lower_stmt(struct state *state, struct ast *stmt, bool last)
+{
+ switch (stmt->k) {
+ case AST_IF: return lower_if(state, stmt, last);
+ case AST_LET: return lower_let(state, stmt, last);
+ case AST_CALL: return lower_call(state, stmt, last);
+ case AST_WRITE: return lower_write(state, stmt);
+ case AST_EXPLODE: return lower_explode(state, stmt, last);
+ case AST_NIL_CHECK: return lower_nil_check(state, stmt, last);
+ case AST_FORGET:
+ case AST_EMPTY:
+ if (last)
+ write_codeline(state, "return stack;\n");
- printf("\n");
+ break;
+ default:
+ internal_error("unhandled statement kind %s", ast_str(stmt->k));
+ abort();
+ break;
+ }
return 0;
}
-static int lower_error(struct ast *err)
+static int lower_stmt_list(struct state *state, struct ast *stmt_list,
+ bool last)
{
- assert(error_str(err) || error_id(err));
- if (error_str(err)) {
- printf("return %s;\n", error_str(err));
- return 0;
+ foreach_node(stmt, stmt_list) {
+ bool req_ret = last && !stmt->n;
+ if (lower_stmt(state, stmt, req_ret))
+ 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 int lower_block(struct state *state, struct ast *block, bool last)
{
- /** @todo name mangling */
- printf("if (!%s_owned) ", own_id(stmt));
- if (lower_block(state, own_body(stmt), ret))
- return -1;
+ write_codeline(state, "{\n");
- printf("\n");
+ increase_indent(state);
+ int ret = lower_stmt_list(state, block_body(block), last);
+ decrease_indent(state);
+
+ write_codeline(state, "}\n");
+ return ret;
+}
+
+static int lower_param(struct state *state, struct ast *param)
+{
+ char *type = lower_type_str(state, param->scope, param->t);
+ char *name = mangle2(param);
+
+ fprintf(state->ctx, " %s %s;\n", type, name);
+
+ free(type);
+ free(name);
return 0;
}
-static int lower_statement(struct state *state, struct ast *stmt, bool ret)
+static int lower_params(struct state *state, struct ast *params)
{
- 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");
+ foreach_node(p, params) {
+ if (lower_param(state, p))
+ return -1;
+ }
+
+ return 0;
+}
- return 0;
+static const char *fwd_typeparam(struct type *t)
+{
+ switch (t->k) {
+ case TYPE_I8: return "i8";
+ case TYPE_I16: return "i16";
+ case TYPE_I32: return "i32";
+ case TYPE_I64: return "i64";
+ case TYPE_U8: return "u8";
+ case TYPE_U16: return "u16";
+ case TYPE_U32: return "u32";
+ case TYPE_U64: return "u64";
+ case TYPE_PTR: return "p";
+ case TYPE_NIL: return "p";
+ case TYPE_FUNC_PTR: return "p";
default:
- internal_error("missing statement lowering");
- return -1;
+ abort();
}
- return 0;
+ return NULL;
}
-static int lower_block_vars(struct state *state, struct ast *block)
+static const char *fwd_ctypestr(struct type *t)
{
- struct scope *scope = block->scope;
+ switch (t->k) {
+ case TYPE_I8: return "FWD_I8";
+ case TYPE_I16: return "FWD_I16";
+ case TYPE_I32: return "FWD_I32";
+ case TYPE_I64: return "FWD_I64";
+ case TYPE_U8: return "FWD_U8";
+ case TYPE_U16: return "FWD_U16";
+ case TYPE_U32: return "FWD_U32";
+ case TYPE_U64: return "FWD_U64";
+ case TYPE_PTR: return "FWD_PTR";
+ case TYPE_NIL: return "FWD_PTR"; /* void ptr */
+ case TYPE_FUNC_PTR: return "FWD_PTR";
+ default:
+ abort();
+ }
- bool populated = false;
- foreach(visible, n, &scope->symbols) {
- struct ast *def = n->data;
- if (def->k != AST_VAR_DEF)
- continue;
+ return NULL;
+}
- if (is_trivially_copyable(def->t))
- continue;
+static int lower_extern_closure_call(struct state *state, struct scope *scope,
+ struct type *rtype, size_t idx)
+{
+ char *q = buildstr("%s_call%zu", state->prefix, uniq(state));
- if (is_callable(def->t))
- continue;
+ char *ctx_buf = NULL; size_t ctx_size = 0;
+ FILE *ctx = open_memstream(&ctx_buf, &ctx_size);
- if (!populated) {
- indent(state);
- printf("[[maybe_unused]] bool %s_owned = true",
- var_id(def));
+ char *type = lower_type_str(state, scope, rtype);
+ fprintf(ctx, "struct %s {\n fwd_start_t start;\n %s a0;\n};\n", q,
+ type
+ );
+ free(type);
- populated = true;
- continue;
- }
+ fclose(ctx);
+ assert(ctx_buf);
- printf(", %s_owned = true", var_id(def));
- }
+ add_type(state, ctx_buf);
- if (populated)
- printf(";\n\n");
+ write_codeline(state, "fwd_call_t call = ctx->a%zu.call;\n", idx);
+ write_codeline(state, "struct %s *call_ctx = ctx->a%zu.args;\n", q,
+ idx
+ );
+ write_codeline(state, "call_ctx->a0 = extern_args[0].%s;\n",
+ fwd_typeparam(rtype)
+ );
+ write_codeline(state, "fwd_stack_free(&stack, extern_args);\n");
+ write_codeline(state, "FWD_MUSTTAIL return call(stack, call_ctx);\n");
+ free(q);
return 0;
}
-static int lower_block(struct state *state, struct ast *block, bool ret)
+static int lower_extern_proc(struct state *state, struct ast *proc)
{
- printf("{\n");
- increase_indent(state);
+ 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
+ );
- if (lower_block_vars(state, block))
- return -1;
+ char *decl = buildstr("%s;\n", proto);
+ add_decl(state, decl);
- foreach_node(stmt, block_body(block)) {
- indent(state);
+ char *code_buf = NULL, *ctx_buf = NULL;
+ size_t code_size = 0, ctx_size = 0;
- bool returning = block_error(block) ? false : ret && !stmt->n;
- if (lower_statement(state, stmt, returning))
- return -1;
+ 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);
+
+ write_codeline(&new_state, "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 */
+ write_codeline(&new_state, "struct %s_ctx *ctx = args;\n", name);
+ write_codeline(&new_state,
+ "struct fwd_arg *extern_args = fwd_stack_alloc(&stack);\n"
+ );
+
+ size_t idx = 0;
+ struct type *rtype = NULL;
+ foreach_node(p, proc_params(proc)) {
+ /* lower arg */
+ 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);
+
+ /* don't lower last parameter as an extern arg if it's a
+ * closure, since that's our return type */
+ if (!p->n && is_closure_type(p->t->k)) {
+ rtype = p->t;
+ break;
+ }
+
+ /* leave place for return value */
+ write_codeline(&new_state, "extern_args[%zu] = (fwd_arg_t){%s, "
+ "{.%s = ctx->a%zu}};\n",
+ idx + 1, fwd_ctypestr(p->t), fwd_typeparam(p->t),
+ idx
+ );
+
+ idx++;
}
- if (block_error(block)) {
- indent(state);
- if (lower_error(block_error(block)))
+ write_codeline(&new_state,
+ "%s((fwd_extern_args_t){.argc = %zu, .args = extern_args});\n",
+ proc_id(proc), idx
+ );
+
+
+ if (rtype) {
+ struct type *ctype = rtype->k == TYPE_PURE_CLOSURE
+ ? tpure_closure_args(rtype)
+ : tclosure_args(rtype)
+ ;
+
+ write_codeline(&new_state, "assert(extern_args[0].t == %s);\n",
+ fwd_ctypestr(ctype)
+ );
+
+ if (lower_extern_closure_call(&new_state, proc->scope,
+ ctype, idx
+ ))
return -1;
- }
- else if (!block_body(block)) {
- indent(state);
- printf("return nullptr;\n");
+
+ } else {
+ /* void func */
+ write_codeline(&new_state,
+ "fwd_stack_free(&stack, extern_args);\n"
+ );
+ write_codeline(&new_state, "return stack;\n");
}
- decrease_indent(state);
- indent(state);
- printf("}");
+ fprintf(new_state.code, "}\n\n");
+ fprintf(new_state.ctx, "};\n\n");
+
+ fclose(new_state.code);
+ assert(code_buf);
+ add_defn(state, code_buf);
+
+ fclose(new_state.ctx);
+ assert(ctx_buf);
+ add_type(state, ctx_buf);
+
+ free(proto);
+ free(name);
return 0;
}
-static int lower_var(struct ast *var)
+static int lower_param_copy(struct state *state, struct ast *param, FILE *f,
+ size_t idx)
{
- if (lower_type(var_type(var)))
- return -1;
+ char *type = lower_type_str(state, param->scope, param->t);
+ fprintf(f, " %s a%zu;\n", type, idx);
+ free(type);
- printf(" %s", var_id(var));
+ char *p = mangle2(param);
+ write_codeline(state, "ctx->%s = params->a%zu;\n", p, idx);
+ free(p);
return 0;
}
-static int lower_vars(struct ast *vars)
+static int lower_param_copies(struct state *state, struct ast *params)
{
- if (!vars)
- return 0;
-
- if (lower_var(vars))
- return -1;
+ char *param_buf = NULL; size_t param_size = 0;
+ FILE *f = open_memstream(&param_buf, &param_size);
+ fprintf(f, "struct %s_params {\n fwd_start_t start;\n", state->prefix);
- foreach_node(var, vars->n) {
- printf(", ");
- if (lower_var(var))
+ size_t idx = 0;
+ foreach_node(p, params) {
+ if (lower_param_copy(state, p, f, idx)) {
+ fclose(f);
+ free(param_buf);
return -1;
+ }
+
+ idx++;
}
+ fprintf(f, "};\n\n");
+ fclose(f);
+ assert(param_buf);
+
+ add_type(state, param_buf);
return 0;
}
-static int lower_closure(struct state *state, struct ast *closure)
+static int lower_type_def(struct state *state, struct ast *type)
{
- printf("[&](");
- if (lower_vars(closure_bindings(closure)))
- return -1;
+ assert(type->k == AST_STRUCT_DEF);
+ if (type_lowered(type))
+ return 0;
- printf(")");
+ ast_set_flags(type, AST_FLAG_LOWERED);
- if (lower_block(state, closure_body(closure), true))
- 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_proto(struct ast *proc)
-{
- /* 'extern' functions should be provided to us by whatever framework the
- * user is using */
- if (!proc_body(proc))
- return 0;
+ char *defn_buf = NULL; size_t defn_len = 0;
+ FILE *defn = open_memstream(&defn_buf, &defn_len);
+ assert(defn);
- printf("fwd_err_t ");
- if (strcmp("main", proc_id(proc)) == 0)
- printf("fwd_main(");
- else
- printf("%s(", proc_id(proc));
+ fprintf(defn, "typedef struct %s {\n", name);
+ 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);
+ }
- if (lower_vars(proc_params(proc)))
- return -1;
+ fprintf(defn, "} %s;\n", name);
+
+ fclose(defn);
+ free(name);
+
+ add_type(state, decl_buf);
+ add_type(state, defn_buf);
- printf(");\n\n");
return 0;
}
-static int lower_proc(struct ast *proc)
+static int lower_proc(struct state *state, struct ast *proc)
{
- 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));
+ ast_set_flags(proc, AST_FLAG_LOWERED);
- if (lower_vars(proc_params(proc)))
- return -1;
+ if (!proc_body(proc))
+ return lower_extern_proc(state, proc);
- printf(")\n");
+ 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
+ );
- struct state state = {0};
- if (lower_block(&state, proc_body(proc), true))
- return -1;
+ char *decl = buildstr("%s;\n", proto);
+ add_decl(state, decl);
- printf("\n\n");
- return 0;
+ 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);
+ write_codeline(&new_state,
+ "static_assert(FWD_FRAME_SIZE >= sizeof(struct %s_ctx),"
+ " \"context exceeds frame size\");\n",
+ name
+ );
+
+ if (ast_flags(proc, AST_REQ_FRAME)) {
+ write_codeline(&new_state, "struct %s_ctx *ctx "
+ "= fwd_stack_alloc(&stack);\n",
+ name
+ );
+ }
+ else {
+ write_codeline(&new_state, "struct %s_ctx ctx_buf;\n", name);
+ write_codeline(&new_state, "struct %s_ctx *ctx = &ctx_buf;\n",
+ name
+ );
+ }
+
+ if (proc_params(proc)) {
+ write_codeline(&new_state,
+ "struct %s_params *params = (struct %s_params *)args;\n",
+ name, name
+ );
+ }
+
+ /* allocates parameter slots */
+ int ret = lower_params(&new_state, proc_params(proc));
+ assert(ret == 0);
+
+ /* actually copies values into parameter slots */
+ ret = lower_param_copies(&new_state, proc_params(proc));
+ assert(ret == 0);
+
+ write_codeline(&new_state, "ctx->global_args = args;\n");
+
+ struct ast *block = proc_body(proc);
+ ret = lower_stmt_list(&new_state, block_body(block), true);
+
+ fprintf(new_state.code, "}\n\n");
+ fprintf(new_state.ctx, "};\n\n");
+
+ fclose(new_state.code);
+ fclose(new_state.ctx);
+ assert(code_buf);
+ assert(ctx_buf);
+
+ add_defn(state, code_buf);
+ add_type(state, ctx_buf);
+
+ 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);
+ }
+
+ 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");
- puts("int main()");
- puts("{");
- puts(" fwd_err_t err = fwd_main();");
- puts(" if (err) {");
- puts(" fprintf(stderr, \"%s\", err);");
- puts(" return -1;");
- puts(" }");
- puts("}");
+ /* 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 1;}\n"
+ );
+ string_vec_destroy(&defns);
+ string_vec_destroy(&decls);
+ string_vec_destroy(&types);
+ proc_set_destroy(&procs);
+ free(name);
return 0;
}
diff --git a/src/main.c b/src/main.c
index 204a920..c5937dc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -45,6 +45,11 @@ static void usage()
*/
int main(int argc, char *argv[])
{
+#ifdef COVERAGE
+ assert(!covsrv_init());
+ atexit(covsrv_destroy);
+#endif
+
int opt;
while ((opt = getopt(argc, argv, "hI:")) != -1) {
switch (opt) {
diff --git a/src/move.c b/src/move.c
index 602b620..44fbddd 100644
--- a/src/move.c
+++ b/src/move.c
@@ -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,16 @@ 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)
{
@@ -235,7 +177,8 @@ static int refcheck(struct state *state, struct ast *node)
case AST_ID: return refcheck_id(state, node);
default:
internal_error("unhandled node %s for refcheck",
- ast_str(node->k));
+ ast_str(node->k)
+ );
return -1;
}
@@ -251,20 +194,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)
@@ -285,7 +215,9 @@ static int total_check_single(struct state *state, struct scope *scope)
if (prev)
continue;
- semantic_warn(scope, def, "%s not moved, might leak", var_id(def));
+ semantic_warn(scope, def, "%s not moved, might leak",
+ var_id(def)
+ );
ret |= 1;
}
@@ -295,47 +227,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 +283,36 @@ 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 +322,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 +343,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;
}
@@ -464,16 +377,29 @@ static int mvcheck_id(struct state *state, struct ast *node)
if (def->k != AST_VAR_DEF)
return 0;
+ struct ast_pair *prev = find_move(state, def);
+
+ /* a reference invalidation is represented as a 'moved'
+ * reference, which is not expressible within the
+ * language but is constructed as part of
+ * opt_group_left/opt_group_right forcing a move to
+ * happen. Hack? */
+ if (def->t->k == TYPE_REF && prev) {
+ /** @todo a more fitting error message? */
+ move_error(node, prev->use);
+ return -1;
+ }
+
if (is_trivially_copyable(def->t))
return 0;
if (in_pure(state)) {
semantic_error(node->scope, node,
- "move in pure context not allowed");
+ "move in pure context not allowed"
+ );
return -1;
}
- struct ast_pair *prev = find_move(state, def);
if (prev) {
/* error messages for opt groups could be improved */
move_error(node, prev->use);
@@ -500,29 +426,37 @@ 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 +470,125 @@ 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 +597,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..bd5b033 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. */
@@ -187,16 +191,41 @@ static char match_escape(char c);
*/
static char *strip(const char *s);
+#define Q(x) if (!(x)) { YYABORT; }
+
%}
%start input;
%%
+param
+ : type ID {
+ $$ = gen_var($2, $1, src_loc(@$));
+ Q($$);
+ }
+
+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
- : type ID { $$ = gen_var($2, $1, src_loc(@$)); }
+ : param
+ | ID {
+ $$ = gen_var($1, NULL, src_loc(@$));
+ Q($$);
+ }
rev_vars
: rev_vars "," var { $$ = $3; $$->n = $1; }
- | rev_vars "|" var { $$ = $3; $$->n = $1; opt_group($1, $3); }
| var
vars
@@ -208,55 +237,62 @@ 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(@$));
+ Q($$);
}
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(@$));
+ Q($$);
}
binop
- : expr "+" expr { $$ = gen_binop(AST_ADD, $1, $3, src_loc(@$)); }
- | expr "-" expr { $$ = gen_binop(AST_SUB, $1, $3, src_loc(@$)); }
- | expr "*" expr { $$ = gen_binop(AST_MUL, $1, $3, src_loc(@$)); }
- | expr "/" expr { $$ = gen_binop(AST_DIV, $1, $3, src_loc(@$)); }
- | expr "%" expr { $$ = gen_binop(AST_REM, $1, $3, src_loc(@$)); }
- | expr "<" expr { $$ = gen_comparison(AST_LT, $1, $3, src_loc(@$)); }
- | expr ">" expr { $$ = gen_comparison(AST_GT, $1, $3, src_loc(@$)); }
- | expr "<<" expr { $$ = gen_binop(AST_LSHIFT, $1, $3, src_loc(@$)); }
- | expr ">>" expr { $$ = gen_binop(AST_RSHIFT, $1, $3, src_loc(@$)); }
- | expr "<=" expr { $$ = gen_comparison(AST_LE, $1, $3, src_loc(@$)); }
- | 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(@$)); }
+ : expr "+" expr { $$ = gen_binop(AST_ADD, $1, $3, src_loc(@$)); Q($$); }
+ | expr "-" expr { $$ = gen_binop(AST_SUB, $1, $3, src_loc(@$)); Q($$); }
+ | expr "*" expr { $$ = gen_binop(AST_MUL, $1, $3, src_loc(@$)); Q($$); }
+ | expr "/" expr { $$ = gen_binop(AST_DIV, $1, $3, src_loc(@$)); Q($$); }
+ | expr "%" expr { $$ = gen_binop(AST_REM, $1, $3, src_loc(@$)); Q($$); }
+ | expr "<" expr { $$ = gen_comparison(AST_LT, $1, $3, src_loc(@$)); Q($$); }
+ | expr ">" expr { $$ = gen_comparison(AST_GT, $1, $3, src_loc(@$)); Q($$); }
+ | expr "<<" expr { $$ = gen_binop(AST_LSHIFT, $1, $3, src_loc(@$)); Q($$); }
+ | expr ">>" expr { $$ = gen_binop(AST_RSHIFT, $1, $3, src_loc(@$)); Q($$); }
+ | expr "<=" expr { $$ = gen_comparison(AST_LE, $1, $3, src_loc(@$)); Q($$); }
+ | expr ">=" expr { $$ = gen_comparison(AST_GE, $1, $3, src_loc(@$)); Q($$); }
+ | expr "!=" expr { $$ = gen_comparison(AST_NE, $1, $3, src_loc(@$)); Q($$); }
+ | expr "==" expr { $$ = gen_comparison(AST_EQ, $1, $3, src_loc(@$)); Q($$); }
+ | id "~" id { $$ = gen_paste($1, $3, src_loc(@$)); Q($$); }
unop
- : "-" expr { $$ = gen_unop(AST_NEG, $2, src_loc(@$)); }
- | "!" expr { $$ = gen_unop(AST_LNOT, $2, src_loc(@$)); }
+ : "-" expr { $$ = gen_unop(AST_NEG, $2, src_loc(@$)); Q($$); }
+ | "!" expr { $$ = gen_unop(AST_LNOT, $2, src_loc(@$)); Q($$); }
+ | "~" expr { $$ = gen_unop(AST_BNEG, $2, src_loc(@$)); Q($$); }
type
- : ID { $$ = tgen_id($[ID], src_loc(@$)); }
- | APPLY "[" opt_types "]" {
- $$ = tgen_construct($[APPLY], $[opt_types], src_loc(@$));
+ : ID {
+ $$ = tgen_id($1, src_loc(@$));
+ Q($$);
}
- | "(" opt_types ")" { $$ = tgen_closure($2, src_loc(@$)); }
+ | "(" opt_types ")" { $$ = tgen_closure($2, src_loc(@$)); Q($$); }
| "&&" type {
if ($2->k == TYPE_CLOSURE) {
$$ = $2; $$->k = TYPE_PURE_CLOSURE;
} else {
$$ = tgen_ref($2, src_loc(@$));
+ Q($$);
}
/* heh */
$$ = tgen_ref($$, src_loc(@$));
+ Q($$);
}
| "&" type {
if ($2->k == TYPE_CLOSURE) {
$$ = $2; $$->k = TYPE_PURE_CLOSURE;
} else {
$$ = tgen_ref($2, src_loc(@$));
+ Q($$);
}
}
| "*" type {
@@ -264,11 +300,13 @@ type
$$ = $2; $$->k = TYPE_FUNC_PTR;
} else {
$$ = tgen_ptr($2, src_loc(@$));
+ Q($$);
}
}
rev_types
: rev_types "," type { $$ = $3; $$->n = $1; }
+ | rev_types ">" type { $$ = $3; $$->n = $1; own_type_group($1, $3); }
| type
types
@@ -278,26 +316,66 @@ opt_types
: types
| { $$ = NULL; }
+construction
+ : expr "=>" ID {
+ $$ = gen_construction($3, $1, src_loc(@$));
+ Q($$);
+ }
+
+constructions
+ : constructions "," construction { $$ = $3; $$->n = $1; }
+ | construction
+
+opt_constructions
+ : constructions
+ | { $$ = NULL; }
+
+deconstruction
+ : ID "=>" var {
+ $$ = gen_deconstruction($1, $3, src_loc(@$));
+ Q($$);
+ }
+
+deconstructions
+ : deconstructions "," deconstruction { $$ = $3; $$->n = $1; }
+ | deconstruction
+
+opt_deconstructions
+ : deconstructions
+ | { $$ = NULL; }
+
construct
- : APPLY "{" opt_exprs "}" {
- $$ = gen_init($[APPLY], NULL, $[opt_exprs], src_loc(@$));
+ : "[" opt_constructions "]" ID {
+ $$ = gen_construct($4, $2, src_loc(@$));
+ Q($$);
}
- | APPLY "[" opt_types "]" "{" opt_exprs "}" {
- $$ = gen_init($[APPLY], $[opt_types], $[opt_exprs], src_loc(@$));
+
+id
+ : ID {
+ $$ = gen_id($1, src_loc(@$));
+ Q($$);
}
expr
- : expr "." ID { $$ = gen_dot($3, $1, 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(@$)); }
+ : "sizeof" "(" type ")" { $$ = gen_sizeof($3, src_loc(@$)); Q($$); }
+ | STRING {
+ char *s = strip($1);
+ Q(s);
+
+ $$ = gen_const_str(s, src_loc(@$));
+ Q($$);
+ }
+ | FLOAT { $$ = gen_const_float($1, src_loc(@$)); Q($$); }
+ | BOOL { $$ = gen_const_bool($1, src_loc(@$)); Q($$); }
+ | CHAR { $$ = gen_const_char($1, src_loc(@$)); Q($$); }
+ | INT { $$ = gen_const_int($1, src_loc(@$)); Q($$); }
+ | "*" { $$ = gen_nil(src_loc(@$)); Q($$); }
| "(" expr ")" { $$ = $2; }
- | expr "&" { $$ = gen_ref($1, src_loc(@$)); }
- | expr "*" { $$ = gen_deref($1, src_loc(@$)); }
+ | expr "&" { $$ = gen_ref($1, src_loc(@$)); Q($$); }
+ | expr "*" { $$ = gen_deref($1, src_loc(@$)); Q($$); }
+ | expr "as" type { $$ = gen_as($1, $3, src_loc(@$)); Q($$); }
| construct
+ | id
| binop
| unop
@@ -315,9 +393,11 @@ opt_exprs
closure
: "=>" opt_vars body {
$$ = gen_closure($[opt_vars], $[body], src_loc(@$));
+ Q($$);
}
| "&" "=>" opt_vars body {
$$ = gen_closure($[opt_vars], $[body], src_loc(@$));
+ Q($$);
ast_set_flags($$, AST_FLAG_NOMOVES);
}
@@ -326,9 +406,13 @@ rev_closures
| closure
trailing_closure
- : "=>" opt_vars ";" { $$ = gen_closure($[opt_vars], NULL, src_loc(@$));}
+ : "=>" opt_vars ";" {
+ $$ = gen_closure($[opt_vars], NULL, src_loc(@$));
+ Q($$);
+ }
| "&" "=>" opt_vars ";" {
$$ = gen_closure($[opt_vars], NULL, src_loc(@$));
+ Q($$);
ast_set_flags($$, AST_FLAG_NOMOVES);
}
@@ -346,87 +430,105 @@ 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(@$));
+ Q($$);
}
- | 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(@$));
+ Q(closure);
+
ast_append(&$[opt_exprs], closure);
- $$ = gen_call($[expr], $[opt_exprs], $[opt_err], src_loc(@$));
+ $$ = gen_call($[expr], $[opt_exprs], src_loc(@$));
+ Q($$);
}
- | 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(@$));
+ Q($$);
}
-let
- : expr "=>" var ";" { $$ = gen_let($3, $1, src_loc(@$)); }
+put
+ : put "*" { $$ = gen_put($1, src_loc(@$)); Q($$); }
+ | id "*" { $$ = gen_put($1, src_loc(@$)); Q($$); }
-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" if { $$ = gen_if($2, $3, $5, src_loc(@$)); }
+let
+ : expr "=>" var ";" {
+ $$ = gen_let($3, $1, src_loc(@$));
+ Q($$);
+ }
+ | expr "=>" put ";" {
+ $$ = gen_write($3, $1, src_loc(@$));
+ Q($$);
+ }
-nil
- : "nil" ID body "=>" var {
- $$ = gen_nil($2, $3, $5, src_loc(@$));
+explode
+ : expr "=>" "[" opt_deconstructions "]" {
+ $$ = gen_explode($4, $1, src_loc(@$));
+ Q($$);
}
-own
- : "own" ID body {
- $$ = gen_own($2, $3, src_loc(@$));
+if
+ : "if" expr body "else" body {
+ $$ = gen_if($2, $3, $5, src_loc(@$));
+ Q($$);
+ }
+ | "if" expr body "else" if {
+ $$ = gen_if($2, $3, $5, src_loc(@$));
+ Q($$);
}
-opt_error
- : "error" STRING {
- $$ = gen_error($2, NULL, src_loc(@$));
+nil
+ : "nil" expr body "=>" var ";" {
+ /** @todo should nil check define new scope for created
+ * variable? */
+ $$ = gen_nil_check($2, $3, $5, src_loc(@$));
+ Q($$);
}
- | "error" ID {
- $$ = gen_error(NULL, gen_id($2, src_loc(@$)), src_loc(@$));
+
+forget
+ : "nil" ID ";" {
+ $$ = gen_forget($2, src_loc(@$));
+ Q($$);
}
- | { $$ = NULL; }
statement
: call
+ | forget
| body
+ | explode
| let
| nil
- | own
| if
- | ";" { $$ = gen_empty(src_loc(@$)); }
+ | ";" { $$ = gen_empty(src_loc(@$)); Q($$); }
rev_statements
: rev_statements statement { $$ = $2; $2->n = $1; }
| statement
statements
- : rev_statements { $$ = reverse_ast_list($1); fix_closures($$); }
+ : rev_statements {
+ $$ = filter_empty(reverse_ast_list($1));
+ fix_closures($$);
+ }
opt_statements
: statements
| { $$ = NULL; }
body
- : "{" opt_statements opt_error "}" {
- $$ = gen_block($2, $3, src_loc(@$));
+ : "{" opt_statements "}" {
+ $$ = gen_block($2, src_loc(@$));
+ Q($$);
}
behaviour
- : APPLY ";" { $$ = gen_trait_apply($1, src_loc(@$)); }
- | proc_decl ";"
+ : proc_decl
| proc
+ | impl
behaviours
: behaviours behaviour { $$ = $1; $1->n = $2; }
@@ -436,22 +538,30 @@ opt_behaviours
: behaviours
| { $$ = NULL; }
+impl
+ : ID "!" {
+ $$ = gen_implements($1, src_loc(@$));
+ Q($$);
+ }
+
member
- : var ";"
- | behaviour
+ : param
members
- : members member { $$ = $1; $1->n = $2; }
+ : members ";" member { $$ = $3; $$->n = $1; }
| member
opt_members
- : members
+ : members ";"
| { $$ = NULL; }
type_param
: ID ID {
struct type *t = tgen_id($1, src_loc(@1));
+ Q(t);
+
$$ = gen_var($2, t, src_loc(@$));
+ Q($$);
}
type_params
@@ -464,41 +574,93 @@ 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(@$));
+ Q($$);
+ }
+
+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(@$));
+ Q(pseudoinst);
+
+ $$ = gen_supertemplate($1, $3, $6, pseudoinst, $17, src_loc(@$));
+ Q($$);
+ }
+
struct
- : ID "[" opt_type_params "]" "{" opt_members "}" {
- $$ = gen_struct($1, $3, $6, src_loc(@$));
+ : ID "{" opt_members "}" {
+ $$ = gen_struct($1, $3, src_loc(@$));
+ Q($$);
}
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(@$));
+ Q($$);
}
trait
- : ID "{" opt_behaviours "}" {
- $$ = gen_trait($1, $3, src_loc(@$));
+ : ID "=" "{" opt_behaviours "}" {
+ $$ = gen_trait($1, $4, src_loc(@$));
+ Q($$);
+ }
+
+instance
+ : ID "=" id "[" opt_types "]" "(" opt_exprs ")" {
+ $$ = gen_instance($1, $3, $5, $8, src_loc(@$));
+ Q($$);
}
import
: "import" STRING {
- $$ = gen_import($2, src_loc(@$));
+ char *s = strip($2);
+ Q(s);
+
+ $$ = gen_import(s, src_loc(@$));
+ Q($$);
}
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(@$));
+ Q($$);
+
parser->failed = true;
/* ignore any content inside a top level thing and just move onto
* the next one */
@@ -593,11 +755,11 @@ static char match_escape(char c)
static char *strip(const char *str)
{
const size_t len = strlen(str) + 1;
- char *buf = malloc(len);
+ char *buf = mallocc(len);
if (!buf) {
/* should probably try to handle the error in some way... */
internal_error("failed allocating buffer for string clone");
- free((void *)str);
+ /* str should be in tracker already, so no need to free it */
return NULL;
}
@@ -607,14 +769,20 @@ static char *strip(const char *str)
buf[j++] = str[i];
buf[j] = 0;
- free((void *)str);
+ track_ptr(buf);
return buf;
}
struct parser *create_parser()
{
- return calloc(1, sizeof(struct parser));
+ struct parser *p = callocc(1, sizeof(struct parser));
+ if (!p) {
+ internal_error("failed allocating parser");
+ return NULL;
+ }
+
+ return p;
}
void destroy_parser(struct parser *p)
diff --git a/src/path.c b/src/path.c
new file mode 100644
index 0000000..5502a7b
--- /dev/null
+++ b/src/path.c
@@ -0,0 +1,86 @@
+/* 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>
+#include <fwd/tracker.h>
+#include <fwd/coverage.h>
+
+char *fwd_basename(const char *file)
+{
+ size_t l = strlen(file);
+ size_t n = l - 1;
+ while (--n) {
+ if (file[n] == '/')
+ break;
+ }
+
+ char *s = NULL;
+ if (n == 0)
+ s = strdupc(file);
+ else
+ s = strndupc(file + n + 1, l - n);
+
+ if (!s) {
+ internal_error("failed allocating basename");
+ return NULL;
+ }
+
+ track_ptr(s);
+ return s;
+}
+
+char *fwd_dirname(const char *file)
+{
+ size_t l = strlen(file);
+ size_t n = l - 1;
+ while (--n) {
+ if (file[n] == '/')
+ break;
+ }
+
+ char *s = strndupc(file, n);
+ if (!s) {
+ internal_error("failed allocating dirname");
+ return NULL;
+ }
+
+ track_ptr(s);
+ return s;
+}
+
+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 = mallocc(size);
+ if (!buf) {
+ internal_error("failed allocating cwd buf");
+ return NULL;
+ }
+
+ track_ptr(buf);
+ if (!getcwd(buf, size)) {
+ error("%s\n", strerror(errno));
+ return NULL;
+ }
+
+ return buf;
+}
diff --git a/src/rewrite.c b/src/rewrite.c
new file mode 100644
index 0000000..6409686
--- /dev/null
+++ b/src/rewrite.c
@@ -0,0 +1,108 @@
+#include <fwd/rewrite.h>
+#include <fwd/debug.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 = strdupc(new);
+ if (!r) {
+ internal_error("failed allocating hole");
+ return -1;
+ }
+
+ 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 = mallocc(on + nn + 1);
+ if (!r) {
+ internal_error("failed allocating hole rewrite buf");
+ return NULL;
+ }
+
+ 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..f350090 100644
--- a/src/scope.c
+++ b/src/scope.c
@@ -8,11 +8,13 @@
#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/coverage.h>
#include <fwd/debug.h>
#include <fwd/scope.h>
@@ -21,7 +23,7 @@ struct scope *create_scope()
/* if I ever try making the parser multithreaded, this should be atomic. */
static size_t counter = 0;
- struct scope *scope = calloc(1, sizeof(struct scope));
+ struct scope *scope = callocc(1, sizeof(struct scope));
if (!scope) {
internal_error("ran out of memory allocating scope");
return NULL;
@@ -29,8 +31,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;
}
@@ -39,12 +43,14 @@ void destroy_scope(struct scope *scope)
if (!scope)
return;
- if (!scope->parent) {
- free((void *)scope->fctx.fbuf);
- 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);
@@ -61,7 +67,9 @@ void destroy_scope(struct scope *scope)
int scope_add_symbol(struct scope *scope, struct ast *symbol)
{
assert(symbol->k == AST_VAR_DEF || symbol->k == AST_PROC_DEF);
- struct ast **exists = visible_insert(&scope->symbols, symbol->s, symbol);
+ struct ast **exists = visible_insert(&scope->symbols, symbol->s,
+ symbol
+ );
if (!exists) {
internal_error("failed inserting symbol into scope");
return -1;
@@ -100,7 +108,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 +123,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 +147,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 +161,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 +183,46 @@ 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);
diff --git a/src/tracker.c b/src/tracker.c
new file mode 100644
index 0000000..2bf56d4
--- /dev/null
+++ b/src/tracker.c
@@ -0,0 +1,22 @@
+#include <fwd/tracker.h>
+
+#define VEC_NAME ptr_tracker
+#define VEC_TYPE void *
+#include <conts/vec.h>
+
+static struct ptr_tracker ptrs;
+
+void *track_ptr(void *p)
+{
+ ptr_tracker_append(&ptrs, p);
+ return p;
+}
+
+void free_tracked_ptrs()
+{
+ foreach(ptr_tracker, ptr, &ptrs) {
+ free(*ptr);
+ }
+
+ ptr_tracker_destroy(&ptrs);
+}
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 0000000..81cc205
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,41 @@
+export VALGRIND ?= 1
+
+check:
+ $(MAKE) -f scripts/makefile check
+
+.DEFAULT:
+ $(MAKE) -f scripts/makefile $<
+
+TESTS != find . -name test.mk
+DO != echo -n > tests.mk && rm -rf reports
+
+# collect tests that are expected to pass into XPASS, each testcase is assumed
+# to be in a separate directory, let's call it $NAME, and have a $NAME/test.mk
+# file which just appends the $NAME of to XPASS if the test should pass, so
+#
+# XPASS += $NAME
+#
+# or to XFAIL along, with the expected error message with the format
+#
+# XFAIL += $NAME,'error message'
+#
+# Note that it's very important to use single quotes here, otherwise Make might
+# start splitting the error message along spaces into arguments.
+#
+# Each test is assumed to have a fwd file called $NAME/$NAME.fwd, which should
+# exercise some part of the compiler. The test framework links all libraries in
+# `../mod` into the binary automatically. Test logs, along with some other
+# useful stuff, is written out to `reports/$NAME`.
+XPASS :=
+XFAIL :=
+
+include $(TESTS)
+
+DO != ./scripts/gen-xpass $(XPASS)
+DO != ./scripts/gen-xfail $(XFAIL)
+
+RM ?= rm
+
+.PHONY: clean
+clean:
+ $(RM) -rf reports
diff --git a/tests/fib/fib.fwd b/tests/fib/fib.fwd
new file mode 100644
index 0000000..e07d8af
--- /dev/null
+++ b/tests/fib/fib.fwd
@@ -0,0 +1,24 @@
+import "../../mod/libfwdio.so"
+
+fib(i64 n, (i64) res)
+{
+ if n < 2 {
+ res(1);
+ } else {
+ fib(n - 1) => i64 f1;
+ fib(n - 2) => i64 f2;
+ res(f1 + f2);
+ }
+}
+
+main()
+{
+ fib(20) => i64 n;
+ if n == 10946 {
+ fwdprint_str("OK\n");
+ } else {
+ fwdprint_str("expected 10946, got ");
+ fwdprint_i64(n);
+ fwdprint_nl();
+ }
+}
diff --git a/tests/fib/test.mk b/tests/fib/test.mk
new file mode 100644
index 0000000..3ace25e
--- /dev/null
+++ b/tests/fib/test.mk
@@ -0,0 +1 @@
+XPASS += fib
diff --git a/tests/guard/guard.fwd b/tests/guard/guard.fwd
new file mode 100644
index 0000000..dbde0f3
--- /dev/null
+++ b/tests/guard/guard.fwd
@@ -0,0 +1,21 @@
+import "../../mod/libfwdio.so"
+
+guard(bool cond, () err | () ok)
+{
+ if cond {
+ err();
+ } else {
+ ok();
+ }
+}
+
+main()
+{
+ guard(1 > 2) => {
+ fwdprint_str("guard or comparison broken");
+ fwdprint_nl();
+ } => ;
+
+ fwdprint_str("OK");
+ fwdprint_nl();
+}
diff --git a/tests/guard/test.mk b/tests/guard/test.mk
new file mode 100644
index 0000000..3214e3c
--- /dev/null
+++ b/tests/guard/test.mk
@@ -0,0 +1 @@
+XPASS += guard
diff --git a/examples/opt_group.fwd b/tests/opt_group/opt_group.fwd
index e520c70..e520c70 100644
--- a/examples/opt_group.fwd
+++ b/tests/opt_group/opt_group.fwd
diff --git a/tests/opt_group/test.mk b/tests/opt_group/test.mk
new file mode 100644
index 0000000..c51cdec
--- /dev/null
+++ b/tests/opt_group/test.mk
@@ -0,0 +1 @@
+XFAIL += opt_group,'opt_group.fwd:6:2: error: using moved value'
diff --git a/tests/ptrs/ptrs.fwd b/tests/ptrs/ptrs.fwd
new file mode 100644
index 0000000..fbb9cbb
--- /dev/null
+++ b/tests/ptrs/ptrs.fwd
@@ -0,0 +1,8 @@
+import "../../mod/libfwdmem.so"
+
+main()
+{
+ fwdmalloc(sizeof(i64)) => *i64 i;
+ /* deref not allowed on raw pointers */
+ i* + 20 => int something;
+}
diff --git a/tests/ptrs/test.mk b/tests/ptrs/test.mk
new file mode 100644
index 0000000..3003a8c
--- /dev/null
+++ b/tests/ptrs/test.mk
@@ -0,0 +1 @@
+XFAIL += ptrs,'deref of raw ptr not allowed'
diff --git a/tests/scripts/gen-xfail b/tests/scripts/gen-xfail
new file mode 100755
index 0000000..a04f8d9
--- /dev/null
+++ b/tests/scripts/gen-xfail
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+mkdir -p $(for d in "${@}"; do echo "$d"; done \
+ | sed "s|,.*||" | uniq | sed "s|^|reports/|")
+
+for s in "${@}"
+do
+ NAME=${s%%,*}
+ EMSG=${s#${NAME},}
+ echo ".PHONY: $NAME" >> tests.mk
+ echo "$NAME:" >> tests.mk
+ echo " ./scripts/run-xfail $NAME '$EMSG'" >> tests.mk
+done
+
+echo -n "TESTS +=" >> tests.mk
+for s in "${@}"
+do
+ NAME=${s%%,*}
+ echo -n " $NAME" >> tests.mk
+done
+
+# append newline
+echo "" >> tests.mk
diff --git a/tests/scripts/gen-xpass b/tests/scripts/gen-xpass
new file mode 100755
index 0000000..8ec383f
--- /dev/null
+++ b/tests/scripts/gen-xpass
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+mkdir -p $(for d in "${@}"; do echo "$d"; done | uniq | sed "s|^|reports/|")
+
+for s in "${@}"
+do
+ echo ".PHONY: $s" >> tests.mk
+ echo "$s:" >> tests.mk
+ echo " ./scripts/run-xpass $s" >> tests.mk
+done
+
+echo "TESTS += " "${@}" >> tests.mk
diff --git a/tests/scripts/makefile b/tests/scripts/makefile
new file mode 100644
index 0000000..e6ea394
--- /dev/null
+++ b/tests/scripts/makefile
@@ -0,0 +1,27 @@
+.PHONY: all
+all: check
+
+COMPILE_TEST := $(CC) -L../mod -I../include -I../lib -Wl,-rpath=../mod -O2
+TEST_LIBS := -lfwdio -lfwdmem -lfwdutil
+
+export COMPILE_TEST
+export TEST_LIBS
+
+TESTS :=
+include tests.mk
+
+.PHONY: check
+check: $(TESTS)
+ @cd reports; OK=1; for d in * ; do \
+ if [ ! -f "$$d/OK" ]; then \
+ echo "BROKEN: $$d" ; \
+ elif [ "$$(tail -n1 $$d/OK)" != "OK" ]; then \
+ OK=0 ; \
+ echo "FAIL: $$d" ; \
+ fi \
+ done; \
+ if [ "$$OK" != 1 ]; then \
+ echo "There were failing tests"; \
+ exit 1; \
+ fi
+ @echo "Done."
diff --git a/tests/scripts/run-xfail b/tests/scripts/run-xfail
new file mode 100755
index 0000000..35bcc75
--- /dev/null
+++ b/tests/scripts/run-xfail
@@ -0,0 +1,58 @@
+#!/bin/sh
+NAME="$1"
+EMSG="$2"
+
+try_vg() {
+ if [ "x$VALGRIND" != "x0" ] && which valgrind > /dev/null >&1; then
+ eval "valgrind -q --leak-check=full --error-exitcode=169 $1"
+ else
+ eval "$1"
+ fi
+
+ RETVAL=$?
+ return "$RETVAL"
+}
+
+I=0
+while :; do
+ if try_vg "../fwd $NAME/$NAME.fwd" \
+ 1> "reports/$NAME/gen.c" \
+ 2> "reports/$NAME/run-$I"
+ then
+ # success was not expected
+ echo "SUCC" > reports/$NAME/OK
+ exit 0
+ fi
+
+ if [ "$RETVAL" = 169 ]; then
+ echo "VALGRIND" > reports/$NAME/OK
+
+ # don't return on an error so that makefile continues running
+ # other tests that are maybe failing, and reports the status
+ # after all tests have been run
+ exit 0
+ fi
+
+ # correctly handled coverage exception, continue to next run
+ # here we check for the special 'internal error' which should be
+ # reported if the error was handled gracefully, otherwise since the
+ # error message is not the one we expected and not a covsrv error, we
+ # don't know what it is and could end up in an infinite loop if we don't
+ # stop here.
+ if grep 'COVSRV: EXIT' "reports/$NAME/gen.c" > /dev/null \
+ && grep 'internal error:' "reports/$NAME/run-$I" > /dev/null; then
+ I=$((I+1))
+ continue
+ fi
+
+ if grep "$EMSG" "reports/$NAME/run-$I" > /dev/null; then
+ # we exited with the expected error message and without leaking
+ # memory, presume covsrv is happy as well
+ echo "OK" > reports/$NAME/OK
+ exit 0
+ fi
+
+ # incorrectly handled coverage exception, stop run
+ echo "MEMERR" > reports/$NAME/OK
+ exit 0
+done
diff --git a/tests/scripts/run-xpass b/tests/scripts/run-xpass
new file mode 100755
index 0000000..93540c9
--- /dev/null
+++ b/tests/scripts/run-xpass
@@ -0,0 +1,53 @@
+#!/bin/sh
+NAME="$1"
+
+if [ -z "${COMPILE_TEST+x}" -o -z "${TEST_LIBS+x}" ]; then
+ echo "Test runner script must have COMPILE_TEST and TEST_LIBS defined."
+ exit 1
+fi
+
+try_vg() {
+ if [ "x$VALGRIND" != "x0" ] && which valgrind > /dev/null >&1; then
+ eval "valgrind -q --leak-check=full --error-exitcode=169 $1"
+ else
+ eval "$1"
+ fi
+
+ RETVAL=$?
+ return "$RETVAL"
+}
+
+I=0
+while :; do
+ if try_vg "../fwd $NAME/$NAME.fwd" \
+ 1> "reports/$NAME/gen.c" \
+ 2> "reports/$NAME/run-$I"
+ then
+ # fwd succeeded, compile and run program to check result
+ # remove possible COVSRV lines in output
+ sed -i "s/COVSRV:.*$//" "reports/$NAME/gen.c"
+ eval "$COMPILE_TEST reports/$NAME/gen.c -o reports/$NAME/$NAME $TEST_LIBS"
+ ./reports/$NAME/$NAME > reports/$NAME/OK 2>&1
+ exit 0
+ fi
+
+ if [ "$RETVAL" = 169 ]; then
+ echo "VALGRIND" > reports/$NAME/OK
+
+ # don't return on an error so that makefile continues running
+ # other tests that are maybe failing, and reports the status
+ # after all tests have been run
+ exit 0
+ fi
+
+ # correctly handled coverage exception, continue to next run
+ if grep 'COVSRV: EXIT' "reports/$NAME/gen.c" > /dev/null \
+ && grep 'internal error:' "reports/$NAME/run-$I" > /dev/null; then
+ I=$((I+1))
+ continue
+ fi
+
+ # incorrectly handled coverage exception, stop run
+ echo "MEMERR" > reports/$NAME/OK
+ exit 0
+done
diff --git a/tests/sum/sum.fwd b/tests/sum/sum.fwd
new file mode 100644
index 0000000..05a4632
--- /dev/null
+++ b/tests/sum/sum.fwd
@@ -0,0 +1,28 @@
+import "../../mod/libfwdio.so"
+
+sum_inner(i32 s, i32 n, (i32) res)
+{
+ if n <= 0 as i32 {
+ res(s);
+ } else {
+ sum_inner(s + n, n - 1 as i32, res);
+ }
+}
+
+sum(i32 n, (i32) res)
+{
+ sum_inner(0 as i32, n, res);
+}
+
+main()
+{
+ sum(1000000 as i32) => i32 s;
+ if s != 1784293664 as i32 {
+ fwdprint_str("expected 1784293664, got ");
+ fwdprint_i64(s as i64);
+ fwdprint_nl();
+ } else {
+ fwdprint_str("OK");
+ fwdprint_nl();
+ }
+}
diff --git a/tests/sum/test.mk b/tests/sum/test.mk
new file mode 100644
index 0000000..9836826
--- /dev/null
+++ b/tests/sum/test.mk
@@ -0,0 +1 @@
+XPASS += sum
diff --git a/tests/type_mismatch/test.mk b/tests/type_mismatch/test.mk
new file mode 100644
index 0000000..3f589d9
--- /dev/null
+++ b/tests/type_mismatch/test.mk
@@ -0,0 +1 @@
+XFAIL += type_mismatch,'type_mismatch.fwd:4:12: error: type mismatch: i32 vs i64'
diff --git a/tests/type_mismatch/type_mismatch.fwd b/tests/type_mismatch/type_mismatch.fwd
new file mode 100644
index 0000000..e837740
--- /dev/null
+++ b/tests/type_mismatch/type_mismatch.fwd
@@ -0,0 +1,5 @@
+takes_i32(i32 n) {}
+
+main() {
+ takes_i32(20);
+}
diff --git a/tests/vec/test.mk b/tests/vec/test.mk
new file mode 100644
index 0000000..4120ce5
--- /dev/null
+++ b/tests/vec/test.mk
@@ -0,0 +1 @@
+XPASS += vec
diff --git a/tests/vec/vec.fwd b/tests/vec/vec.fwd
new file mode 100644
index 0000000..b88d226
--- /dev/null
+++ b/tests/vec/vec.fwd
@@ -0,0 +1,153 @@
+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];
+
+ /* expand if we run out of space */
+ if c > 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([c => n, s => s, nbuf => buf] vec);
+ } else {
+ /* if this were a generic vector we'd have to worry about
+ * destroying the elements, but since we're just dealing with
+ * integers this is fine */
+ ok([c => n, s => s, buf => buf] vec);
+ }
+}
+
+set_vec(vec v, i64 i, i64 e, (vec) ok)
+{
+ v => [n => n, s => s, buf => buf];
+ /* oh wait, this is dumb, of course nbuf will never be null */
+ buf + i => nbuf;
+ nil nbuf {
+ nil nbuf;
+ fwdfree(buf);
+ fwdpanic("set_vec, should never happen\n");
+ } => 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, 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("at_vec, bounds error\n");
+ } => ;
+
+ buf + i => *i64 nbuf;
+ nil nbuf {
+ nil nbuf;
+ fwdfree(buf);
+ fwdpanic("at_vec, should never happen\n");
+ } => &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 {
+ ok();
+ } else {
+ err();
+ }
+}
+
+check_vec(i64 i, i64 n, vec v, (vec) ok)
+{
+ if i < n {
+ at_vec(v, i as u64) => v, elem;
+
+ /* oh, this doesn't really work with the memory analysis, since
+ * I can destroy `v` but `elem` is still usable. I guess I could
+ * transform this such that `elem` is only alive within a
+ * closure? Not sure. */
+ guard(elem* == i) => {
+ destroy_vec(v);
+ fwdpanic("check_vec, vec built wrong\n");
+ } => ;
+
+ 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);
+
+ fwdprint_str("OK");
+ fwdprint_nl();
+}
diff --git a/uncrustify.conf b/uncrustify.conf
index 5e01d98..d262c40 100644
--- a/uncrustify.conf
+++ b/uncrustify.conf
@@ -1448,7 +1448,7 @@ indent_paren_nl = false # true/false
# 1: Align under the open parenthesis
# 2: Indent to the brace level
# -1: Preserve original indentation
-indent_paren_close = 0 # number
+indent_paren_close = 2 # number
# Whether to indent the open parenthesis of a function definition,
# if the parenthesis is on its own line.