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:
@@ -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 제어 흐름
|
||||
|
||||
|
||||
@@ -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진만 안다 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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가 받지만 값 계산이 진법을 반영하지 않는다. 실행 결과를
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// ERROR:3:at least one variant
|
||||
unit bademen;
|
||||
enum E { }
|
||||
@@ -0,0 +1,3 @@
|
||||
// ERROR:3:at least one member
|
||||
unit bademer;
|
||||
error E { }
|
||||
@@ -0,0 +1,3 @@
|
||||
// ERROR:3:unsafe block
|
||||
unit badasm;
|
||||
fn f() -> void { asm { "nop" } return; }
|
||||
@@ -0,0 +1,3 @@
|
||||
// ERROR:3:do not chain
|
||||
unit badchain;
|
||||
fn f() -> bool { return true == false == true; }
|
||||
@@ -0,0 +1,4 @@
|
||||
// ERROR:4:known at compile time
|
||||
unit badcini;
|
||||
fn r() -> i32 { return 1; }
|
||||
const A: i32 = r();
|
||||
@@ -0,0 +1,3 @@
|
||||
// ERROR:3:only ABI
|
||||
unit badexab;
|
||||
extern "stdcall" fn f();
|
||||
@@ -0,0 +1,3 @@
|
||||
// ERROR:3:no body
|
||||
unit badexbd;
|
||||
extern "c" fn f() -> i32 { return 1; }
|
||||
@@ -0,0 +1,3 @@
|
||||
// ERROR:3:ABI string
|
||||
unit badexns;
|
||||
extern fn f();
|
||||
@@ -0,0 +1,3 @@
|
||||
// ERROR:4:must be extern
|
||||
unit badfnsm;
|
||||
fn f() -> i32;
|
||||
@@ -0,0 +1,4 @@
|
||||
// ERROR:4:known at compile time
|
||||
unit badsini;
|
||||
fn r() -> i32 { return 1; }
|
||||
static A: i32 = r();
|
||||
@@ -0,0 +1,3 @@
|
||||
// ERROR:3:parenthesise
|
||||
unit badunas;
|
||||
fn f(x: i32) -> u32 { return -x as u32; }
|
||||
@@ -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; }
|
||||
Reference in New Issue
Block a user