audit: 프런트엔드 빈틈 열하나와 진법 리터럴

전부 구현 쪽이었다. SPEC 은 §6.2(체이닝·단항 뒤 as), §5 R9(asm), §6.1·§7.3
(extern, 빈 enum/error)을 이미 옳게 적고 있었고 구현만 따라가지 않았다.

  const A: i32 = r();        전역 초기값에 호출. lowering 이 조용히 버리고 0
  static A: i32 = r();       같은 것을 저장소 0 으로
  true == false == true      비교 체이닝
  -x as u32                  괄호 없이 단항 뒤 as
  asm { }                    unsafe 밖에서
  extern fn f();             ABI 문자열 없이
  extern "stdcall" fn f();   c 아닌 ABI
  extern "c" fn f() { }      extern 에 본문
  fn f() -> i32;             extern 아닌데 본문 없음
  enum E { }  error E { }    빈 선언

FRONT-01/02 는 SPEC 에도 규칙이 없었다. §7.1 에 넣었다 -- 전역의 바이트는
이미지에 들어가므로 초기값이 실행될 순간이 없다. 리터럴과 다른 const, 배리언트,
error.Name, 그리고 그것들에 대한 연산까지가 허용된다.

-x as T 와 비교 체이닝을 잡으려면 괄호가 트리에 남아야 해서 FE_NODE_PAREN 을
두었다. 파싱 뒤에는 -x as T 와 -(x as T) 가 같은 트리다.

그리고 0b1010 이 0, 0o17 이 0 이었다. 10진으로 읽다가 b 에서 멈춰 0 을 내는데
0 도 숫자라 아무도 눈치채지 못한다. 값 계산 두 군데를 고쳤다.

262/262, 40/40.
This commit is contained in:
2026-08-17 17:00:29 +09:00
parent c7a1b98654
commit b4f947b643
24 changed files with 252 additions and 32 deletions
+1
View File
@@ -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 제어 흐름
-1
View File
@@ -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진만 안다 |
---
+31
View File
@@ -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가 받지만 값 계산이 진법을 반영하지 않는다. 실행 결과를
+3
View File
@@ -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;
+6
View File
@@ -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);
+44
View File
@@ -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)
+3
View File
@@ -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,
+4
View File
@@ -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;u<c->build->count;++u) { enter_unit(c,u); declare_unit(c); }
+17 -12
View File
@@ -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;
}
+14 -12
View File
@@ -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;
+42 -7
View File
@@ -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);
+29
View File
@@ -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";
}
+3
View File
@@ -0,0 +1,3 @@
// ERROR:3:at least one variant
unit bademen;
enum E { }
+3
View File
@@ -0,0 +1,3 @@
// ERROR:3:at least one member
unit bademer;
error E { }
+3
View File
@@ -0,0 +1,3 @@
// ERROR:3:unsafe block
unit badasm;
fn f() -> void { asm { "nop" } return; }
+3
View File
@@ -0,0 +1,3 @@
// ERROR:3:do not chain
unit badchain;
fn f() -> bool { return true == false == true; }
+4
View File
@@ -0,0 +1,4 @@
// ERROR:4:known at compile time
unit badcini;
fn r() -> i32 { return 1; }
const A: i32 = r();
+3
View File
@@ -0,0 +1,3 @@
// ERROR:3:only ABI
unit badexab;
extern "stdcall" fn f();
+3
View File
@@ -0,0 +1,3 @@
// ERROR:3:no body
unit badexbd;
extern "c" fn f() -> i32 { return 1; }
+3
View File
@@ -0,0 +1,3 @@
// ERROR:3:ABI string
unit badexns;
extern fn f();
+3
View File
@@ -0,0 +1,3 @@
// ERROR:4:must be extern
unit badfnsm;
fn f() -> i32;
+4
View File
@@ -0,0 +1,4 @@
// ERROR:4:known at compile time
unit badsini;
fn r() -> i32 { return 1; }
static A: i32 = r();
+3
View File
@@ -0,0 +1,3 @@
// ERROR:3:parenthesise
unit badunas;
fn f(x: i32) -> u32 { return -x as u32; }
+23
View File
@@ -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; }