std: io 와 fmt 를 Ferro 로 쓰고, 컴파일된 프로그램이 출력한다
io.Writer 는 핸들 하나짜리 enum 이다. 참조도 컨텍스트 포인터도 담지 않으므로 Copy 이고 자유롭게 오간다 (SPEC 5 R8). fmt 는 sink 를 소유하지 않는다 -- 호출자가 버퍼를 주고 앞에서 몇 바이트가 쓰였는지 돌려받는다. lowering 에 추가: enum 변이 상수, match, 정수 캐스트, 문자열 이스케이프. 프론트엔드 정밀도 하나: 항상 빠져나가는 분기의 상태를 병합하지 않는다. 그 분기가 소비한 값이 그 분기를 지나지 않은 경로에서도 소비된 것처럼 보였다. fmt_i32 가 이것 때문에 못 쓰였다. extern "c" 이름은 유닛 접두사를 붙이지 않는다. 링커가 이미 아는 이름이라는 것이 그 선언의 요점이다. run.py 199/199, exec.py 11/11.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;i<count;++i) left[i]=base[i];
|
||||
if (own_left && own_base)
|
||||
for (i=0;i<count;++i) own_left[i]=own_base[i];
|
||||
if (borrow_left && borrow_base)
|
||||
for (i=0;i<count;++i) borrow_left[i]=borrow_base[i];
|
||||
}
|
||||
m7_restore_flow(base,own_base,borrow_base,count);
|
||||
if (n->c) 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;i<count;++i) right[i]=base[i];
|
||||
if (own_right && own_base)
|
||||
for (i=0;i<count;++i) own_right[i]=own_base[i];
|
||||
if (borrow_right && borrow_base)
|
||||
for (i=0;i<count;++i) borrow_right[i]=borrow_base[i];
|
||||
}
|
||||
} else {
|
||||
own_right=flow_own_new(s,count);
|
||||
borrow_right=flow_borrow_new(s,count);
|
||||
@@ -3698,7 +3715,10 @@ static FeScope *declare_unit_scope(FeCheck *c, FeCheckerState *s)
|
||||
for (n=c->ast->root ? c->ast->root->children : 0;n;n=n->next)
|
||||
if (n->kind==FE_N_FN) {
|
||||
t=fe_type_intern(&c->types,"<fn>");
|
||||
/* `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;
|
||||
|
||||
+94
-4
@@ -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;
|
||||
|
||||
+2
-2
@@ -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)
|
||||
{
|
||||
|
||||
+32
-6
@@ -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;
|
||||
}
|
||||
|
||||
+24
-9
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user