From 53bca214b82c3abf7882ad41fb177cc488e999af Mon Sep 17 00:00:00 2001 From: Sebastian Jeong Date: Sun, 16 Aug 2026 13:24:25 +0900 Subject: [PATCH] feat: implement M4 formatting builtins --- fec/src/check.c | 188 ++++++++++++++++++++- fec/src/emit_c.c | 316 ++++++++++++++++++++++++++++++++++-- fec/src/emit_c.h | 1 + fec/src/parser.c | 13 +- fec/src/types.c | 53 +++++- fec/src/types.h | 7 +- fec/test-dos.bat | 39 +++++ fec/tests/m4/bad-arity.fe | 6 + fec/tests/m4/bad-cls.fe | 6 + fec/tests/m4/bad-many.fe | 6 + fec/tests/m4/bad-open.fe | 6 + fec/tests/m4/bad-runtime.fe | 7 + fec/tests/m4/bad-try.fe | 6 + fec/tests/m4/bad-type.fe | 9 + fec/tests/m4/bad-verb.fe | 6 + fec/tests/m4/bad-writer.fe | 7 + fec/tests/m4/format.fe | 30 ++++ fec/tests/m4/prop.fe | 5 + fec/tests/m4/proptest.c | 23 +++ fec/tests/m4/try-fprint.fe | 8 + fec/tests/run-tests.sh | 30 ++++ fec/vm-m1.bat | 28 ++++ 22 files changed, 779 insertions(+), 21 deletions(-) create mode 100644 fec/tests/m4/bad-arity.fe create mode 100644 fec/tests/m4/bad-cls.fe create mode 100644 fec/tests/m4/bad-many.fe create mode 100644 fec/tests/m4/bad-open.fe create mode 100644 fec/tests/m4/bad-runtime.fe create mode 100644 fec/tests/m4/bad-try.fe create mode 100644 fec/tests/m4/bad-type.fe create mode 100644 fec/tests/m4/bad-verb.fe create mode 100644 fec/tests/m4/bad-writer.fe create mode 100644 fec/tests/m4/format.fe create mode 100644 fec/tests/m4/prop.fe create mode 100644 fec/tests/m4/proptest.c create mode 100644 fec/tests/m4/try-fprint.fe diff --git a/fec/src/check.c b/fec/src/check.c index 7714945..9563b18 100644 --- a/fec/src/check.c +++ b/fec/src/check.c @@ -12,6 +12,7 @@ struct FeSym { FeNode *fn; int mutable; int initialized; + FeNode *decl; }; struct FeScope { @@ -192,6 +193,7 @@ static FeSym *add_symbol(FeCheckerState *s, FeScope *scope, sym->fn = fn; sym->mutable = mutable; sym->initialized = initialized; + sym->decl = decl; if (decl) { decl->cname = cname; decl->sem_type = type; @@ -215,6 +217,128 @@ static FeType *check_expr(FeCheckerState *s, FeNode *n); static void check_match(FeCheckerState *s, FeNode *n); static void check_stmt(FeCheckerState *s, FeNode *n); +static FeNode *find_const_node(FeCheck *c, const char *name) +{ + FeNode *n; + for (n=c->ast->root ? c->ast->root->children : 0; n; n=n->next) + if (n->kind==FE_N_CONST && n->text && name && strcmp(n->text,name)==0) + return n; + return 0; +} + +static const char *builtin_format(FeCheckerState *s, FeNode *fmt) +{ + FeNode *decl; + FeSym *sym; + if (fmt && fmt->kind==FE_N_LITERAL && fmt->text && fmt->text[0]=='"') + return fmt->text; + if (fmt && fmt->kind==FE_N_IDENT) { + sym=find_symbol(s->scope,fmt->text); + decl=sym && sym->decl && sym->decl->kind==FE_N_CONST ? + sym->decl : find_const_node(s->c,fmt->text); + if (decl && decl->b && decl->b->kind==FE_N_LITERAL && + decl->b->text && decl->b->text[0]=='"') { + if (!decl->a || fe_type_from_ast(&s->c->types,decl->a)->kind==FE_TYPE_STR) + return decl->b->text; + } + } + return 0; +} + +static int format_is_slice_u8(FeType *t) +{ + return t && t->kind==FE_TYPE_SLICE && t->elem && + t->elem->kind==FE_TYPE_INT && strcmp(t->elem->name,"u8")==0; +} + +static int format_is_writer_type(FeType *t) +{ + return t && t->kind==FE_TYPE_STRUCT && + (strcmp(t->name,"Writer")==0 || strcmp(t->name,"io.Writer")==0); +} + +static int format_arg_ok(FeType *t, int verb) +{ + if (!t) return 0; + if (verb=='x') return fe_type_is_integer(t); + if (verb=='c') return t->kind==FE_TYPE_CHAR; + if (verb=='s') return t->kind==FE_TYPE_STR || format_is_slice_u8(t); + if (verb=='b') return t->kind==FE_TYPE_BOOL; + if (t->kind==FE_TYPE_INT || t->kind==FE_TYPE_BOOL || + t->kind==FE_TYPE_CHAR || t->kind==FE_TYPE_STR) return 1; + return format_is_slice_u8(t) || + (t->kind==FE_TYPE_ENUM && t->is_error); +} + +static void check_format_call(FeCheckerState *s, FeNode *n) +{ + const char *fmt; + FeNode *fmt_node; + FeNode *arg; + FeNode *x; + FeType *t; + unsigned long i,j; + unsigned count=0; + unsigned argc=0; + unsigned offset=0; + int verb; + int bad=0; + if (strcmp(n->text,"@fprint")==0) offset=1; + fmt_node=n->children; + if (offset) { + if (!fmt_node) { err(s->c,n->loc,"@fprint requires a writer"); return; } + t=check_expr(s,fmt_node); + if (!(t && t->kind==FE_TYPE_REF && t->ref_mut && + format_is_writer_type(t->elem))) + err(s->c,fmt_node->loc,"@fprint requires &mut io.Writer"); + fmt_node=fmt_node->next; + } + if (strcmp(n->text,"@sprint")==0) { + if (!fmt_node) { err(s->c,n->loc,"@sprint requires a buffer"); return; } + t=check_expr(s,fmt_node); + if (!format_is_slice_u8(t)) err(s->c,fmt_node->loc,"@sprint requires []u8 buffer"); + fmt_node=fmt_node->next; + } + fmt=builtin_format(s,fmt_node); + if (!fmt) { err(s->c,n->loc,"format must be a comptime string"); return; } + n->aux_text=(char *)fmt; + arg=fmt_node ? fmt_node->next : 0; + for (x=arg;x;x=x->next) { check_expr(s,x); ++argc; } + i=1; + while (fmt[i] && fmt[i]!='"') { + if (fmt[i]=='\\') { if (fmt[i+1]) ++i; ++i; continue; } + if (fmt[i]=='{' && fmt[i+1]=='{') { i+=2; continue; } + if (fmt[i]=='}' && fmt[i+1]=='}') { i+=2; continue; } + if (fmt[i]=='{') { + j=i+1; + while (fmt[j] && fmt[j]!='}') ++j; + if (!fmt[j]) { err(s->c,n->loc,"unterminated format placeholder"); bad=1; break; } + if (j==i+1) verb=' '; else if (j==i+2) verb=(unsigned char)fmt[i+1]; else verb='?'; + if (verb!=' ' && verb!='x' && verb!='c' && verb!='s' && verb!='b') { + err(s->c,n->loc,"unsupported format verb"); bad=1; + } + if (!arg) { err(s->c,n->loc,"format argument count mismatch"); bad=1; } + else { + t=arg->sem_type; + if (verb==' ' && t && t->kind==FE_TYPE_ENUM && t->is_error) verb='s'; + if (!format_arg_ok(t,verb)) { err(s->c,arg->loc,"no fmt writer for argument type"); bad=1; } + arg=arg->next; + } + ++count; i=j+1; continue; + } + if (fmt[i]=='}') { err(s->c,n->loc,"unmatched '}' in format"); bad=1; } + ++i; + } + if (count!=argc) { err(s->c,n->loc,"format argument count mismatch"); bad=1; } + (void)bad; +} + +static int is_format_builtin(const char *name) +{ + return name && (strcmp(name,"@print")==0 || strcmp(name,"@fprint")==0 || + strcmp(name,"@sprint")==0); +} + static int lvalue_writable(FeCheckerState *s, FeNode *n) { FeSym *sym; @@ -292,12 +416,21 @@ static FeType *check_array_init(FeCheckerState *s, FeNode *n) n->sem_type=fe_type_array(&s->c->types,count,elem); return n->sem_type; } +static int array_slice_lvalue(FeNode *n) +{ + return n && (n->kind==FE_N_IDENT || n->kind==FE_N_MEMBER || + n->kind==FE_N_INDEX); +} + static FeType *check_index(FeCheckerState *s, FeNode *n) { FeType *base=check_expr(s,n->a); FeType *idx; FeType *elem; if(!fe_type_is_indexable(base)) { err(s->c,n->loc,"indexing requires an array or slice"); return unknown(s->c); } if(n->b) { idx=check_expr(s,n->b); if(known(idx)&&!fe_type_is_integer(idx)) err(s->c,n->loc,"index must be an integer"); } - if(n->c || !n->b) { if(n->c) { idx=check_expr(s,n->c); if(known(idx)&&!fe_type_is_integer(idx)) err(s->c,n->loc,"slice bound must be an integer"); } elem=base->elem; n->sem_type=fe_type_slice(&s->c->types,elem); if(base->kind==FE_TYPE_STR) n->flags|=2U; return n->sem_type; } + if(n->c || !n->b) { + if (base->kind==FE_TYPE_ARRAY && !array_slice_lvalue(n->a)) + err(s->c,n->loc,"array slicing requires a stable lvalue"); + if(n->c) { idx=check_expr(s,n->c); if(known(idx)&&!fe_type_is_integer(idx)) err(s->c,n->loc,"slice bound must be an integer"); } elem=base->elem; n->sem_type=fe_type_slice(&s->c->types,elem); if(base->kind==FE_TYPE_STR) n->flags|=2U; return n->sem_type; } n->sem_type=base->elem; return n->sem_type; } @@ -361,6 +494,15 @@ static FeType *check_expr(FeCheckerState *s, FeNode *n) } else if (strcmp(op, "-") == 0) { if (known(a) && !fe_type_is_integer(a)) err(c, n->loc, "unary '-' requires integer"); + } else if (strcmp(op, "try") == 0) { + if (a && a->kind==FE_TYPE_ERROR_UNION) + a=a->error_value; + else { + err(c,n->loc,"try requires an error result"); + a=unknown(c); + } + } else if (strcmp(op,"&")==0 || strcmp(op,"&mut")==0) { + a=fe_type_ref(&c->types,a,strcmp(op,"&mut")==0); } n->sem_type = a; return a; @@ -403,6 +545,34 @@ static FeType *check_expr(FeCheckerState *s, FeNode *n) return a; } if (n->kind == FE_N_CALL) { + if (n->a && n->a->kind==FE_N_MEMBER && n->a->a && + n->a->a->kind==FE_N_IDENT && n->a->a->text && + strcmp(n->a->a->text,"io")==0 && n->a->b && n->a->b->text && + (strcmp(n->a->b->text,"buf_writer")==0 || + strcmp(n->a->b->text,"null_writer")==0)) { + FeNode *arg=n->children; + if (strcmp(n->a->b->text,"buf_writer")==0) { + if (!arg) err(c,n->loc,"io.buf_writer requires a buffer"); + else { + a=check_expr(s,arg); + if (!(a && a->kind==FE_TYPE_REF && a->ref_mut && + format_is_slice_u8(a->elem))) + err(c,arg->loc,"io.buf_writer requires &mut []u8 buffer"); + } + } else if (arg) err(c,n->loc,"io.null_writer takes no arguments"); + n->sem_type=fe_type_intern(&c->types,"io.Writer"); + return n->sem_type; + } + if (n->text && is_format_builtin(n->text)) { + check_format_call(s,n); + if (strcmp(n->text,"@print")==0) + n->sem_type=fe_type_intern(&c->types,"void"); + else if (strcmp(n->text,"@sprint")==0) + n->sem_type=fe_type_intern(&c->types,"usize"); + else + n->sem_type=fe_type_error_union(&c->types,fe_type_intern(&c->types,"void")); + return n->sem_type; + } if (!n->a && n->text && (strcmp(n->text,"@size_of")==0 || strcmp(n->text,"@align_of")==0)) { FeNode *type_arg=n->children; FeType *target=type_arg && type_arg->kind==FE_N_IDENT ? fe_type_intern(&c->types,type_arg->text) : unknown(c); @@ -452,6 +622,12 @@ static FeType *check_expr(FeCheckerState *s, FeNode *n) return unknown(c); } if (n->kind == FE_N_MEMBER) { + if (n->a && n->a->kind==FE_N_IDENT && n->a->text && + strcmp(n->a->text,"io")==0 && n->b && n->b->text && + strcmp(n->b->text,"stdout")==0) { + n->sem_type=fe_type_intern(&c->types,"io.Writer"); + return n->sem_type; + } a=check_expr(s,n->a); if (a->kind == FE_TYPE_REF && n->b && n->b->text && strcmp(n->b->text,"^")==0) { @@ -645,6 +821,10 @@ static void check_type_cycle(FeCheck *c, FeType *t) t->kind == FE_TYPE_INT || t->kind == FE_TYPE_BOOL || t->kind == FE_TYPE_CHAR || t->kind == FE_TYPE_VOID || t->kind == FE_TYPE_UNKNOWN || t->kind == FE_TYPE_ERROR) return; + if (t->kind == FE_TYPE_ERROR_UNION) { + check_type_cycle(c,t->error_value); + return; + } if (t->cycle_state == 1) { if (c->ast->root) err(c, c->ast->root->loc, "by-value recursive type"); return; @@ -739,6 +919,10 @@ static void check_stmt(FeCheckerState *s, FeNode *n) break; case FE_N_EXPR_STMT: check_expr(s, n->a); + if (n->a && n->a->kind==FE_N_UNARY && n->a->text && + strcmp(n->a->text,"try")==0 && + (!s->ret || s->ret->kind!=FE_TYPE_ERROR_UNION)) + err(c,n->loc,"try requires an enclosing error result"); break; case FE_N_IF: a = check_expr(s, n->a); @@ -815,6 +999,8 @@ int fe_check_program(FeCheck *c) fe_type_declare_struct(&c->types, n, (n->flags & 1U) != 0); for (n = c->ast->root ? c->ast->root->children : 0; n; n = n->next) if (n->kind == FE_N_ENUM) fe_type_declare_enum(&c->types, n); + for (n = c->ast->root ? c->ast->root->children : 0; n; n = n->next) + if (n->kind == FE_N_ERROR_DECL) fe_type_declare_error(&c->types, n); check_type_cycles(c); fe_type_layout_all(&c->types); for (n = c->ast->root ? c->ast->root->children : 0; n; n = n->next) { diff --git a/fec/src/emit_c.c b/fec/src/emit_c.c index 04dd5f5..ff75623 100644 --- a/fec/src/emit_c.c +++ b/fec/src/emit_c.c @@ -40,6 +40,7 @@ static void emit_type_deps(FeEmitter *e, FeType *t) static void emit_one_type(FeEmitter *e, FeType *t) { unsigned i,j; + if (t && strcmp(t->name,"io.Writer")==0) return; if (!t || t->emit_state || (t->kind != FE_TYPE_STRUCT && t->kind != FE_TYPE_ENUM && t->kind != FE_TYPE_ARRAY && t->kind != FE_TYPE_SLICE)) return; @@ -114,12 +115,12 @@ static void emit_type_helpers(FeEmitter *e) fe_type_c_name(t->elem,e->pointer_bits),t->indexer,t->cname); if(!e->no_checks) fprintf(e->out,"if (i >= %lu) fe_trap_bounds(); ",t->length); fprintf(e->out,"return x.a[i]; }\n"); - fprintf(e->out,"static %s %s(%s x, unsigned long a, unsigned long b) { ", + fprintf(e->out,"static %s %s(%s *x, unsigned long a, unsigned long b) { ", fe_type_c_name(fe_type_slice(&e->check->types,t->elem),e->pointer_bits),t->slicer,t->cname); if(!e->no_checks) fputs("if (a > b || b > ",e->out), fprintf(e->out,"%lu",t->length), fputs(") fe_trap_bounds(); ",e->out); - fprintf(e->out,"return %s(x.a+a,b-a); }\n",fe_type_slice(&e->check->types,t->elem)->maker); - fprintf(e->out,"static %s %s(%s x) { return %s(x,0,%lu); }\n",fe_type_c_name(fe_type_slice(&e->check->types,t->elem),e->pointer_bits),t->full_slicer,t->cname,t->slicer,t->length); - fprintf(e->out,"static %s %s(%s x, unsigned long a) { return %s(x,a,%lu); }\n",fe_type_c_name(fe_type_slice(&e->check->types,t->elem),e->pointer_bits),t->tail_slicer,t->cname,t->slicer,t->length); + fprintf(e->out,"return %s(x->a+a,b-a); }\n",fe_type_slice(&e->check->types,t->elem)->maker); + fprintf(e->out,"static %s %s(%s *x) { return %s(x,0,%lu); }\n",fe_type_c_name(fe_type_slice(&e->check->types,t->elem),e->pointer_bits),t->full_slicer,t->cname,t->slicer,t->length); + fprintf(e->out,"static %s %s(%s *x, unsigned long a) { return %s(x,a,%lu); }\n",fe_type_c_name(fe_type_slice(&e->check->types,t->elem),e->pointer_bits),t->tail_slicer,t->cname,t->slicer,t->length); } else if (t->kind==FE_TYPE_SLICE && t->indexer) { fprintf(e->out,"static %s %s(%s x, unsigned long i) { ", fe_type_c_name(t->elem,e->pointer_bits),t->indexer,t->cname); @@ -143,6 +144,49 @@ static void emit_type_helpers(FeEmitter *e) fputs("static fe_str fe_tail_slice_str(fe_str x, unsigned long a) { return fe_slice_str(x,a,x.n); }\n",e->out); } +static void emit_m4_runtime(FeEmitter *e) +{ + fputs("typedef struct { unsigned char *p; unsigned long n; } fe_m4_slice;\n",e->out); + fputs("typedef struct { void *ctx; unsigned short (*write_fn)(void *, const unsigned char *, unsigned long); } fe_writer;\n",e->out); + fputs("unsigned short fe_m4_error;\n",e->out); + fputs("unsigned short fe_m4_stdout_write(void *ctx, const unsigned char *p, unsigned long n) { (void)ctx; return fwrite(p,1,(size_t)n,stdout)==(size_t)n ? 0 : 1; }\n",e->out); + fputs("unsigned short fe_m4_null_write(void *ctx, const unsigned char *p, unsigned long n) { (void)ctx; (void)p; (void)n; return 0; }\n",e->out); + fputs("unsigned short fe_m4_buf_write(void *ctx, const unsigned char *p, unsigned long n) { fe_m4_slice *b=(fe_m4_slice*)ctx; unsigned long k=nn?n:b->n; if(k) memcpy(b->p,p,(size_t)k); b->p+=k; b->n-=k; return 0; }\n",e->out); + fputs("fe_writer fe_m4_stdout_writer(void) { fe_writer w; w.ctx=0; w.write_fn=fe_m4_stdout_write; return w; }\n",e->out); + fputs("fe_writer fe_m4_null_writer(void) { fe_writer w; w.ctx=0; w.write_fn=fe_m4_null_write; return w; }\n",e->out); + fputs("fe_writer fe_m4_buf_writer(fe_m4_slice *b) { fe_writer w; w.ctx=b; w.write_fn=fe_m4_buf_write; return w; }\n",e->out); + fputs("/* bounded sprint stack; overflow traps instead of corrupting an outer call */\n#define FE_M4_SPRINT_DEPTH 8\n",e->out); + fputs("typedef struct { fe_m4_slice b; unsigned long start_n; } fe_m4_sprint_frame;\n",e->out); + fputs("static fe_m4_sprint_frame fe_m4_sprint_stack[FE_M4_SPRINT_DEPTH];\n",e->out); + fputs("static unsigned fe_m4_sprint_depth;\n",e->out); + fputs("void fe_m4_sprint_begin(fe_m4_slice *b) { if (fe_m4_sprint_depth>=FE_M4_SPRINT_DEPTH) abort(); fe_m4_sprint_stack[fe_m4_sprint_depth].b=*b; fe_m4_sprint_stack[fe_m4_sprint_depth].start_n=b->n; ++fe_m4_sprint_depth; }\n",e->out); + fputs("fe_writer fe_m4_sprint_writer(void) { return fe_m4_buf_writer(&fe_m4_sprint_stack[fe_m4_sprint_depth-1].b); }\n",e->out); + fputs("unsigned long fe_m4_sprint_finish(void) { unsigned long result; if (!fe_m4_sprint_depth) abort(); --fe_m4_sprint_depth; result=fe_m4_sprint_stack[fe_m4_sprint_depth].start_n-fe_m4_sprint_stack[fe_m4_sprint_depth].b.n; return result; }\n",e->out); + fputs("unsigned short fe_m4_write_bytes(fe_writer w, const unsigned char *p, unsigned long n) { return w.write_fn ? w.write_fn(w.ctx,p,n) : 1; }\n",e->out); + fputs("unsigned short fe_m4_write_cstr(fe_writer w, const char *p) { return fe_m4_write_bytes(w,(const unsigned char*)p,(unsigned long)strlen(p)); }\n",e->out); + fputs("unsigned short fe_m4_write_str(fe_writer w, fe_str s) { return fe_m4_write_bytes(w,s.p,s.n); }\n",e->out); + fputs("#define fe_m4_write_slice(w,s) fe_m4_write_bytes((w),(s).p,(s).n)\n",e->out); + fputs("unsigned short fe_m4_write_int(fe_writer w, long v) { char b[40]; sprintf(b,\"%ld\",v); return fe_m4_write_cstr(w,b); }\n",e->out); + fputs("unsigned short fe_m4_write_hex(fe_writer w, unsigned long v) { char b[40]; sprintf(b,\"%lx\",v); return fe_m4_write_cstr(w,b); }\n",e->out); + fputs("unsigned short fe_m4_write_char(fe_writer w, unsigned char v) { return fe_m4_write_bytes(w,&v,1); }\n",e->out); + fputs("unsigned short fe_m4_write_bool(fe_writer w, unsigned char v) { return fe_m4_write_cstr(w,v ? \"true\" : \"false\"); }\n",e->out); + fputs("unsigned short fe_m4_write_error(fe_writer w, unsigned long v) { char b[40]; sprintf(b,\"error#%lu\",v); return fe_m4_write_cstr(w,b); }\n",e->out); +} + +static int node_uses_m4(FeNode *n) +{ + FeNode *x; + if (!n) return 0; + if (n->kind==FE_N_CALL && n->text && + (strcmp(n->text,"@print")==0 || strcmp(n->text,"@fprint")==0 || + strcmp(n->text,"@sprint")==0)) return 1; + if (n->kind==FE_N_CALL && n->a && n->a->kind==FE_N_MEMBER && + n->a->a && n->a->a->text && strcmp(n->a->a->text,"io")==0) return 1; + if (node_uses_m4(n->a) || node_uses_m4(n->b) || node_uses_m4(n->c)) return 1; + for (x=n->children; x; x=x->next) if (node_uses_m4(x)) return 1; + return 0; +} + static void emit_expr(FeEmitter *e, FeNode *n); static void emit_stmt(FeEmitter *e, FeNode *n); @@ -267,6 +311,187 @@ static void emit_c_literal(FILE *out, const char *text, int string) fputc(quote,out); } +static void emit_m4_piece(FILE *out, const char *fmt, unsigned long begin, + unsigned long end) +{ + unsigned long i; + unsigned long cp; + int h0,h1,h2,h3; + int c; + fputc('"',out); + for(i=begin;i=0 && + (h1=hex_value((unsigned char)fmt[i+2]))>=0) { + emit_byte(out,(unsigned)((h0<<4)|h1)); i+=2; + } else if(c=='u' && i+4=0 && + (h1=hex_value((unsigned char)fmt[i+2]))>=0 && + (h2=hex_value((unsigned char)fmt[i+3]))>=0 && + (h3=hex_value((unsigned char)fmt[i+4]))>=0) { + cp=(unsigned long)((h0<<12)|(h1<<8)|(h2<<4)|h3); + emit_codepoint(out,cp); i+=4; + } + else if(c=='\\' || c=='"') { fputc('\\',out); fputc(c,out); } + else emit_byte(out,(unsigned)c); + continue; + } + if(c=='"' || c=='\\') fputc('\\',out); + fputc(c,out); + } + fputc('"',out); +} + +static void emit_m4_writer(FeEmitter *e, FeNode *arg, int buffer) +{ + if (buffer) { + fputs("fe_m4_buf_writer((fe_m4_slice*)&",e->out); + if (arg && arg->kind==FE_N_UNARY && arg->text && + (strcmp(arg->text,"&")==0 || strcmp(arg->text,"&mut")==0)) emit_expr(e,arg->a); + else emit_expr(e,arg); + fputs(")",e->out); + } else if (arg && arg->kind==FE_N_UNARY && arg->text && + (strcmp(arg->text,"&")==0 || strcmp(arg->text,"&mut")==0)) { + emit_expr(e,arg->a); + } else if (arg && arg->kind==FE_N_CALL && arg->a && + arg->a->kind==FE_N_MEMBER) { + emit_expr(e,arg); + } else if (arg && arg->sem_type && arg->sem_type->kind==FE_TYPE_REF) { + fputs("(*",e->out); emit_expr(e,arg); fputc(')',e->out); + } else { + emit_expr(e,arg); + } +} + +static void emit_m4_writer_value(FeEmitter *e, FeNode *writer, int buffer) +{ + if (!writer) fputs("fe_m4_stdout_writer()",e->out); + else if (buffer) fputs("fe_m4_sprint_writer()",e->out); + else emit_m4_writer(e,writer,buffer); +} + +static void emit_m4_arg(FeEmitter *e, FeNode *arg, int verb, + FeNode *writer, int buffer, int error_value) +{ + FeType *t=arg ? arg->sem_type : 0; + if (verb=='x') { + fputs("fe_m4_write_hex(",e->out); emit_m4_writer_value(e,writer,buffer); fputs(", (unsigned long)",e->out); emit_expr(e,arg); fputc(')',e->out); return; + } + if (verb=='c') { + fputs("fe_m4_write_char(",e->out); emit_m4_writer_value(e,writer,buffer); fputs(", (unsigned char)",e->out); emit_expr(e,arg); fputc(')',e->out); return; + } + if (verb=='b') { + fputs("fe_m4_write_bool(",e->out); emit_m4_writer_value(e,writer,buffer); fputs(", ",e->out); emit_expr(e,arg); fputc(')',e->out); return; + } + if (verb=='s' || (verb==' ' && t && (t->kind==FE_TYPE_STR || t->kind==FE_TYPE_SLICE))) { + if (t && t->kind==FE_TYPE_STR) fputs("fe_m4_write_str(",e->out); + else fputs("fe_m4_write_slice(",e->out); + emit_m4_writer_value(e,writer,buffer); fputs(", ",e->out); emit_expr(e,arg); fputc(')',e->out); return; + } + if (error_value) { + fputs("fe_m4_write_error(",e->out); emit_m4_writer_value(e,writer,buffer); fputs(", (unsigned long)",e->out); emit_expr(e,arg); fputs(".tag)",e->out); return; + } + if (verb==' ' && t && t->kind==FE_TYPE_BOOL) { + fputs("fe_m4_write_bool(",e->out); emit_m4_writer_value(e,writer,buffer); fputs(", ",e->out); emit_expr(e,arg); fputc(')',e->out); return; + } + if (verb==' ' && t && t->kind==FE_TYPE_CHAR) { + fputs("fe_m4_write_char(",e->out); emit_m4_writer_value(e,writer,buffer); fputs(", (unsigned char)",e->out); emit_expr(e,arg); fputc(')',e->out); return; + } + fputs("fe_m4_write_int(",e->out); emit_m4_writer_value(e,writer,buffer); fputs(", (long)",e->out); emit_expr(e,arg); fputc(')',e->out); +} + +static void emit_m4_builtin(FeEmitter *e, FeNode *n) +{ + const char *fmt=n->aux_text; + FeNode *fmt_node=n->children; + FeNode *arg; + FeNode *writer_arg=0; + FeNode *buffer_arg=0; + unsigned long i,j,last=1; + unsigned count=0; + int verb; + int error_value; + int first=1; + int is_print=strcmp(n->text,"@print")==0; + int is_sprint=strcmp(n->text,"@sprint")==0; + if (!fmt) { fputs("0",e->out); return; } + if (is_print) writer_arg=0; + else if (is_sprint) { buffer_arg=fmt_node; fmt_node=fmt_node ? fmt_node->next : 0; } + else { writer_arg=fmt_node; fmt_node=fmt_node ? fmt_node->next : 0; } + arg=fmt_node ? fmt_node->next : 0; + error_value=0; + fputc('(',e->out); + if (!is_print && !is_sprint) { + fputs("fe_m4_error=0",e->out); + first=0; + } + if (is_sprint) { + fputs("fe_m4_sprint_begin((fe_m4_slice*)&",e->out); emit_expr(e,buffer_arg); fputs(")",e->out); + first=0; + } + i=1; + while(fmt[i] && fmt[i]!='"') { + if(fmt[i]=='\\') { ++i; if(fmt[i]) ++i; continue; } + if(fmt[i]=='{' && fmt[i+1]=='{') { i+=2; continue; } + if(fmt[i]=='}' && fmt[i+1]=='}') { i+=2; continue; } + if(fmt[i]=='{') { + j=i+1; while(fmt[j] && fmt[j]!='}') ++j; + if(!fmt[j]) break; + if(i>last) { + if(!first) fputs(", ",e->out); + if (!is_print && !is_sprint) + fputs("fe_m4_error ? fe_m4_error : (fe_m4_error = ",e->out); + if(is_print) { fputs("fe_m4_write_cstr(fe_m4_stdout_writer(), ",e->out); emit_m4_piece(e->out,fmt,last,i); fputc(')',e->out); } + else { fputs("fe_m4_write_cstr(",e->out); if(is_sprint) emit_m4_writer_value(e,buffer_arg,1); else emit_m4_writer(e,writer_arg,0); fputs(", ",e->out); emit_m4_piece(e->out,fmt,last,i); fputc(')',e->out); } + if (!is_print && !is_sprint) fputc(')',e->out); + first=0; + } + verb=(j==i+1) ? ' ' : (j==i+2 ? (unsigned char)fmt[i+1] : '?'); + if(arg) { + error_value=arg->sem_type && arg->sem_type->kind==FE_TYPE_ENUM && + arg->sem_type->is_error; + if(!first) fputs(", ",e->out); + if (!is_print && !is_sprint) + fputs("fe_m4_error ? fe_m4_error : (fe_m4_error = ",e->out); + if(is_print) emit_m4_arg(e,arg,verb,0,0,error_value); + else { + /* The writer expression is repeated intentionally; it is + a value wrapper and does not re-evaluate source args. */ + emit_m4_arg(e,arg,verb, is_sprint ? buffer_arg : writer_arg, + is_sprint,error_value); + } + if (!is_print && !is_sprint) fputc(')',e->out); + first=0; arg=arg->next; ++count; + } + last=j+1; i=j+1; continue; + } + ++i; + } + if(fmt[i]=='"' && i>last) { + if(!first) fputs(", ",e->out); + if (!is_print && !is_sprint) + fputs("fe_m4_error ? fe_m4_error : (fe_m4_error = ",e->out); + if(is_print) { fputs("fe_m4_write_cstr(fe_m4_stdout_writer(), ",e->out); emit_m4_piece(e->out,fmt,last,i); fputc(')',e->out); } + else { fputs("fe_m4_write_cstr(",e->out); if(is_sprint) emit_m4_writer_value(e,buffer_arg,1); else emit_m4_writer(e,writer_arg,0); fputs(", ",e->out); emit_m4_piece(e->out,fmt,last,i); fputc(')',e->out); } + if (!is_print && !is_sprint) fputc(')',e->out); + first=0; + } + if(first) fputs("0",e->out); + if(is_print) fputs(", (void)0",e->out); + else if(is_sprint) { fputs(", fe_m4_sprint_finish()",e->out); } + fputc(')',e->out); + (void)count; +} + static void emit_lvalue(FeEmitter *e, FeNode *n) { FeType *bt; @@ -300,14 +525,23 @@ static void emit_slice_call(FeEmitter *e, FeNode *n) FeType *bt=n->a ? n->a->sem_type : 0; const char *maker=bt && bt->slicer ? bt->slicer : "fe_slice_str"; if (!n->b && !n->c && bt && bt->full_slicer) { - fputs(bt->full_slicer,e->out); fputc('(',e->out); emit_expr(e,n->a); fputc(')',e->out); return; + fputs(bt->full_slicer,e->out); + if (bt->kind==FE_TYPE_ARRAY) { fputs("(&",e->out); emit_lvalue(e,n->a); } + else { fputc('(',e->out); emit_expr(e,n->a); } + fputc(')',e->out); return; } if (!n->c && bt && bt->tail_slicer) { - fputs(bt->tail_slicer,e->out); fputc('(',e->out); emit_expr(e,n->a); fputs(", ",e->out); + fputs(bt->tail_slicer,e->out); + if (bt->kind==FE_TYPE_ARRAY) { fputs("(&",e->out); emit_lvalue(e,n->a); } + else { fputc('(',e->out); emit_expr(e,n->a); } + fputs(", ",e->out); if (n->b) emit_expr(e,n->b); else fputs("0",e->out); fputc(')',e->out); return; } - fputs(maker,e->out); fputc('(',e->out); emit_expr(e,n->a); fputs(", ",e->out); + fputs(maker,e->out); + if (bt && bt->kind==FE_TYPE_ARRAY) { fputs("(&",e->out); emit_lvalue(e,n->a); } + else { fputc('(',e->out); emit_expr(e,n->a); } + fputs(", ",e->out); if(n->b) emit_expr(e,n->b); else fputs("0",e->out); fputs(", ",e->out); if(n->c) emit_expr(e,n->c); @@ -375,6 +609,7 @@ static void emit_expr(FeEmitter *e, FeNode *n) } case FE_N_UNARY: op = n->text ? n->text : ""; + if (strcmp(op, "try") == 0) { emit_expr(e,n->a); break; } if (strcmp(op, "not") == 0) fputs("(!", e->out); else { fputc('(', e->out); @@ -405,7 +640,16 @@ static void emit_expr(FeEmitter *e, FeNode *n) case FE_N_CALL: { FeVariantType *v; int special=0; - if(!n->a && n->text && strcmp(n->text,"@size_of")==0 && n->children && n->children->kind==FE_N_IDENT) { fprintf(e->out,"%lu",fe_type_size(fe_type_intern(&e->check->types,n->children->text))); special=1; } + if(n->text && (strcmp(n->text,"@print")==0 || strcmp(n->text,"@fprint")==0 || strcmp(n->text,"@sprint")==0)) { emit_m4_builtin(e,n); special=1; } + else if(n->a && n->a->kind==FE_N_MEMBER && n->a->a && + n->a->a->kind==FE_N_IDENT && n->a->a->text && + strcmp(n->a->a->text,"io")==0 && n->a->b && n->a->b->text && + strcmp(n->a->b->text,"buf_writer")==0 && n->children) { emit_m4_writer(e,n->children,1); special=1; } + else if(n->a && n->a->kind==FE_N_MEMBER && n->a->a && + n->a->a->kind==FE_N_IDENT && n->a->a->text && + strcmp(n->a->a->text,"io")==0 && n->a->b && n->a->b->text && + strcmp(n->a->b->text,"null_writer")==0) { fputs("fe_m4_null_writer()",e->out); special=1; } + else if(!n->a && n->text && strcmp(n->text,"@size_of")==0 && n->children && n->children->kind==FE_N_IDENT) { fprintf(e->out,"%lu",fe_type_size(fe_type_intern(&e->check->types,n->children->text))); special=1; } else if(!n->a && n->text && strcmp(n->text,"@align_of")==0 && n->children && n->children->kind==FE_N_IDENT) { fprintf(e->out,"%u",fe_type_align(fe_type_intern(&e->check->types,n->children->text))); special=1; } else if (n->a && n->a->kind==FE_N_MEMBER && n->a->a && n->a->a->sem_type && n->a->a->sem_type->kind==FE_TYPE_ENUM) { v=fe_type_variant(n->a->a->sem_type,n->a->b ? n->a->b->text : ""); @@ -424,7 +668,10 @@ static void emit_expr(FeEmitter *e, FeNode *n) } case FE_N_MEMBER: { FeVariantType *v; - if(n->a && n->a->sem_type && n->a->sem_type->kind==FE_TYPE_REF && + if(n->a && n->a->kind==FE_N_IDENT && n->a->text && + strcmp(n->a->text,"io")==0 && n->b && n->b->text && + strcmp(n->b->text,"stdout")==0) fputs("fe_m4_stdout_writer()",e->out); + else if(n->a && n->a->sem_type && n->a->sem_type->kind==FE_TYPE_REF && n->b && n->b->text && strcmp(n->b->text,"^")==0) { fputs("(*",e->out); emit_expr(e,n->a); fputs(")",e->out); } else if(n->a && n->a->sem_type && n->a->sem_type->kind==FE_TYPE_ENUM) { v=fe_type_variant(n->a->sem_type,n->b ? n->b->text : ""); if(v) fputs(v->maker,e->out); else fputs("0",e->out); if(v)fputs("()",e->out); } @@ -443,6 +690,16 @@ static void emit_decl(FeEmitter *e, FeNode *n) fputs(ctype(e, n), e->out); fputc(' ', e->out); fputs(cname(n, "fe_local"), e->out); + if (n->kind==FE_N_CONST && n->b) { + fputs(" = ",e->out); + if (n->b->kind==FE_N_LITERAL && n->b->text && n->b->text[0]=='"') { + fputs("{ (const unsigned char*)",e->out); + emit_c_literal(e->out,n->b->text,1); + fputs(", sizeof(",e->out); + emit_c_literal(e->out,n->b->text,1); + fputs(")-1 }",e->out); + } else emit_expr(e,n->b); + } fputs(";\n", e->out); } @@ -463,9 +720,15 @@ static void emit_block(FeEmitter *e, FeNode *n) ++e->indent; /* C89 requires declarations before statements in each actual block. */ for (x = n->children; x; x = x->next) - if (x->kind == FE_N_LET || x->kind == FE_N_VAR) emit_decl(e, x); + if (x->kind == FE_N_LET || x->kind == FE_N_VAR || + x->kind == FE_N_CONST) emit_decl(e, x); for (x = n->children; x; x = x->next) emit_stmt(e, x); --e->indent; + if (e->fallthrough_block==n) { + pad(e); + fputs("return 0;\n",e->out); + e->fallthrough_block=0; + } pad(e); fputc('}', e->out); } @@ -531,8 +794,15 @@ static void emit_stmt(FeEmitter *e, FeNode *n) break; case FE_N_EXPR_STMT: pad(e); - emit_expr(e, n->a); - fputs(";\n", e->out); + if (n->a && n->a->kind==FE_N_UNARY && n->a->text && + strcmp(n->a->text,"try")==0 && n->a->a) { + fputs("if ((fe_m4_error = ",e->out); + emit_expr(e,n->a->a); + fputs(") != 0) return fe_m4_error;\n",e->out); + } else { + emit_expr(e, n->a); + fputs(";\n", e->out); + } break; case FE_N_RETURN: pad(e); @@ -630,6 +900,10 @@ static void emit_fn(FeEmitter *e, FeNode *fn, int prototype) if (prototype) fputs(";\n", e->out); else { fputs(" ", e->out); + if (fn->sem_type && fn->sem_type->kind==FE_TYPE_ERROR_UNION && + fn->sem_type->error_value && + fn->sem_type->error_value->kind==FE_TYPE_VOID) + e->fallthrough_block=fn->c; emit_block(e, fn->c); fputc('\n', e->out); } @@ -658,19 +932,26 @@ void fe_emit_c_init(FeEmitter *e, FILE *out, FeCheck *check, e->indent = 0; e->no_checks = no_checks; e->temp_serial = 0; + e->fallthrough_block = 0; } void fe_emit_c_program(FeEmitter *e) { FeNode *n; FeNode *main_fn = 0; - fputs("/* generated by fec M3 */\n#include \n#include \ntypedef char fe_assert_u8[(sizeof(unsigned char)==1) ? 1 : -1];\ntypedef char fe_assert_u16[(sizeof(unsigned short)==2) ? 1 : -1];\ntypedef char fe_assert_u32[(sizeof(unsigned long)==4) ? 1 : -1];\n", e->out); + FeType *type; + int need_m4; + need_m4=node_uses_m4(e->check->ast->root); + for (type=e->check->types.types; type; type=type->next) + if (strcmp(type->name,"io.Writer")==0) need_m4=1; + fputs("/* generated by fec M4 */\n#include \n#include \n#include \n#include \ntypedef char fe_assert_u8[(sizeof(unsigned char)==1) ? 1 : -1];\ntypedef char fe_assert_u16[(sizeof(unsigned short)==2) ? 1 : -1];\ntypedef char fe_assert_u32[(sizeof(unsigned long)==4) ? 1 : -1];\n", e->out); if (e->pointer_bits==16) fputs("typedef char fe_assert_usize[(sizeof(unsigned short)==2) ? 1 : -1];\n",e->out); else fputs("typedef char fe_assert_usize[(sizeof(unsigned long)==4) ? 1 : -1];\n",e->out); fputs("static void fe_trap_bounds(void) { abort(); }\n\n", e->out); emit_type_defs(e); + if (need_m4) emit_m4_runtime(e); emit_type_helpers(e); for (n = e->check->ast->root ? e->check->ast->root->children : 0; n; n = n->next) { @@ -680,7 +961,14 @@ void fe_emit_c_program(FeEmitter *e) fputs(cname(n, "fe_global"), e->out); if (n->b) { fputs(" = ", e->out); - emit_expr(e, n->b); + if (n->kind==FE_N_CONST && n->b->kind==FE_N_LITERAL && + n->b->text && n->b->text[0]=='"') { + fputs("{ (const unsigned char*)",e->out); + emit_c_literal(e->out,n->b->text,1); + fputs(", sizeof(",e->out); + emit_c_literal(e->out,n->b->text,1); + fputs(")-1 }",e->out); + } else emit_expr(e, n->b); } fputs(";\n", e->out); } diff --git a/fec/src/emit_c.h b/fec/src/emit_c.h index b9f12ab..bc927a5 100644 --- a/fec/src/emit_c.h +++ b/fec/src/emit_c.h @@ -10,6 +10,7 @@ typedef struct FeEmitter { int indent; int no_checks; unsigned temp_serial; + FeNode *fallthrough_block; } FeEmitter; void fe_emit_c_init(FeEmitter *e, FILE *out, FeCheck *check, diff --git a/fec/src/parser.c b/fec/src/parser.c index f5abc90..9ddd72c 100644 --- a/fec/src/parser.c +++ b/fec/src/parser.c @@ -58,7 +58,14 @@ static FeNode *type(FeParser *p) } if (is_name(p) || is(p,FE_TOK_TYPE)) { next(p); n=toknode(p,FE_N_TYPE,t); - if (eat(p,FE_TOK_DOT)) { if(is_name(p)){FeNode *m=toknode(p,FE_N_IDENT,p->previous); n->a=m; next(p);} else error(p,"expected type name after '.'"); } + if (eat(p,FE_TOK_DOT)) { + if(is_name(p)) { + FeToken mt=p->current; + FeNode *m=toknode(p,FE_N_IDENT,mt); + n->a=m; + next(p); + } else error(p,"expected type name after '.'"); + } if (eat(p,FE_TOK_BANG)) { FeNode *e=toknode(p,FE_N_TYPE,p->previous); e->a=n; e->b=type(p); return e; } if (eat(p,FE_TOK_LPAREN)) { while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(n,type(p));if(!eat(p,FE_TOK_COMMA))break;} want(p,FE_TOK_RPAREN,"expected ')' in generic type"); } return n; @@ -147,7 +154,7 @@ 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_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); n->a=expr(p,11); left=n; } + if(is(p,FE_TOK_MINUS)||is(p,FE_TOK_NOT)||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; } 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; } return left; @@ -229,7 +236,7 @@ static FeNode *statement(FeParser *p) if(is(p,FE_TOK_LBRACE)) return block(p); if(eat(p,FE_TOK_LET)) { n=toknode(p,FE_N_LET,t);if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected variable name");if(eat(p,FE_TOK_COLON))n->a=type(p);want(p,FE_TOK_EQ,"expected '=' in let");n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';'");return n; } if(eat(p,FE_TOK_VAR)) { n=toknode(p,FE_N_VAR,t);if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected variable name");if(eat(p,FE_TOK_COLON))n->a=type(p);if(eat(p,FE_TOK_EQ))n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';'");return n; } - if(eat(p,FE_TOK_CONST)) { n=toknode(p,FE_N_CONST,t);if(is_name(p))next(p);else error(p,"expected constant name");if(eat(p,FE_TOK_COLON))n->a=type(p);want(p,FE_TOK_EQ,"expected '=' in const");n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';'");return n; } + if(eat(p,FE_TOK_CONST)) { n=toknode(p,FE_N_CONST,t);if(is_name(p)){n->text=fe_arena_strdup(&p->ast->arena,p->current.begin,p->current.length);next(p);}else error(p,"expected constant name");if(eat(p,FE_TOK_COLON))n->a=type(p);want(p,FE_TOK_EQ,"expected '=' in const");n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';'");return n; } if(eat(p,FE_TOK_IF)) { n=toknode(p,FE_N_IF,t);if(eat(p,FE_TOK_LET)){n->text=fe_arena_strdup(&p->ast->arena,"if let",6);if(is_name(p))next(p);if(eat(p,FE_TOK_LPAREN)){if(is_name(p))next(p);want(p,FE_TOK_RPAREN,"expected ')' in if let pattern");}want(p,FE_TOK_EQ,"expected '=' in if let");}n->a=header_expr(p);n->b=block(p);if(eat(p,FE_TOK_ELSE))n->c=is(p,FE_TOK_IF)?statement(p):block(p);return n; } if(eat(p,FE_TOK_COMPTIME)) { n=toknode(p,FE_N_IF,t);want(p,FE_TOK_IF,"expected 'if' after comptime");n->text=fe_arena_strdup(&p->ast->arena,"comptime if",11);n->a=header_expr(p);n->b=block(p);if(eat(p,FE_TOK_ELSE))n->c=is(p,FE_TOK_IF)?statement(p):block(p);return n; } if(eat(p,FE_TOK_WHILE)) {n=toknode(p,FE_N_WHILE,t);n->a=header_expr(p);n->b=block(p);return n;} diff --git a/fec/src/types.c b/fec/src/types.c index 8640ac1..3d77341 100644 --- a/fec/src/types.c +++ b/fec/src/types.c @@ -22,10 +22,12 @@ static FeType *new_type(FeTypeCtx *ctx, const char *name, FeTypeKind kind) t->bits = 0; t->is_unsigned = 0; t->packed = 0; + t->is_error = 0; t->length = 0; t->size = 0; t->align = 1; t->elem = 0; + t->error_value = 0; t->ref_mut = 0; t->fields = 0; t->field_count = 0; @@ -60,6 +62,9 @@ FeType *fe_type_intern(FeTypeCtx *ctx, const char *name) else if (strcmp(name, "bool") == 0) kind = FE_TYPE_BOOL; else if (strcmp(name, "char") == 0) kind = FE_TYPE_CHAR; else if (strcmp(name, "str") == 0) kind = FE_TYPE_STR; + else if (strcmp(name, "io.Writer") == 0) { + kind = FE_TYPE_STRUCT; + } else if (strcmp(name, "i8") == 0 || strcmp(name, "u8") == 0) { kind = FE_TYPE_INT; bits = 8; uns = name[0] == 'u'; } else if (strcmp(name, "i16") == 0 || strcmp(name, "u16") == 0) { @@ -73,6 +78,12 @@ FeType *fe_type_intern(FeTypeCtx *ctx, const char *name) if (!t) return 0; t->bits = bits; t->is_unsigned = uns; + if (strcmp(name,"io.Writer")==0) { + t->cname=fe_arena_strdup(ctx->arena,"fe_writer",10); + t->size=4; + t->align=1; + return t; + } if (kind == FE_TYPE_STR) { t->cname = fe_arena_strdup(ctx->arena, "fe_str", 6); t->elem = fe_type_intern(ctx, "u8"); @@ -155,6 +166,19 @@ FeType *fe_type_ref(FeTypeCtx *ctx, FeType *elem, int mutable) return t; } +FeType *fe_type_error_union(FeTypeCtx *ctx, FeType *value) +{ + char key[128]; + FeType *t; + sprintf(key,"!%s",value ? value->name : "?"); + t=fe_type_intern(ctx,key); + if(t->kind==FE_TYPE_UNKNOWN) { + t->kind=FE_TYPE_ERROR_UNION; + t->error_value=value; + } + return t; +} + FeType *fe_type_declare_struct(FeTypeCtx *ctx, const FeNode *node, int packed) { FeType *t; @@ -227,7 +251,10 @@ FeType *fe_type_declare_enum(FeTypeCtx *ctx, const FeNode *node) t->variants[i].name = v->text; t->variants[i].fields = 0; t->variants[i].field_count = 0; - t->variants[i].tag = i; + if (node->kind==FE_N_ERROR_DECL && v->a && + v->a->kind==FE_N_LITERAL && v->a->text) + t->variants[i].tag=(unsigned)strtoul(v->a->text,0,0); + else t->variants[i].tag = i; t->variants[i].ast_node = v; t->variants[i].maker = generated_name(ctx, "fe_make_variant_", v->text ? v->text : "variant"); if (v->a && v->a->kind == FE_N_TYPE) { @@ -266,6 +293,13 @@ FeType *fe_type_declare_enum(FeTypeCtx *ctx, const FeNode *node) return t; } +FeType *fe_type_declare_error(FeTypeCtx *ctx, const FeNode *node) +{ + FeType *t=fe_type_declare_enum(ctx,node); + if (t) t->is_error=1; + return t; +} + static unsigned long round_up(unsigned long x, unsigned a) { unsigned long rem; @@ -302,6 +336,10 @@ static void layout_type(FeTypeCtx *ctx, FeType *t) t->cycle_state = 1; if (t->kind == FE_TYPE_VOID || t->kind == FE_TYPE_UNKNOWN || t->kind == FE_TYPE_ERROR) { t->size = 0; t->align = 1; t->cycle_state = 2; return; } + if (t->kind == FE_TYPE_ERROR_UNION) { + t->size = 2; t->align = ctx->pointer_bits == 16 ? 1U : 2U; + t->cycle_state = 2; return; + } if (t->kind == FE_TYPE_BOOL || t->kind == FE_TYPE_CHAR) { t->size = 1; t->align = 1; t->cycle_state = 2; return; } @@ -395,12 +433,18 @@ FeVariantType *fe_type_variant(FeType *t, const char *name) FeType *fe_type_from_ast(FeTypeCtx *ctx, const FeNode *node) { unsigned long length = 0; + char qualified[128]; if (!node) return fe_type_intern(ctx, ""); if (node->kind != FE_N_TYPE) return fe_type_intern(ctx, ""); if (node->text && strcmp(node->text, "as") == 0) return fe_type_from_ast(ctx, node->b); if (node->text && strcmp(node->text, "str") == 0) return fe_type_intern(ctx, "str"); + if (node->a && node->a->kind==FE_N_IDENT && node->text && + strcmp(node->text,"io")==0 && node->a->text) { + sprintf(qualified,"%s.%s",node->text,node->a->text); + return fe_type_intern(ctx,qualified); + } if (node->text && (strcmp(node->text, "&") == 0 || strcmp(node->text, "&mut") == 0)) return fe_type_ref(ctx, fe_type_from_ast(ctx,node->a), @@ -413,8 +457,12 @@ FeType *fe_type_from_ast(FeTypeCtx *ctx, const FeNode *node) } return fe_type_slice(ctx, fe_type_from_ast(ctx, node->b)); } + if (node->text && strcmp(node->text, "!") == 0) + /* Prefix !T stores T in a; the E!T spelling stores its success + type in b and the error type in a. */ + return fe_type_error_union(ctx, fe_type_from_ast( + ctx, node->b ? node->b : node->a)); if (node->text && (strcmp(node->text, "?") == 0 || - strcmp(node->text, "!") == 0 || strcmp(node->text, "^") == 0 || strcmp(node->text, "&") == 0 || strcmp(node->text, "&mut") == 0 || @@ -447,6 +495,7 @@ const char *fe_type_c_name(const FeType *t, unsigned pointer_bits) if (!t) return "long"; if (t->cname) return t->cname; if (t->kind == FE_TYPE_VOID) return "void"; + if (t->kind == FE_TYPE_ERROR_UNION) return "unsigned short"; if (t->kind == FE_TYPE_BOOL || t->kind == FE_TYPE_CHAR) return "unsigned char"; if (t->kind == FE_TYPE_REF) { static char ref_name[128]; diff --git a/fec/src/types.h b/fec/src/types.h index 5165c78..16fa67e 100644 --- a/fec/src/types.h +++ b/fec/src/types.h @@ -4,7 +4,7 @@ #include "ast.h" typedef enum FeTypeKind { - FE_TYPE_ERROR, FE_TYPE_VOID, FE_TYPE_BOOL, FE_TYPE_CHAR, FE_TYPE_INT, + FE_TYPE_ERROR, FE_TYPE_ERROR_UNION, FE_TYPE_VOID, FE_TYPE_BOOL, FE_TYPE_CHAR, FE_TYPE_INT, FE_TYPE_STRUCT, FE_TYPE_ENUM, FE_TYPE_ARRAY, FE_TYPE_SLICE, FE_TYPE_STR, FE_TYPE_REF, FE_TYPE_UNKNOWN } FeTypeKind; @@ -40,10 +40,13 @@ struct FeType { unsigned bits; int is_unsigned; int packed; + int is_error; unsigned long length; unsigned long size; unsigned align; FeType *elem; + /* Success value for an error union; !void is represented directly. */ + FeType *error_value; int ref_mut; FeFieldType *fields; unsigned field_count; @@ -68,8 +71,10 @@ FeType *fe_type_from_ast(FeTypeCtx *ctx, const FeNode *node); FeType *fe_type_array(FeTypeCtx *ctx, unsigned long length, FeType *elem); FeType *fe_type_slice(FeTypeCtx *ctx, FeType *elem); FeType *fe_type_ref(FeTypeCtx *ctx, FeType *elem, int mutable); +FeType *fe_type_error_union(FeTypeCtx *ctx, FeType *value); FeType *fe_type_declare_struct(FeTypeCtx *ctx, const FeNode *node, int packed); FeType *fe_type_declare_enum(FeTypeCtx *ctx, const FeNode *node); +FeType *fe_type_declare_error(FeTypeCtx *ctx, const FeNode *node); void fe_type_layout_all(FeTypeCtx *ctx); FeFieldType *fe_type_field(FeType *t, const char *name); FeVariantType *fe_type_variant(FeType *t, const char *name); diff --git a/fec/test-dos.bat b/fec/test-dos.bat index 22ce1f9..acc08bb 100644 --- a/fec/test-dos.bat +++ b/fec/test-dos.bat @@ -189,6 +189,45 @@ if not errorlevel 1 goto test_fail fec.exe --target=bits32 --emit-c TESTS\M3\BADINDEX.FE -o TESTS\M3\BADINDEX.C > nul if not errorlevel 1 goto test_fail +if exist TESTS\M4\FORMAT.C del TESTS\M4\FORMAT.C +if exist TESTS\M4\FORMAT.EXE del TESTS\M4\FORMAT.EXE +fec.exe --target=bits32 --emit-c TESTS\M4\FORMAT.FE -o TESTS\M4\FORMAT.C > nul +if errorlevel 1 goto test_fail +wcl386 -q -za -wx -wcd=202 -bt=dos -fe=TESTS\M4\FORMAT.EXE TESTS\M4\FORMAT.C +if errorlevel 1 goto test_fail +TESTS\M4\FORMAT.EXE > nul +if errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M4\TRY-FPR.FE -o TESTS\M4\TRY-FPR.C > nul +if errorlevel 1 goto test_fail +wcl386 -q -za -wx -wcd=202 -bt=dos -fe=TESTS\M4\TRY-FPR.EXE TESTS\M4\TRY-FPR.C +if errorlevel 1 goto test_fail +TESTS\M4\TRY-FPR.EXE > nul +if errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M4\PROP.FE -o TESTS\M4\PROP.C > nul +if errorlevel 1 goto test_fail +wcl386 -q -za -wx -wcd=202 -bt=dos -fe=TESTS\M4\PROP.EXE TESTS\M4\PROPTEST.C +if errorlevel 1 goto test_fail +TESTS\M4\PROP.EXE > nul +if errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-ARI.FE -o TESTS\M4\BAD-ARI.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-VERB.FE -o TESTS\M4\BAD-VERB.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-RUN.FE -o TESTS\M4\BAD-RUN.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-TYP.FE -o TESTS\M4\BAD-TYP.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-TRY.FE -o TESTS\M4\BAD-TRY.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-WRI.FE -o TESTS\M4\BAD-WRI.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-MANY.FE -o TESTS\M4\BAD-MANY.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-OPEN.FE -o TESTS\M4\BAD-OPEN.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M4\BAD-CLS.FE -o TESTS\M4\BAD-CLS.C > nul +if not errorlevel 1 goto test_fail + echo OK>TEST.OK cd C:\FEC goto test_done diff --git a/fec/tests/m4/bad-arity.fe b/fec/tests/m4/bad-arity.fe new file mode 100644 index 0000000..3b29ff8 --- /dev/null +++ b/fec/tests/m4/bad-arity.fe @@ -0,0 +1,6 @@ +unit m4_bad_arity; + +fn main() -> i32 { + @print("{} {}", 1); + return 0; +} diff --git a/fec/tests/m4/bad-cls.fe b/fec/tests/m4/bad-cls.fe new file mode 100644 index 0000000..a321e1c --- /dev/null +++ b/fec/tests/m4/bad-cls.fe @@ -0,0 +1,6 @@ +unit m4_bad_cls; + +fn main() -> i32 { + @print("}", 1); + return 0; +} diff --git a/fec/tests/m4/bad-many.fe b/fec/tests/m4/bad-many.fe new file mode 100644 index 0000000..66a7833 --- /dev/null +++ b/fec/tests/m4/bad-many.fe @@ -0,0 +1,6 @@ +unit m4_bad_many; + +fn main() -> i32 { + @print("{}", 1, 2); + return 0; +} diff --git a/fec/tests/m4/bad-open.fe b/fec/tests/m4/bad-open.fe new file mode 100644 index 0000000..3f1f584 --- /dev/null +++ b/fec/tests/m4/bad-open.fe @@ -0,0 +1,6 @@ +unit m4_bad_open; + +fn main() -> i32 { + @print("{", 1); + return 0; +} diff --git a/fec/tests/m4/bad-runtime.fe b/fec/tests/m4/bad-runtime.fe new file mode 100644 index 0000000..fa6d7f1 --- /dev/null +++ b/fec/tests/m4/bad-runtime.fe @@ -0,0 +1,7 @@ +unit m4_bad_runtime; + +fn main() -> i32 { + var fmt: str = "{}"; + @print(fmt, 1); + return 0; +} diff --git a/fec/tests/m4/bad-try.fe b/fec/tests/m4/bad-try.fe new file mode 100644 index 0000000..2bd0e28 --- /dev/null +++ b/fec/tests/m4/bad-try.fe @@ -0,0 +1,6 @@ +unit m4_bad_try; + +fn main() -> i32 { + try @print("nope"); + return 0; +} diff --git a/fec/tests/m4/bad-type.fe b/fec/tests/m4/bad-type.fe new file mode 100644 index 0000000..e88b48f --- /dev/null +++ b/fec/tests/m4/bad-type.fe @@ -0,0 +1,9 @@ +unit m4_bad_type; + +struct Point { x: i32, } + +fn main() -> i32 { + let p: Point = Point{ x: 1 }; + @print("{}", p); + return 0; +} diff --git a/fec/tests/m4/bad-verb.fe b/fec/tests/m4/bad-verb.fe new file mode 100644 index 0000000..ea19d9a --- /dev/null +++ b/fec/tests/m4/bad-verb.fe @@ -0,0 +1,6 @@ +unit m4_bad_verb; + +fn main() -> i32 { + @print("{q}", 1); + return 0; +} diff --git a/fec/tests/m4/bad-writer.fe b/fec/tests/m4/bad-writer.fe new file mode 100644 index 0000000..8dac8c4 --- /dev/null +++ b/fec/tests/m4/bad-writer.fe @@ -0,0 +1,7 @@ +unit m4_bad_writer; + +fn main() -> i32 { + var x: i32 = 0; + @fprint(&mut x, "bad"); + return 0; +} diff --git a/fec/tests/m4/format.fe b/fec/tests/m4/format.fe new file mode 100644 index 0000000..e3582fd --- /dev/null +++ b/fec/tests/m4/format.fe @@ -0,0 +1,30 @@ +unit m4_format; + +const FMT: str = "n={} hex={x} c={c} s={s} b={b} {{ok}}\n"; + +fn main() -> i32 { + var raw: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0]; + var raw2: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0]; + var raw3: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0]; + var raw4: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0]; + var raw5: [8]u8 = [0, 0, 0, 0, 0, 0, 0, 0]; + var buf: []u8 = raw[..]; + var buf2: []u8 = raw2[..]; + var buf3: []u8 = raw3[..]; + var buf4: []u8 = raw4[..]; + var buf5: []u8 = raw5[..]; + let w: io.Writer = io.buf_writer(&mut buf); + const LOCAL_FMT: str = "value={}\n"; + @print(FMT, 7, 15, 'A', "yes", true); + @fprint(&mut w, LOCAL_FMT, 12); + let n: usize = @sprint(buf2, "A\x42\u0043defghi"); + let n2: usize = @sprint(buf3, "xy"); + let inner_n: usize = @sprint(buf4, "xy"); + let outer_n: usize = @sprint(buf5, "n={}", @sprint(buf4, "xy")); + if n == 8 and buf2[0] == ('A' as u8) and + buf2[1] == ('B' as u8) and buf2[2] == ('C' as u8) and + n2 == 2 and buf3[0] == ('x' as u8) and + inner_n == 2 and outer_n == 3 and + buf5[0] == ('n' as u8) and buf5[2] == ('2' as u8) { return 0; } + return 1; +} diff --git a/fec/tests/m4/prop.fe b/fec/tests/m4/prop.fe new file mode 100644 index 0000000..81a85f4 --- /dev/null +++ b/fec/tests/m4/prop.fe @@ -0,0 +1,5 @@ +unit m4_prop; + +pub fn propagate(w: &mut io.Writer) -> !void { + try @fprint(w, "a{}b", 1); +} diff --git a/fec/tests/m4/proptest.c b/fec/tests/m4/proptest.c new file mode 100644 index 0000000..7261461 --- /dev/null +++ b/fec/tests/m4/proptest.c @@ -0,0 +1,23 @@ +#include "prop.c" + +static unsigned short calls; + +static unsigned short fail_write(void *ctx, const unsigned char *p, + unsigned long n) +{ + (void)ctx; + (void)p; + (void)n; + ++calls; + return calls == 1 ? 7 : 0; +} + +int main(void) +{ + fe_writer w; + unsigned short result; + w.ctx=0; + w.write_fn=fail_write; + result=fe_m4_prop_propagate(&w); + return (result==7 && calls==1) ? 0 : 1; +} diff --git a/fec/tests/m4/try-fprint.fe b/fec/tests/m4/try-fprint.fe new file mode 100644 index 0000000..e6c419c --- /dev/null +++ b/fec/tests/m4/try-fprint.fe @@ -0,0 +1,8 @@ +unit m4_try_fprint; + +fn main() -> !void { + var raw: [4]u8 = [0, 0, 0, 0]; + var buf: []u8 = raw[..]; + let w: io.Writer = io.buf_writer(&mut buf); + try @fprint(&mut w, "ok"); +} diff --git a/fec/tests/run-tests.sh b/fec/tests/run-tests.sh index 9b907cc..5d211ac 100644 --- a/fec/tests/run-tests.sh +++ b/fec/tests/run-tests.sh @@ -61,3 +61,33 @@ for f in badfld badmat badarr badcycle badstr badchar badfield badindex; do fi done echo "M3 tests: structs, enums, arrays, slices, str, match, and bounds passed" + +m4tmp=$(mktemp -d) +trap 'rm -rf "$m2tmp" "$m3tmp" "$m4tmp"' EXIT HUP INT TERM +for f in format; do + "$root"/fec --target=bits32 --emit-c "$root"/tests/m4/$f.fe -o "$m4tmp/$f.c" + ${CC:-cc} -std=c89 -pedantic "$m4tmp/$f.c" -o "$m4tmp/$f" + "$m4tmp/$f" >"$m4tmp/$f.out" +done +for f in try-fprint; do + "$root"/fec --target=bits32 --emit-c "$root"/tests/m4/$f.fe -o "$m4tmp/$f.c" + ${CC:-cc} -std=c89 -pedantic "$m4tmp/$f.c" -o "$m4tmp/$f" + "$m4tmp/$f" +done +"$root"/fec --target=bits32 --emit-c "$root"/tests/m4/prop.fe -o "$m4tmp/prop.c" +cp "$root"/tests/m4/proptest.c "$m4tmp/proptest.c" +${CC:-cc} -std=c89 -pedantic "$m4tmp/proptest.c" -o "$m4tmp/prop" +"$m4tmp/prop" +for f in bad-arity bad-verb bad-runtime bad-type bad-try bad-writer; do + if "$root"/fec --target=bits32 --emit-c "$root"/tests/m4/$f.fe -o "$m4tmp/$f.c" >/dev/null 2>/dev/null; then + echo "FAIL (accepted M4 semantic error): $f.fe" + exit 1 + fi +done +for f in bad-many bad-open bad-cls; do + if "$root"/fec --target=bits32 --emit-c "$root"/tests/m4/$f.fe -o "$m4tmp/$f.c" >/dev/null 2>/dev/null; then + echo "FAIL (accepted M4 format-brace error): $f.fe" + exit 1 + fi +done +echo "M4 tests: formatting builtins passed" diff --git a/fec/vm-m1.bat b/fec/vm-m1.bat index 63f9cb7..b5755f1 100644 --- a/fec/vm-m1.bat +++ b/fec/vm-m1.bat @@ -8,6 +8,7 @@ if not exist C:\FEC\TESTS\PASS md C:\FEC\TESTS\PASS if not exist C:\FEC\TESTS\FAIL md C:\FEC\TESTS\FAIL if not exist C:\FEC\TESTS\M2 md C:\FEC\TESTS\M2 if not exist C:\FEC\TESTS\M3 md C:\FEC\TESTS\M3 +if not exist C:\FEC\TESTS\M4 md C:\FEC\TESTS\M4 if exist C:\FEC\VM.FAIL del C:\FEC\VM.FAIL if exist C:\FEC\STAGE.FAIL del C:\FEC\STAGE.FAIL @@ -142,6 +143,33 @@ if errorlevel 1 goto stage_fail copy D:\FEC\TESTS\M3\BADINDEX.FE C:\FEC\TESTS\M3\BADINDEX.FE > nul if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\FORMAT.FE C:\FEC\TESTS\M4\FORMAT.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\BAD-ARI~1.FE C:\FEC\TESTS\M4\BAD-ARI.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\BAD-VERB.FE C:\FEC\TESTS\M4\BAD-VERB.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\BAD-RUN~1.FE C:\FEC\TESTS\M4\BAD-RUN.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\BAD-TYP~1.FE C:\FEC\TESTS\M4\BAD-TYP.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\BAD-TRY.FE C:\FEC\TESTS\M4\BAD-TRY.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\TRY-FPR~1.FE C:\FEC\TESTS\M4\TRY-FPR.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\BAD-WRI~1.FE C:\FEC\TESTS\M4\BAD-WRI.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\PROP.FE C:\FEC\TESTS\M4\PROP.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\PROPTEST.C C:\FEC\TESTS\M4\PROPTEST.C > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\BAD-MANY.FE C:\FEC\TESTS\M4\BAD-MANY.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\BAD-OPEN.FE C:\FEC\TESTS\M4\BAD-OPEN.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M4\BAD-CLS.FE C:\FEC\TESTS\M4\BAD-CLS.FE > nul +if errorlevel 1 goto stage_fail + call C:\FEC\TEST-DOS.BAT if exist C:\FEC\TEST.OK goto vm_success echo FAIL>C:\FEC\VM.FAIL