diff --git a/fec/src/ast.h b/fec/src/ast.h index e623588..2db0b06 100644 --- a/fec/src/ast.h +++ b/fec/src/ast.h @@ -43,6 +43,7 @@ struct FeNode { #define FE_NODE_SHARED 0x4U #define FE_NODE_PUB 0x8U #define FE_NODE_COMPTIME 0x10U +#define FE_NODE_EXTERN 0x20U typedef struct FeAst { FeArena arena; diff --git a/fec/src/check.c b/fec/src/check.c index 134fab5..f2e291d 100644 --- a/fec/src/check.c +++ b/fec/src/check.c @@ -3515,6 +3515,16 @@ static void check_stmt(FeCheckerState *s, FeNode *n) borrow_left=flow_borrow_new(s,count); flow_own_capture(left,own_left,count); flow_borrow_capture(left,borrow_left,count); + /* A branch that always leaves contributes nothing to what follows. + Merging its state would make a value it consumed look consumed + afterwards, on a path that never ran it. */ + if (m7_stmt_definitely_exits(n->b)) { + for (i=0;ic) check_stmt(s,n->c); if (n->c) { @@ -3523,6 +3533,13 @@ static void check_stmt(FeCheckerState *s, FeNode *n) borrow_right=flow_borrow_new(s,count); flow_own_capture(right,own_right,count); flow_borrow_capture(right,borrow_right,count); + if (m7_stmt_definitely_exits(n->c)) { + for (i=0;iast->root ? c->ast->root->children : 0;n;n=n->next) if (n->kind==FE_N_FN) { t=fe_type_intern(&c->types,""); + /* `extern "c"` means the linker already knows this name, so it is + not decorated with the unit it was declared in. */ add_symbol(s,globals,n->text,t,n,0,1, + (n->flags & FE_NODE_EXTERN) && n->text ? n->text : unit_cname(c,n->text ? n->text : "fn"),n); } return globals; diff --git a/fec/src/lower.c b/fec/src/lower.c index d5d27e8..04faf35 100644 --- a/fec/src/lower.c +++ b/fec/src/lower.c @@ -62,6 +62,8 @@ static void lower_stmt(Lower *L, FeNode *n); static void store_into(Lower *L, FeIrPlace dst, Slot value, FeNode *n, unsigned long size); static void lower_for(Lower *L, FeNode *n); +static void lower_match(Lower *L, FeNode *n); +static int enum_has_payload(const FeType *t); static void lower_global(Lower *L, FeNode *n); static long literal_value(FeNode *n); static Slot wrap_context(Lower *L, Slot v, FeNode *n); @@ -505,14 +507,33 @@ static Slot lower_expr_core(Lower *L, FeNode *n) case FE_N_LITERAL: if (n->text && n->text[0] == '"') { /* The bytes live in the image; the value is a pointer to them and - how many there are. */ - unsigned long len = strlen(n->text); + how many there are. The lexer keeps the quotes and the escapes, + so this is where ` +` becomes one byte. */ + char text[1024]; + unsigned long raw = strlen(n->text); + unsigned long len = 0; + unsigned long i; const char *label; unsigned local; unsigned p; unsigned c; - if (len >= 2) len -= 2; - label = fe_ir_string(L->m, n->text + 1, len); + if (raw >= 2) raw -= 2; + for (i = 0; i < raw && len + 1 < sizeof text; ++i) { + char ch = n->text[1 + i]; + if (ch == 92 && i + 1 < raw) { /* a backslash */ + ++i; + switch (n->text[1 + i]) { + case 'n': ch = 10; break; + case 't': ch = 9; break; + case 'r': ch = 13; break; + case '0': ch = 0; break; + default: ch = n->text[1 + i]; break; + } + } + text[len++] = ch; + } + label = fe_ir_string(L->m, text, len); if (!label) { fail(L, "a string literal", n); return slot_void(); } local = scratch(L, t, "text"); p = fe_ir_addr(L->m, L->b, fe_ir_at_global(label, 0)); @@ -583,6 +604,14 @@ static Slot lower_expr_core(Lower *L, FeNode *n) fail(L, "this unary operator", n); return slot_void(); case FE_N_MEMBER: + /* A payload-free variant used as a value is just its tag. */ + if (t && t->kind == FE_TYPE_ENUM && !enum_has_payload(t) && + n->b && n->b->text) { + FeVariantType *v = fe_type_variant(t, n->b->text); + if (v) + return slot_value(fe_ir_const(L->m, L->b, ir_type(t), + (long)v->tag), ir_type(t)); + } /* `error.Name` is a member of the open default set: a code, and nothing to look up. */ if (n->a && n->a->kind == FE_N_IDENT && n->a->text && @@ -688,6 +717,19 @@ static Slot lower_expr_core(Lower *L, FeNode *n) } case FE_N_CALL: return lower_call(L, n); + case FE_N_TYPE: + /* `x as T`: the operand is `a` and the target type is the node's own. + Between integers this only changes how wide the value is and whether + the top bits repeat the sign. */ + if (n->a) { + FeType *from = n->a->sem_type; + unsigned v = as_value(L, lower_expr(L, n->a), n->a); + if (ir_type(from) == it) return slot_value(v, it); + return slot_value(fe_ir_cast(L->m, L->b, ir_type(from), it, v, + type_is_unsigned(from)), it); + } + fail(L, "this type expression", n); + return slot_void(); case FE_N_EXPR: return lower_expr(L, n->a); default: @@ -1064,6 +1106,51 @@ static void lower_for(Lower *L, FeNode *n) L->b = done; } +/* `match` over a payload-free enum or an integer: compare the tag against each + arm's pattern in turn. The checker already proved the arms cover everything, + so falling off the end cannot happen in a program that compiled -- but the + generated code has to go somewhere, and going to the join is right. */ +static void lower_match(Lower *L, FeNode *n) +{ + FeType *t = n->a ? n->a->sem_type : 0; + FeIrType it = ir_type(t); + Slot subject = lower_expr(L, n->a); + unsigned value; + FeIrBlock *join; + FeNode *arm; + if (it == FE_IR_MEM) { fail(L, "a match over a payload", n); return; } + value = as_value(L, subject, n->a); + join = new_block(L); + for (arm = n->children; arm; arm = arm->next) { + FeIrBlock *body; + FeIrBlock *next; + unsigned want; + unsigned same; + FeVariantType *v; + if (arm->kind != FE_N_ARM) continue; + if (arm->text && !strcmp(arm->text, "_")) { + lower_stmt(L, arm->a); + fe_ir_jmp(L->b, join->id); + L->b = join; + return; + } + v = t && t->kind == FE_TYPE_ENUM && arm->text + ? fe_type_variant(t, arm->text) : 0; + want = fe_ir_const(L->m, L->b, it, + v ? (long)v->tag : literal_value(arm)); + same = fe_ir_binary(L->m, L->b, FE_IR_EQ, it, value, want, 1); + body = new_block(L); + next = new_block(L); + fe_ir_br(L->b, same, body->id, next->id); + L->b = body; + lower_stmt(L, arm->a); + fe_ir_jmp(L->b, join->id); + L->b = next; + } + fe_ir_jmp(L->b, join->id); + L->b = join; +} + static void lower_stmt(Lower *L, FeNode *n) { FeNode *x; @@ -1123,6 +1210,9 @@ static void lower_stmt(Lower *L, FeNode *n) case FE_N_FOR: lower_for(L, n); return; + case FE_N_MATCH: + lower_match(L, n); + return; default: fail(L, "this statement", n); return; diff --git a/fec/src/parser.c b/fec/src/parser.c index 27d7f99..3f780bd 100644 --- a/fec/src/parser.c +++ b/fec/src/parser.c @@ -191,9 +191,9 @@ static FeNode *params(FeParser *p) static FeNode *fn_decl(FeParser *p, int pub, int external, int interrupt, int interrupt_safe) { FeToken t=p->current, name; FeNode *n; - (void)external; (void)interrupt; (void)interrupt_safe; + (void)interrupt; (void)interrupt_safe; want(p,FE_TOK_FN,"expected 'fn'"); if(!is_name(p)){error(p,"expected function name");return fe_node(p->ast,FE_N_ERROR_NODE,t.loc,"fn",2);} - name=p->current; n=toknode(p,FE_N_FN,t); if(pub) n->flags|=FE_NODE_PUB; n->text=fe_arena_strdup(&p->ast->arena,name.begin,name.length); next(p); n->a=params(p); if(eat(p,FE_TOK_ARROW)) n->b=type(p); if(eat(p,FE_TOK_SEMI)) return n; n->c=block(p); return n; + name=p->current; n=toknode(p,FE_N_FN,t); if(pub) n->flags|=FE_NODE_PUB; if(external) n->flags|=FE_NODE_EXTERN; n->text=fe_arena_strdup(&p->ast->arena,name.begin,name.length); next(p); n->a=params(p); if(eat(p,FE_TOK_ARROW)) n->b=type(p); if(eat(p,FE_TOK_SEMI)) return n; n->c=block(p); return n; } static FeNode *field(FeParser *p, int pub) { diff --git a/fec/std/fmt.fe b/fec/std/fmt.fe index e63bc0b..dabea45 100644 --- a/fec/std/fmt.fe +++ b/fec/std/fmt.fe @@ -1,6 +1,32 @@ -unit fmt; -pub fn fmt_int_i32(buf: []mut u8, v: i32) -> str; -pub fn fmt_hex_i32(buf: []mut u8, v: i32) -> str; -pub fn fmt_char(buf: []mut u8, v: char) -> str; -pub fn fmt_bool(buf: []mut u8, v: bool) -> str; -pub fn fmt_error(buf: []mut u8, v: core.Error) -> str; +unit std.fmt; + +// Pure conversion. Nothing here owns a sink: the caller supplies the buffer +// and is told how many bytes at the front of it were written (SPEC 10). + +pub fn fmt_u32(buf: []mut u8, v: u32) -> usize { + var tmp: [10]u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + var value: u32 = v; + var count: usize = 0; + while true { + tmp[count] = ((value % 10) as u8) + ('0' as u8); + count = count + 1; + value = value / 10; + if value == 0 { break; } + if count == 10 { break; } + } + var i: usize = 0; + while i < count { + if i < buf.n { buf[i] = tmp[count - 1 - i]; } + i = i + 1; + } + return count; +} + +pub fn fmt_i32(buf: []mut u8, v: i32) -> usize { + if v >= 0 { return fmt_u32(buf, v as u32); } + if buf.n == 0 { return 0; } + buf[0] = '-' as u8; + var rest: []mut u8 = buf[1..buf.n]; + let digits: usize = fmt_u32(rest, (0 - v) as u32); + return digits + 1; +} diff --git a/fec/std/io.fe b/fec/std/io.fe index d7cc685..475b1d8 100644 --- a/fec/std/io.fe +++ b/fec/std/io.fe @@ -1,10 +1,25 @@ -unit io; -pub enum Writer { Stdout, Stderr, File(u16), Null } -pub enum Reader { Stdin, File(u16) } -pub fn write(w: Writer, bytes: []u8) -> !usize; -pub fn read(r: Reader, bytes: []mut u8) -> !usize; -pub struct File { - handle: u16, - pub fn close(self: Self) -> !void; - pub fn drop(self: &mut Self) { } +unit std.io; +import std.sys; + +// A writer is a handle and nothing else: an integer the runtime understands. +// It stores no reference and no context pointer, so it is Copy and can be +// passed and returned freely (SPEC 5 R8). +pub enum Writer { Null, Stdout, Stderr } + +pub fn write(w: Writer, bytes: []u8) -> usize { + if w == Writer.Null { return bytes.n; } + var handle: i32 = 1; + if w == Writer.Stderr { handle = 2; } + let done: i32 = sys.raw_write(handle, &bytes[0], bytes.n); + if done < 0 { return 0; } + return done as usize; +} + +pub fn print(bytes: []u8) -> usize { + return write(Writer.Stdout, bytes); +} + +pub fn println(bytes: []u8) -> usize { + let n: usize = write(Writer.Stdout, bytes); + return n + write(Writer.Stdout, "\n"); } diff --git a/fec/tests/exec/hello.fe b/fec/tests/exec/hello.fe new file mode 100644 index 0000000..e39fcb6 --- /dev/null +++ b/fec/tests/exec/hello.fe @@ -0,0 +1,9 @@ +// EXIT:0 +// OUTPUT:hello from ferro +unit hello; +import std.io; + +fn main() -> i32 { + io.println("hello from ferro"); + return 0; +} diff --git a/tests/run.py b/tests/run.py index 84cd013..f4d3575 100644 --- a/tests/run.py +++ b/tests/run.py @@ -80,7 +80,8 @@ def run_case(fec: Path, path: Path) -> tuple[bool, str]: want = expectation(path) # The grammar fixtures are not all well-typed; stop after parsing. mode = "--dump-ast" if path.parent.name == "parse" else "--check" - done = subprocess.run([str(fec), mode, str(path)], + done = subprocess.run([str(fec), mode, str(path), + f"--std={ROOT / 'fec'}"], capture_output=True, text=True, timeout=30) output = (done.stdout + done.stderr).strip() rejected = done.returncode != 0