diff --git a/SPEC.md b/SPEC.md index 8716434..36253de 100644 --- a/SPEC.md +++ b/SPEC.md @@ -546,6 +546,7 @@ pub fn main() -> !void { - `&mut x`, mutable slice 생성과 `&mut Self` 메서드 호출은 `var` place에서만 가능하다. `let`이 `^T`를 보유해도 그 대상을 안전 코드에서 변경할 수 없다. by-value `self: Self`는 소비 메서드 안에서 자신의 필드를 무효 상태로 바꿀 수 있는 가변 local owner로 취급한다. - 모든 변수는 사용 전 초기화 필수(정적 검사). 명시적 미초기화는 `= undefined`(unsafe 아님, 단 읽기 전 쓰기 필수는 여전히 검사). - 섀도잉 허용(같은 스코프에서 `let` 재선언). +- **전역 `const`/`static`/`var`의 초기값은 컴파일 시점에 알 수 있어야 한다.** 저장소가 이미지에 들어가므로 초기값이 실행될 순간이 없다. 리터럴, 다른 `const`, 열거형 배리언트, `error.Name`, 그리고 그것들에 대한 연산과 캐스트·집합체 리터럴까지가 허용되며 함수 호출은 허용되지 않는다. 실행 시점에 계산해야 하는 값은 `main`에서 만든다. ### 7.2 제어 흐름 diff --git a/TODO.md b/TODO.md index 0db2f06..8cf6d8c 100644 --- a/TODO.md +++ b/TODO.md @@ -56,7 +56,6 @@ uv run python tests/exec.py 38/38 컴파일된 프로그램이 실제로 | 컨테이너 두 원소의 동시 `&mut` | 인덱스는 갈라지지 않는다. `swap` 같은 것은 stdlib 안에서 해결한다 | | `--strip-error-names` | 받아들이지만 아무것도 하지 않는다 (SPEC §4.6) | | `fmt.fmt_error` | 없다. SPEC §4.6 이 약속만 하고 있다 | -| `0b` / `0o` 리터럴 | 렉서는 받지만 값 계산이 10진과 16진만 안다 | --- diff --git a/audits/2026-08-17-frontend-gaps.md b/audits/2026-08-17-frontend-gaps.md index 7be90b3..5312b13 100644 --- a/audits/2026-08-17-frontend-gaps.md +++ b/audits/2026-08-17-frontend-gaps.md @@ -3,6 +3,8 @@ - 날짜: 2026-08-17 - 기준 커밋: `6dc298d828872409fdf6b7d2e85830f18a118d9f` - 범위: parser, checker, 전역 lowering의 경계 +- **해결: 11 건 전부와 `0b`/`0o` 리터럴까지. 모두 구현 쪽이었다.** + fixture 는 아래 표에 적었다. 본문은 조사 시점 그대로다. ## 재현된 문제 @@ -25,6 +27,35 @@ 중복 struct field와 중복 enum variant 선언도 통과했지만, 중복 선언 규칙을 SPEC에서 먼저 확정해야 하므로 위 목록에는 넣지 않았다. +## 해결 + +SPEC 은 FRONT-03·04(§6.2), 05(§5 R9), 06~09(§6.1·§7.3), 10·11(§6.1)을 이미 +옳게 적고 있었다. 구현만 따라가지 않았다. FRONT-01·02 는 SPEC 에도 규칙이 +없어서 §7.1 에 문장을 넣었다 -- 전역 초기값은 컴파일 시점에 알 수 있어야 한다. + +| ID | fixture | +|---|---| +| FRONT-01 | `types/badcini.fe` | +| FRONT-02 | `types/badsini.fe` | +| FRONT-03 | `types/badchain.fe` | +| FRONT-04 | `types/badunas.fe` | +| FRONT-05 | `types/badasm.fe` | +| FRONT-06 | `types/badexns.fe` | +| FRONT-07 | `types/badexab.fe` | +| FRONT-08 | `types/badexbd.fe` | +| FRONT-09 | `types/badfnsm.fe` | +| FRONT-10 | `parse/bademen.fe` | +| FRONT-11 | `parse/bademer.fe` | +| 허용되는 짝 | `types/okglobin.fe` | +| `0b`/`0o` | `exec/radix.fe` | + +`-x as T` 와 비교 체이닝을 구별하려면 괄호가 트리에 남아야 해서 노드에 +`FE_NODE_PAREN` 을 두었다. 파싱 뒤에는 `-x as T` 와 `-(x as T)` 가 같은 +트리다. + +남은 것: `parse/` fixture 가 트리 내용을 비교하지 않는다는 지적은 그대로 +유효하다. 우선순위는 지금 `exec/bitnot.fe` 처럼 실행 결과로 구별한다. + ## 이미 알려진 실행 문제 `0b`와 `0o` 리터럴은 lexer가 받지만 값 계산이 진법을 반영하지 않는다. 실행 결과를 diff --git a/fec/src/ast.h b/fec/src/ast.h index b53221c..f13f657 100644 --- a/fec/src/ast.h +++ b/fec/src/ast.h @@ -47,6 +47,9 @@ struct FeNode { /* An index expression that had `..` in it, so it makes a slice rather than reaching an element. `x[a]` and `x[a..]` are otherwise the same shape. */ #define FE_NODE_SLICE 0x40U +/* This expression was written inside parentheses. `-x as T` is a mistake + and `-(x as T)` is not, and after parsing they are the same tree. */ +#define FE_NODE_PAREN 0x80U typedef struct FeAst { FeArena arena; diff --git a/fec/src/checkcal.c b/fec/src/checkcal.c index b27b096..2347b2e 100644 --- a/fec/src/checkcal.c +++ b/fec/src/checkcal.c @@ -1035,7 +1035,13 @@ void check_stmt(FeCheckerState *s, FeNode *n) err(s->c,n->loc,"break or continue outside loop"); break; case FE_N_UNSAFE: + ++s->unsafe_depth; check_stmt(s,n->a); + --s->unsafe_depth; + break; + case FE_N_ASM: + if (!s->unsafe_depth) + err(s->c,n->loc,"asm requires an unsafe block"); break; default: check_stmt_core(s,n); diff --git a/fec/src/checkgen.c b/fec/src/checkgen.c index e2432df..37522fe 100644 --- a/fec/src/checkgen.c +++ b/fec/src/checkgen.c @@ -413,6 +413,50 @@ FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok) return unknown(c); } +/* Can this initializer be worked out before the program runs? + + A global's bytes go into the image, so there is no moment at which a call in + its initializer could happen -- the emitter had been quietly dropping the + work and leaving zeros. Anything that is a name for a value already known is + fine; anything that is work is not. */ +int const_foldable(FeCheckerState *s, FeNode *n) +{ + FeNode *x; + if (!n) return 1; + switch (n->kind) { + case FE_N_LITERAL: + return 1; + case FE_N_IDENT: { + /* Another `const` is a name for a value; a `static`/`var` is storage + that does not exist yet. */ + FeSym *sym=find_symbol(s->scope,n->text ? n->text : ""); + return sym && sym->decl && sym->decl->kind==FE_N_CONST; + } + case FE_N_MEMBER: + /* `E.Variant`, `error.Name`, `unit.CONST` -- a name, not work. */ + if (n->a && n->a->kind==FE_N_IDENT) return 1; + return const_foldable(s,n->a); + case FE_N_UNARY: + if (n->text && strcmp(n->text,"try")==0) return 0; + return const_foldable(s,n->a); + case FE_N_BINARY: + if (n->text && (strcmp(n->text,"catch")==0 || + strcmp(n->text,"orelse")==0)) return 0; + return const_foldable(s,n->a) && const_foldable(s,n->b); + case FE_N_TYPE: + return const_foldable(s,n->a); + case FE_N_EXPR: + return const_foldable(s,n->a); + case FE_N_STRUCT_INIT: + case FE_N_ARRAY_INIT: + for (x=n->children;x;x=x->next) + if (!const_foldable(s,x->kind==FE_N_FIELD ? x->a : x)) return 0; + return 1; + default: + return 0; + } +} + /* A `comptime if` condition. Only the forms SPEC 9 allows: type equality and the type predicates. Anything else is not decidable here. */ int comptime_condition(FeCheckerState *s, FeNode *n, int *out) diff --git a/fec/src/checkpri.h b/fec/src/checkpri.h index 9e59b2e..5c112a3 100644 --- a/fec/src/checkpri.h +++ b/fec/src/checkpri.h @@ -52,6 +52,8 @@ typedef struct FeCheckerState { FeType *ret; unsigned loop_depth; unsigned defer_depth; + /* SPEC 5 R9 lists what only `unsafe` allows; `asm` is on it. */ + unsigned unsafe_depth; FeOwnLiveness liveness; FeNode *fn_node; /* While a projection is being checked, which field of which base it @@ -212,6 +214,7 @@ FeType *instantiate_struct(FeCheck *c, FeUnit *home, const char *name, FeType *instantiate_type_node(void *owner, const FeNode *node); FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok); int comptime_condition(FeCheckerState *s, FeNode *n, int *out); +int const_foldable(FeCheckerState *s, FeNode *n); void instantiate_body(FeCheck *c, FeUnit *home, FeNode *decl, FeType *owner, FeBindSave *bindings, FeLoc site); FeType *check_generic_call(FeCheckerState *s, FeNode *n, FeSym *sym, diff --git a/fec/src/checkpro.c b/fec/src/checkpro.c index 0d67cd5..e3a5f80 100644 --- a/fec/src/checkpro.c +++ b/fec/src/checkpro.c @@ -131,6 +131,9 @@ void check_unit_bodies(FeCheck *c, FeCheckerState *s) sym=find_current(s->globals,n->text ? n->text : ""); if (n->kind==FE_N_CONST && const_names_type(s,n)) continue; if (n->b) { + if (!const_foldable(s,n->b)) + err(c,n->b->loc, + "a global initializer must be known at compile time"); iv=m7_check_expected(s,n->b,sym ? sym->type : 0); if (sym && sym->type->kind==FE_TYPE_UNKNOWN) { sym->type=iv; @@ -162,6 +165,7 @@ int fe_check_program(FeCheck *c) s.ret=fe_type_intern(&c->types,"void"); s.loop_depth=0; s.defer_depth=0; + s.unsafe_depth=0; s.fn_node=0; fe_own_liveness_init(&s.liveness,&c->arena); for (u=0;ubuild->count;++u) { enter_unit(c,u); declare_unit(c); } diff --git a/fec/src/checkstm.c b/fec/src/checkstm.c index ae79b49..e5f1aa1 100644 --- a/fec/src/checkstm.c +++ b/fec/src/checkstm.c @@ -488,6 +488,10 @@ void check_stmt_core(FeCheckerState *s, FeNode *n) !compatible(s->ret,b,n->a)) err(c, n->loc, "return type mismatch"); break; + case FE_N_ASM: + if (!s->unsafe_depth) + err(c,n->loc,"asm requires an unsafe block"); + break; case FE_N_UNSAFE: check_stmt(s, n->a); break; @@ -508,6 +512,7 @@ void check_fn(FeCheck *c, FeNode *fn, FeScope *globals) s.ret = fn->b ? node_type(c, fn->b) : fe_type_intern(&c->types, "void"); s.loop_depth=0; s.defer_depth=0; + s.unsafe_depth=0; s.fn_node=fn; fe_own_liveness_init(&s.liveness,&c->arena); fe_own_collect_last_uses(&s.liveness,fn); @@ -546,6 +551,7 @@ void check_method(FeCheck *c, FeNode *fn, FeScope *globals, s.ret=fn->b ? method_type(c,fn->b,owner) : fe_type_intern(&c->types,"void"); s.loop_depth=0; s.defer_depth=0; + s.unsafe_depth=0; s.fn_node=fn; fe_own_liveness_init(&s.liveness,&c->arena); fe_own_collect_last_uses(&s.liveness,fn); @@ -572,21 +578,20 @@ int m7_actual_compatible(FeType *want, FeType *got, FeNode *value) static unsigned long literal_magnitude(const char *s) { unsigned long v = 0; + unsigned long base = 10UL; if (!s) return 0; - if (s[0]=='0' && (s[1]=='x' || s[1]=='X')) { - for (s += 2; *s; ++s) { - int d = *s>='0'&&*s<='9' ? *s-'0' : - *s>='a'&&*s<='f' ? *s-'a'+10 : - *s>='A'&&*s<='F' ? *s-'A'+10 : -1; - if (d < 0) { if (*s=='_') continue; break; } - v = v*16UL + (unsigned long)d; - } - return v; - } + if (s[0]=='0' && (s[1]=='x' || s[1]=='X')) { base = 16UL; s += 2; } + else if (s[0]=='0' && (s[1]=='b' || s[1]=='B')) { base = 2UL; s += 2; } + else if (s[0]=='0' && (s[1]=='o' || s[1]=='O')) { base = 8UL; s += 2; } for (; *s; ++s) { + unsigned long d; if (*s=='_') continue; - if (*s<'0' || *s>'9') break; - v = v*10UL + (unsigned long)(*s-'0'); + if (*s>='0' && *s<='9') d = (unsigned long)(*s-'0'); + else if (*s>='a' && *s<='f') d = (unsigned long)(*s-'a'+10); + else if (*s>='A' && *s<='F') d = (unsigned long)(*s-'A'+10); + else break; + if (d >= base) break; + v = v*base + d; } return v; } diff --git a/fec/src/lower.c b/fec/src/lower.c index dbfc439..4953540 100644 --- a/fec/src/lower.c +++ b/fec/src/lower.c @@ -360,20 +360,22 @@ long literal_value(FeNode *n) return (long)(unsigned char)s[1]; } if (*s == '-') { neg = 1; ++s; } - if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { - s += 2; - for (; *s; ++s) { - int d = *s >= '0' && *s <= '9' ? *s - '0' : - *s >= 'a' && *s <= 'f' ? *s - 'a' + 10 : - *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : -1; - if (d < 0) { if (*s == '_') continue; break; } - v = v * 16 + d; - } - } else { + { + /* SPEC 3 spells four radices. Reading `0b1010` as decimal stops at the + `b` and answers zero, which is a number and so goes unnoticed. */ + int base = 10; + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; } + else if (s[0] == '0' && (s[1] == 'b' || s[1] == 'B')) { base = 2; s += 2; } + else if (s[0] == '0' && (s[1] == 'o' || s[1] == 'O')) { base = 8; s += 2; } for (; *s; ++s) { + int d; if (*s == '_') continue; - if (*s < '0' || *s > '9') break; - v = v * 10 + (*s - '0'); + if (*s >= '0' && *s <= '9') d = *s - '0'; + else if (*s >= 'a' && *s <= 'f') d = *s - 'a' + 10; + else if (*s >= 'A' && *s <= 'F') d = *s - 'A' + 10; + else break; + if (d >= base) break; + v = v * base + d; } } return neg ? -v : v; diff --git a/fec/src/parser.c b/fec/src/parser.c index 836babf..23224ba 100644 --- a/fec/src/parser.c +++ b/fec/src/parser.c @@ -71,6 +71,14 @@ static FeNode *type(FeParser *p) error(p,"expected type"); next(p); return fe_node(p->ast,FE_N_TYPE,t.loc,"error",5); } +/* Is this binary node one of the six comparisons? They share a precedence + level and SPEC 6.2 forbids chaining them. The operator is the node's text. */ +static int is_comparison(const char *op) +{ + if(!op) return 0; + return !strcmp(op,"==")||!strcmp(op,"!=")||!strcmp(op,"<")|| + !strcmp(op,"<=")||!strcmp(op,">")||!strcmp(op,">="); +} static int precedence(FeTokKind k) { switch(k) { @@ -107,7 +115,7 @@ static FeNode *primary(FeParser *p) } return n; } - if(eat(p,FE_TOK_LPAREN)) { int old=p->forbid_struct_literal; p->forbid_struct_literal=0; n=expr(p,0); p->forbid_struct_literal=old; want(p,FE_TOK_RPAREN,"expected ')'"); return n; } + if(eat(p,FE_TOK_LPAREN)) { int old=p->forbid_struct_literal; p->forbid_struct_literal=0; n=expr(p,0); p->forbid_struct_literal=old; want(p,FE_TOK_RPAREN,"expected ')'"); if(n) n->flags|=FE_NODE_PAREN; return n; } if(eat(p,FE_TOK_AT)) { FeToken name=p->current; if(!is_name(p)){error(p,"expected builtin name after '@'");return fe_node(p->ast,FE_N_ERROR_NODE,t.loc,"builtin",7);} next(p); n=toknode(p,FE_N_CALL,name); n->text=fe_arena_strdup(&p->ast->arena,name.begin-1,name.length+1); @@ -167,9 +175,17 @@ static FeNode *postfix(FeParser *p) static FeNode *expr(FeParser *p, int minprec) { FeToken t=p->current; FeNode *left,*n; int prec; - if(is(p,FE_TOK_MINUS)||is(p,FE_TOK_NOT)||is(p,FE_TOK_TILDE)||is(p,FE_TOK_XOR)||is(p,FE_TOK_AND)||is(p,FE_TOK_STAR)||is(p,FE_TOK_TRY)) { next(p); n=toknode(p,FE_N_UNARY,t); if(t.kind==FE_TOK_AND && eat(p,FE_TOK_MUT)) n->text=fe_arena_strdup(&p->ast->arena,"&mut",4); n->a=expr(p,11); left=n; } + if(is(p,FE_TOK_MINUS)||is(p,FE_TOK_NOT)||is(p,FE_TOK_TILDE)||is(p,FE_TOK_XOR)||is(p,FE_TOK_AND)||is(p,FE_TOK_STAR)||is(p,FE_TOK_TRY)) { next(p); n=toknode(p,FE_N_UNARY,t); if(t.kind==FE_TOK_AND && eat(p,FE_TOK_MUT)) n->text=fe_arena_strdup(&p->ast->arena,"&mut",4); n->a=expr(p,11); + if(n->a && n->a->kind==FE_N_TYPE && n->a->b && + !(n->a->flags & FE_NODE_PAREN)) + error(p,"parenthesise: '-x as T' is read as -(x as T)"); + left=n; } else left=postfix(p); - for(;;) { t=p->current;prec=precedence(t.kind);if(prec<=minprec)break;next(p);n=toknode(p,FE_N_BINARY,t);n->a=left;if(t.kind==FE_TOK_CATCH && eat(p,FE_TOK_OR)){if(is_name(p))n->b=toknode(p,FE_N_IDENT,p->current),next(p);else error(p,"expected catch binding");want(p,FE_TOK_OR,"expected '|' after catch binding");n->c=block(p);}else n->b=expr(p,prec);left=n; } + for(;;) { t=p->current;prec=precedence(t.kind);if(prec<=minprec)break; + if(prec==4 && left && left->kind==FE_N_BINARY && + !(left->flags & FE_NODE_PAREN) && is_comparison(left->text)) + error(p,"comparisons do not chain; write 'a < b and b < c'"); + next(p);n=toknode(p,FE_N_BINARY,t);n->a=left;if(t.kind==FE_TOK_CATCH && eat(p,FE_TOK_OR)){if(is_name(p))n->b=toknode(p,FE_N_IDENT,p->current),next(p);else error(p,"expected catch binding");want(p,FE_TOK_OR,"expected '|' after catch binding");n->c=block(p);}else n->b=expr(p,prec);left=n; } return left; } @@ -208,7 +224,16 @@ static FeNode *fn_decl(FeParser *p, int pub, int external, int interrupt, int in FeToken t=p->current, name; FeNode *n; (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; 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; + 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)) { + /* A body-less function is a promise that someone else defines it, and + `extern` is how that promise is made. Without it the name is + mangled into this unit and nothing anywhere defines it. */ + if(!external) error(p,"a function without a body must be extern"); + return n; + } + if(external) error(p,"an extern function has no body"); + n->c=block(p); return n; } static FeNode *field(FeParser *p, int pub) { @@ -220,15 +245,25 @@ static FeNode *decl(FeParser *p) int pub=0, external=0, interrupt=0, interrupt_safe=0, shared=0, atomic=0; FeToken t=p->current; FeNode *n; FeTokKind before; (void)shared; (void)atomic; if(eat(p,FE_TOK_PUB)) pub=1; - if(eat(p,FE_TOK_EXTERN)) { external=1; if(is(p,FE_TOK_STRING)) next(p); } + if(eat(p,FE_TOK_EXTERN)) { + external=1; + if(!is(p,FE_TOK_STRING)) error(p,"extern requires an ABI string"); + else { + /* The token keeps its quotes, so "c" is four characters. */ + FeToken abi=p->current; + if(abi.length!=3 || abi.begin[1]!='c') + error(p,"the only ABI is \"c\""); + next(p); + } + } if(eat(p,FE_TOK_INTERRUPT)) interrupt=1; if(eat(p,FE_TOK_INTERRUPT_SAFE)) interrupt_safe=1; if(!is(p,FE_TOK_PACKED)) t=p->current; if(is(p,FE_TOK_FN)) return fn_decl(p,pub,external,interrupt,interrupt_safe); if(eat(p,FE_TOK_PACKED)) t=p->previous; if(eat(p,FE_TOK_STRUCT)) { n=toknode(p,FE_N_STRUCT,t);if(pub)n->flags|=FE_NODE_PUB;if(t.kind==FE_TOK_PACKED)n->flags|=FE_NODE_PACKED;if(!is_name(p)){error(p,"expected struct name");return n;}next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);if(eat(p,FE_TOK_LPAREN)){n->a=fe_node(p->ast,FE_N_BLOCK,p->current.loc,"generics",8);while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(n->a,type(p));if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RPAREN,"expected ')' after generic parameters");}want(p,FE_TOK_LBRACE,"expected '{' in struct");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){int mpub=eat(p,FE_TOK_PUB);if(is(p,FE_TOK_FN))fe_node_add(n,fn_decl(p,mpub,0,0,0));else fe_node_add(n,field(p,mpub));}want(p,FE_TOK_RBRACE,"expected '}' after struct");return n; } - if(eat(p,FE_TOK_ENUM)) { n=toknode(p,FE_N_ENUM,t);if(pub)n->flags|=FE_NODE_PUB;if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected enum name");if(eat(p,FE_TOK_LPAREN)){n->a=fe_node(p->ast,FE_N_BLOCK,p->current.loc,"generics",8);while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(n->a,type(p));if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RPAREN,"expected ')' after generic parameters");}want(p,FE_TOK_LBRACE,"expected '{' in enum");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *v=toknode(p,FE_N_VARIANT,p->current);if(is_name(p))next(p);else{error(p,"expected variant name");recover(p);break;}if(eat(p,FE_TOK_LPAREN)){v->a=type(p);want(p,FE_TOK_RPAREN,"expected ')' in variant");}else if(eat(p,FE_TOK_LBRACE)){while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF))fe_node_add(v,field(p,1));want(p,FE_TOK_RBRACE,"expected '}' in variant");}fe_node_add(n,v);if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RBRACE,"expected '}' after enum");return n; } - if(eat(p,FE_TOK_ERROR_KW)) { n=toknode(p,FE_N_ERROR_DECL,t);if(pub)n->flags|=FE_NODE_PUB;if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected error name");want(p,FE_TOK_LBRACE,"expected '{' in error declaration");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *v=toknode(p,FE_N_VARIANT,p->current);if(is_name(p))next(p);else{error(p,"expected error member");recover(p);break;}want(p,FE_TOK_EQ,"expected '=' in error member");if(is(p,FE_TOK_INT)){v->a=toknode(p,FE_N_LITERAL,p->current);next(p);}else error(p,"an error code must be an integer literal");if(!is(p,FE_TOK_COMMA)&&!is(p,FE_TOK_RBRACE)){error(p,"an error code must be an integer literal");recover(p);break;}want(p,FE_TOK_COMMA,"expected ',' in error declaration");fe_node_add(n,v);}want(p,FE_TOK_RBRACE,"expected '}' after error");return n; } + if(eat(p,FE_TOK_ENUM)) { n=toknode(p,FE_N_ENUM,t);if(pub)n->flags|=FE_NODE_PUB;if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected enum name");if(eat(p,FE_TOK_LPAREN)){n->a=fe_node(p->ast,FE_N_BLOCK,p->current.loc,"generics",8);while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(n->a,type(p));if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RPAREN,"expected ')' after generic parameters");}want(p,FE_TOK_LBRACE,"expected '{' in enum");if(is(p,FE_TOK_RBRACE))error(p,"an enum needs at least one variant");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *v=toknode(p,FE_N_VARIANT,p->current);if(is_name(p))next(p);else{error(p,"expected variant name");recover(p);break;}if(eat(p,FE_TOK_LPAREN)){v->a=type(p);want(p,FE_TOK_RPAREN,"expected ')' in variant");}else if(eat(p,FE_TOK_LBRACE)){while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF))fe_node_add(v,field(p,1));want(p,FE_TOK_RBRACE,"expected '}' in variant");}fe_node_add(n,v);if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RBRACE,"expected '}' after enum");return n; } + if(eat(p,FE_TOK_ERROR_KW)) { n=toknode(p,FE_N_ERROR_DECL,t);if(pub)n->flags|=FE_NODE_PUB;if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected error name");want(p,FE_TOK_LBRACE,"expected '{' in error declaration");if(is(p,FE_TOK_RBRACE))error(p,"an error declaration needs at least one member");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *v=toknode(p,FE_N_VARIANT,p->current);if(is_name(p))next(p);else{error(p,"expected error member");recover(p);break;}want(p,FE_TOK_EQ,"expected '=' in error member");if(is(p,FE_TOK_INT)){v->a=toknode(p,FE_N_LITERAL,p->current);next(p);}else error(p,"an error code must be an integer literal");if(!is(p,FE_TOK_COMMA)&&!is(p,FE_TOK_RBRACE)){error(p,"an error code must be an integer literal");recover(p);break;}want(p,FE_TOK_COMMA,"expected ',' in error declaration");fe_node_add(n,v);}want(p,FE_TOK_RBRACE,"expected '}' after error");return n; } if(eat(p,FE_TOK_SHARED)) { shared=1; if(eat(p,FE_TOK_ATOMIC)) atomic=1; if(!is(p,FE_TOK_VAR)) error(p,"expected 'var' after shared"); } if(is(p,FE_TOK_CONST)||is(p,FE_TOK_STATIC)||is(p,FE_TOK_VAR)) { FeTokKind kk=p->current.kind;next(p);n=toknode(p,kk==FE_TOK_CONST?FE_N_CONST:FE_N_GLOBAL,t);if(pub)n->flags|=FE_NODE_PUB;if(kk==FE_TOK_STATIC)n->flags|=FE_NODE_STATIC;if(shared)n->flags|=FE_NODE_SHARED;if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected declaration name");if(eat(p,FE_TOK_COLON))n->a=type(p);else if(kk!=FE_TOK_CONST)error(p,"a global declaration requires an explicit type");want(p,FE_TOK_EQ,"expected '=' in declaration");n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';' after declaration");return n; } error(p,"expected declaration"); before=p->current.kind; recover(p); diff --git a/fec/tests/exec/radix.fe b/fec/tests/exec/radix.fe new file mode 100644 index 0000000..378e5e1 --- /dev/null +++ b/fec/tests/exec/radix.fe @@ -0,0 +1,29 @@ +// EXIT:0 +// OUTPUT:bin 10 255 0 +// OUTPUT:oct 15 511 8 +// OUTPUT:hex 255 4095 16 +// OUTPUT:dec 1000 1000000 +// OUTPUT:same yes yes +unit radix; + +import std.io; + +// SPEC §3 spells four radices. Reading `0b1010` as decimal stops at the `b` +// and answers zero -- which is a number, so nothing looks wrong until the +// program does the wrong thing. + +fn main() -> i32 { + @print("bin {} {} {}\n", 0b1010, 0b11111111, 0b0); + @print("oct {} {} {}\n", 0o17, 0o777, 0o10); + @print("hex {} {} {}\n", 0xFF, 0xfff, 0x10); + @print("dec {} {}\n", 1_000, 1_000_000); + + // The same number four ways. + @print("same {} {}\n", yesno(0b1111 == 0o17), yesno(0o17 == 0xF)); + return 0; +} + +fn yesno(b: bool) -> []u8 { + if b { return "yes"; } + return "no"; +} diff --git a/fec/tests/parse/bademen.fe b/fec/tests/parse/bademen.fe new file mode 100644 index 0000000..9c72977 --- /dev/null +++ b/fec/tests/parse/bademen.fe @@ -0,0 +1,3 @@ +// ERROR:3:at least one variant +unit bademen; +enum E { } diff --git a/fec/tests/parse/bademer.fe b/fec/tests/parse/bademer.fe new file mode 100644 index 0000000..a690859 --- /dev/null +++ b/fec/tests/parse/bademer.fe @@ -0,0 +1,3 @@ +// ERROR:3:at least one member +unit bademer; +error E { } diff --git a/fec/tests/types/badasm.fe b/fec/tests/types/badasm.fe new file mode 100644 index 0000000..9cb70e4 --- /dev/null +++ b/fec/tests/types/badasm.fe @@ -0,0 +1,3 @@ +// ERROR:3:unsafe block +unit badasm; +fn f() -> void { asm { "nop" } return; } diff --git a/fec/tests/types/badchain.fe b/fec/tests/types/badchain.fe new file mode 100644 index 0000000..9aea447 --- /dev/null +++ b/fec/tests/types/badchain.fe @@ -0,0 +1,3 @@ +// ERROR:3:do not chain +unit badchain; +fn f() -> bool { return true == false == true; } diff --git a/fec/tests/types/badcini.fe b/fec/tests/types/badcini.fe new file mode 100644 index 0000000..c52a9dd --- /dev/null +++ b/fec/tests/types/badcini.fe @@ -0,0 +1,4 @@ +// ERROR:4:known at compile time +unit badcini; +fn r() -> i32 { return 1; } +const A: i32 = r(); diff --git a/fec/tests/types/badexab.fe b/fec/tests/types/badexab.fe new file mode 100644 index 0000000..1ab2d2a --- /dev/null +++ b/fec/tests/types/badexab.fe @@ -0,0 +1,3 @@ +// ERROR:3:only ABI +unit badexab; +extern "stdcall" fn f(); diff --git a/fec/tests/types/badexbd.fe b/fec/tests/types/badexbd.fe new file mode 100644 index 0000000..06252f5 --- /dev/null +++ b/fec/tests/types/badexbd.fe @@ -0,0 +1,3 @@ +// ERROR:3:no body +unit badexbd; +extern "c" fn f() -> i32 { return 1; } diff --git a/fec/tests/types/badexns.fe b/fec/tests/types/badexns.fe new file mode 100644 index 0000000..b51ce2f --- /dev/null +++ b/fec/tests/types/badexns.fe @@ -0,0 +1,3 @@ +// ERROR:3:ABI string +unit badexns; +extern fn f(); diff --git a/fec/tests/types/badfnsm.fe b/fec/tests/types/badfnsm.fe new file mode 100644 index 0000000..cd3c37a --- /dev/null +++ b/fec/tests/types/badfnsm.fe @@ -0,0 +1,3 @@ +// ERROR:4:must be extern +unit badfnsm; +fn f() -> i32; diff --git a/fec/tests/types/badsini.fe b/fec/tests/types/badsini.fe new file mode 100644 index 0000000..c585800 --- /dev/null +++ b/fec/tests/types/badsini.fe @@ -0,0 +1,4 @@ +// ERROR:4:known at compile time +unit badsini; +fn r() -> i32 { return 1; } +static A: i32 = r(); diff --git a/fec/tests/types/badunas.fe b/fec/tests/types/badunas.fe new file mode 100644 index 0000000..849ef40 --- /dev/null +++ b/fec/tests/types/badunas.fe @@ -0,0 +1,3 @@ +// ERROR:3:parenthesise +unit badunas; +fn f(x: i32) -> u32 { return -x as u32; } diff --git a/fec/tests/types/okglobin.fe b/fec/tests/types/okglobin.fe new file mode 100644 index 0000000..9549013 --- /dev/null +++ b/fec/tests/types/okglobin.fe @@ -0,0 +1,23 @@ +unit okglobin; + +// 거부되는 것들의 짝. 전역 초기값은 컴파일 시점에 알 수 있어야 하고, +// extern 은 ABI 를 대고 본문이 없으며, 비교는 괄호로 묶으면 이어 쓸 수 있고, +// 단항 뒤의 as 는 괄호가 어느 쪽인지 말해주면 되고, asm 은 unsafe 안이면 된다. +import std.sys; + +const A: i32 = 1; +const B: i32 = A + 2; +const S: str = "hi"; +static C: i32 = -A; +var D: u32 = 0xFF; + +enum E { One, Two, } +error Er { Bad = 1, } + +extern "c" fn fe_rt_allocs() -> i32; + +fn f(x: i32) -> u32 { return (-x) as u32; } +fn g(x: i32) -> u32 { return -(x as u32); } +fn h(a: i32, b: i32, c: i32) -> bool { return a < b and b < c; } +fn i(a: i32, b: i32) -> bool { return (a == b) == true; } +fn j() -> void { unsafe { asm { "nop" } } return; }