From 2e27b88c1b9fa8ec9b9f243909f5b793b376f207 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 30 Apr 2024 13:18:47 -0400 Subject: [PATCH] Improved syntax for optionals --- ast.c | 1 + ast.h | 5 ++++- builtins/pointer.c | 14 ++++++++++---- builtins/types.h | 5 +++-- compile.c | 10 ++++++++-- environment.c | 14 +++++++------- parse.c | 25 +++++++++++++++++++------ repl.c | 4 ++-- test/structs.tm | 2 +- test/tables.tm | 4 ++-- typecheck.c | 17 +++++++++++++++-- types.c | 4 ++-- 12 files changed, 74 insertions(+), 31 deletions(-) diff --git a/ast.c b/ast.c index 80681e1..acbe109 100644 --- a/ast.c +++ b/ast.c @@ -140,6 +140,7 @@ CORD ast_to_xml(ast_t *ast) T(LangDef, "%r", data.name, ast_to_xml(data.namespace)) T(Index, "%r%r", optional_tagged("indexed", data.indexed), optional_tagged("index", data.index)) T(FieldAccess, "%r", data.field, ast_to_xml(data.fielded)) + T(Optional, "%r", ast_to_xml(data.value)) T(DocTest, "%r%r", optional_tagged("expression", data.expr), xml_escape(data.output)) T(Use, "%r", xml_escape(data.raw_path)) T(LinkerDirective, "%r", xml_escape(data.directive)) diff --git a/ast.h b/ast.h index 17ab727..b0e924f 100644 --- a/ast.h +++ b/ast.h @@ -110,7 +110,7 @@ typedef enum { Return, Extern, StructDef, EnumDef, LangDef, - Index, FieldAccess, + Index, FieldAccess, Optional, DocTest, Use, LinkerDirective, @@ -255,6 +255,9 @@ struct ast_s { ast_t *fielded; const char *field; } FieldAccess; + struct { + ast_t *value; + } Optional; struct { ast_t *expr; const char *output; diff --git a/builtins/pointer.c b/builtins/pointer.c index 7fe8c16..d71811b 100644 --- a/builtins/pointer.c +++ b/builtins/pointer.c @@ -22,7 +22,8 @@ public CORD Pointer$as_text(const void *x, bool colorize, const TypeInfo *type) auto ptr_info = type->PointerInfo; if (!x) { CORD typename = generic_as_text(NULL, false, ptr_info.pointed); - return colorize ? CORD_asprintf("\x1b[34;1m%s%s\x1b[m", ptr_info.sigil, typename) : CORD_cat(ptr_info.sigil, typename); + CORD c = colorize ? CORD_asprintf("\x1b[34;1m%s%s\x1b[m", ptr_info.sigil, typename) : CORD_cat(ptr_info.sigil, typename); + return ptr_info.is_optional ? CORD_cat(c, "?") : c; } const void *ptr = *(const void**)x; if (!ptr) { @@ -36,8 +37,11 @@ public CORD Pointer$as_text(const void *x, bool colorize, const TypeInfo *type) int32_t depth = 0; for (recursion_t *r = recursion; r; r = r->next) { ++depth; - if (r->ptr == ptr) - return CORD_asprintf(colorize ? "\x1b[34;1m%s..%d\x1b[m" : "%s..%d", ptr_info.sigil, depth); + if (r->ptr == ptr) { + CORD c = CORD_asprintf(colorize ? "\x1b[34;1m%s..%d\x1b[m" : "%s..%d", ptr_info.sigil, depth); + if (ptr_info.is_optional) c = CORD_cat(c, colorize ? "\x1b[34;1m?\x1b[m" : "?"); + return c; + } } CORD pointed; @@ -47,7 +51,9 @@ public CORD Pointer$as_text(const void *x, bool colorize, const TypeInfo *type) pointed = generic_as_text(ptr, colorize, ptr_info.pointed); recursion = recursion->next; } - return colorize ? CORD_asprintf("\x1b[34;1m%s\x1b[m%r", ptr_info.sigil, pointed) : CORD_cat(ptr_info.sigil, pointed); + CORD c = colorize ? CORD_asprintf("\x1b[34;1m%s\x1b[m%r", ptr_info.sigil, pointed) : CORD_cat(ptr_info.sigil, pointed); + if (ptr_info.is_optional) c = CORD_cat(c, colorize ? "\x1b[34;1m?\x1b[m" : "?"); + return c; } public int32_t Pointer$compare(const void *x, const void *y, const TypeInfo *type) { diff --git a/builtins/types.h b/builtins/types.h index ee49d9a..3b517fd 100644 --- a/builtins/types.h +++ b/builtins/types.h @@ -28,6 +28,7 @@ typedef struct TypeInfo { } CustomInfo; struct { const char *sigil; + bool is_optional; const struct TypeInfo *pointed; } PointerInfo; struct { @@ -50,8 +51,8 @@ typedef struct TypeInfo { }; } TypeInfo; -#define $PointerInfo(sigil_expr, pointed_info) &((TypeInfo){.size=sizeof(void*), .align=__alignof__(void*), \ - .tag=PointerInfo, .PointerInfo.sigil=sigil_expr, .PointerInfo.pointed=pointed_info}) +#define $PointerInfo(sigil_expr, pointed_info, opt) &((TypeInfo){.size=sizeof(void*), .align=__alignof__(void*), \ + .tag=PointerInfo, .PointerInfo={.sigil=sigil_expr, .pointed=pointed_info, .is_optional=opt}}) #define $ArrayInfo(item_info) &((TypeInfo){.size=sizeof(array_t), .align=__alignof__(array_t), \ .tag=ArrayInfo, .ArrayInfo.item=item_info}) #define $TableInfo(key_expr, value_expr) &((TypeInfo){.size=sizeof(table_t), .align=__alignof__(table_t), \ diff --git a/compile.c b/compile.c index 1e46be1..dec8ea4 100644 --- a/compile.c +++ b/compile.c @@ -1014,6 +1014,9 @@ CORD compile(env_t *env, ast_t *ast) return CORD_all("(&", compile(env, subject), ")"); return CORD_all("stack(", compile(env, subject), ")"); } + case Optional: { + return compile(env, Match(ast, Optional)->value); + } case BinaryOp: { auto binop = Match(ast, BinaryOp); CORD lhs = compile(env, binop->lhs); @@ -1929,9 +1932,12 @@ CORD compile_type_info(env_t *env, type_t *t) } case PointerType: { auto ptr = Match(t, PointerType); - CORD sigil = ptr->is_stack ? "&" : (ptr->is_optional ? "?" : "@"); + CORD sigil = ptr->is_stack ? "&" : "@"; if (ptr->is_readonly) sigil = CORD_cat(sigil, "%"); - return CORD_asprintf("$PointerInfo(%r, %r)", Text$quoted(sigil, false), compile_type_info(env, ptr->pointed)); + return CORD_asprintf("$PointerInfo(%r, %r, %s)", + Text$quoted(sigil, false), + compile_type_info(env, ptr->pointed), + ptr->is_optional ? "yes" : "no"); } case FunctionType: { return CORD_asprintf("$FunctionInfo(%r)", Text$quoted(type_to_cord(t), false)); diff --git a/environment.c b/environment.c index db3dbe9..ec9ec5d 100644 --- a/environment.c +++ b/environment.c @@ -52,14 +52,14 @@ env_t *new_compilation_unit(void) {"Void", Type(VoidType), "Void_t", "$Void", {}}, {"Memory", Type(MemoryType), "Memory_t", "$Memory", {}}, {"Bool", Type(BoolType), "Bool_t", "$Bool", TypedArray(ns_entry_t, - {"from_text", "Bool$from_text", "func(text:Text, success=!Bool)->Bool"}, + {"from_text", "Bool$from_text", "func(text:Text, success=!&Bool)->Bool"}, )}, {"Int", Type(IntType, .bits=64), "Int_t", "$Int", TypedArray(ns_entry_t, {"format", "Int$format", "func(i:Int, digits=0)->Text"}, {"hex", "Int$hex", "func(i:Int, digits=0, uppercase=yes, prefix=yes)->Text"}, {"octal", "Int$octal", "func(i:Int, digits=0, prefix=yes)->Text"}, {"random", "Int$random", "func(min=0, max=0xffffffff)->Int"}, - {"from_text", "Int$from_text", "func(text:Text, the_rest=!Text)->Int"}, + {"from_text", "Int$from_text", "func(text:Text, the_rest=!&Text)->Int"}, {"bits", "Int$bits", "func(x:Int)->[Bool]"}, {"abs", "labs", "func(i:Int)->Int"}, {"min", "Int$min", "Int"}, @@ -70,7 +70,7 @@ env_t *new_compilation_unit(void) {"hex", "Int32$hex", "func(i:Int32, digits=0, uppercase=yes, prefix=yes)->Text"}, {"octal", "Int32$octal", "func(i:Int32, digits=0, prefix=yes)->Text"}, {"random", "Int32$random", "func(min=0, max=0xffffffff)->Int32"}, - {"from_text", "Int$from_text", "func(text:Text, the_rest=!Text)->Int32"}, + {"from_text", "Int$from_text", "func(text:Text, the_rest=!&Text)->Int32"}, {"bits", "Int32$bits", "func(x:Int32)->[Bool]"}, {"abs", "abs", "func(i:Int32)->Int32"}, {"min", "Int32$min", "Int32"}, @@ -81,7 +81,7 @@ env_t *new_compilation_unit(void) {"hex", "Int16$hex", "func(i:Int16, digits=0, uppercase=yes, prefix=yes)->Text"}, {"octal", "Int16$octal", "func(i:Int16, digits=0, prefix=yes)->Text"}, {"random", "Int16$random", "func(min=0, max=0xffffffff)->Int16"}, - {"from_text", "Int$from_text", "func(text:Text, the_rest=!Text)->Int16"}, + {"from_text", "Int$from_text", "func(text:Text, the_rest=!&Text)->Int16"}, {"bits", "Int16$bits", "func(x:Int16)->[Bool]"}, {"abs", "abs", "func(i:Int16)->Int16"}, {"min", "Int16$min", "Int16"}, @@ -92,7 +92,7 @@ env_t *new_compilation_unit(void) {"hex", "Int8$hex", "func(i:Int8, digits=0, uppercase=yes, prefix=yes)->Text"}, {"octal", "Int8$octal", "func(i:Int8, digits=0, prefix=yes)->Text"}, {"random", "Int8$random", "func(min=0, max=0xffffffff)->Int8"}, - {"from_text", "Int$from_text", "func(text:Text, the_rest=!Text)->Int8"}, + {"from_text", "Int$from_text", "func(text:Text, the_rest=!&Text)->Int8"}, {"bits", "Int8$bits", "func(x:Int8)->[Bool]"}, {"abs", "abs", "func(i:Int8)->Int8"}, {"min", "Int8$min", "Int8"}, @@ -115,7 +115,7 @@ env_t *new_compilation_unit(void) {"TAU", "(2.*M_PI)", "Num"}, {"random", "Num$random", "func()->Num"}, {"mix", "Num$mix", "func(amount:Num, x:Num, y:Num)->Num"}, - {"from_text", "Num$from_text", "func(text:Text, the_rest=!Text)->Num"}, + {"from_text", "Num$from_text", "func(text:Text, the_rest=!&Text)->Num"}, {"abs", "fabs", "func(n:Num)->Num"}, F(acos), F(acosh), F(asin), F(asinh), F(atan), F(atanh), F(cbrt), F(ceil), F(cos), F(cosh), F(erf), F(erfc), F(exp), F(exp2), F(expm1), F(floor), F(j0), F(j1), F(log), F(log10), F(log1p), F(log2), F(logb), @@ -143,7 +143,7 @@ env_t *new_compilation_unit(void) {"TAU", "(Num32_t)(2.f*M_PI)", "Num32"}, {"random", "Num32$random", "func()->Num32"}, {"mix", "Num32$mix", "func(amount:Num32, x:Num32, y:Num32)->Num32"}, - {"from_text", "Num32$from_text", "func(text:Text, the_rest=!Text)->Num32"}, + {"from_text", "Num32$from_text", "func(text:Text, the_rest=!&Text)->Num32"}, {"abs", "fabsf", "func(n:Num32)->Num32"}, F(acos), F(acosh), F(asin), F(asinh), F(atan), F(atanh), F(cbrt), F(ceil), F(cos), F(cosh), F(erf), F(erfc), F(exp), F(exp2), F(expm1), F(floor), F(j0), F(j1), F(log), F(log10), F(log1p), F(log2), F(logb), diff --git a/parse.c b/parse.c index e3fdbb4..720c796 100644 --- a/parse.c +++ b/parse.c @@ -71,6 +71,7 @@ static ast_t *parse_method_call_suffix(parse_ctx_t *ctx, ast_t *self); static ast_t *parse_field_suffix(parse_ctx_t *ctx, ast_t *lhs); static ast_t *parse_index_suffix(parse_ctx_t *ctx, ast_t *lhs); static ast_t *parse_comprehension_suffix(parse_ctx_t *ctx, ast_t *lhs); +static ast_t *parse_optional_suffix(parse_ctx_t *ctx, ast_t *lhs); static arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos, bool allow_unnamed); static PARSER(parse_for); static PARSER(parse_while); @@ -496,11 +497,9 @@ type_ast_t *parse_array_type(parse_ctx_t *ctx, const char *pos) { type_ast_t *parse_pointer_type(parse_ctx_t *ctx, const char *pos) { const char *start = pos; - bool optional = false, is_stack = false; + bool is_stack; if (match(&pos, "@")) - optional = false; - else if (match(&pos, "?")) - optional = true; + is_stack = false; else if (match(&pos, "&")) is_stack = true; else @@ -511,6 +510,7 @@ type_ast_t *parse_pointer_type(parse_ctx_t *ctx, const char *pos) { spaces(&pos); type_ast_t *type = expect(ctx, start, &pos, parse_type, "I couldn't parse a pointer type after this point"); + bool optional = match(&pos, "?"); return NewTypeAST(ctx->file, start, pos, PointerTypeAST, .pointed=type, .is_optional=optional, .is_stack=is_stack, .is_readonly=is_readonly); } @@ -735,6 +735,15 @@ ast_t *parse_field_suffix(parse_ctx_t *ctx, ast_t *lhs) { return NewAST(ctx->file, lhs->start, pos, FieldAccess, .fielded=lhs, .field=field); } +ast_t *parse_optional_suffix(parse_ctx_t *ctx, ast_t *lhs) { + if (!lhs) return NULL; + const char *pos = lhs->end; + if (match(&pos, "?")) + return NewAST(ctx->file, lhs->start, pos, Optional, .value=lhs); + else + return NULL; +} + PARSER(parse_reduction) { const char *start = pos; if (!match(&pos, "(")) return NULL; @@ -956,7 +965,9 @@ PARSER(parse_heap_alloc) { if (!match(&pos, "@")) return NULL; spaces(&pos); ast_t *val = expect(ctx, start, &pos, parse_term, "I expected an expression for this '@'"); - return NewAST(ctx->file, start, pos, HeapAllocate, .value=val); + ast_t *ast = NewAST(ctx->file, start, pos, HeapAllocate, .value=val); + ast_t *optional = parse_optional_suffix(ctx, ast); + return optional ? optional : ast; } PARSER(parse_stack_reference) { @@ -964,7 +975,9 @@ PARSER(parse_stack_reference) { if (!match(&pos, "&")) return NULL; spaces(&pos); ast_t *val = expect(ctx, start, &pos, parse_term, "I expected an expression for this '&'"); - return NewAST(ctx->file, start, pos, StackReference, .value=val); + ast_t *ast = NewAST(ctx->file, start, pos, StackReference, .value=val); + ast_t *optional = parse_optional_suffix(ctx, ast); + return optional ? optional : ast; } PARSER(parse_not) { diff --git a/repl.c b/repl.c index 0e968cd..94b47e4 100644 --- a/repl.c +++ b/repl.c @@ -132,11 +132,11 @@ const TypeInfo *type_to_type_info(type_t *t) } case PointerType: { auto ptr = Match(t, PointerType); - CORD sigil = ptr->is_stack ? "&" : (ptr->is_optional ? "?" : "@"); + CORD sigil = ptr->is_stack ? "&" : "@"; if (ptr->is_readonly) sigil = CORD_cat(sigil, "%"); const TypeInfo *pointed_info = type_to_type_info(ptr->pointed); const TypeInfo pointer_info = {.size=sizeof(void*), .align=__alignof__(void*), - .tag=PointerInfo, .PointerInfo.sigil=sigil, .PointerInfo.pointed=pointed_info}; + .tag=PointerInfo, .PointerInfo={.sigil=sigil, .pointed=pointed_info, .is_optional=ptr->is_optional}}; return memcpy(GC_MALLOC(sizeof(TypeInfo)), &pointer_info, sizeof(TypeInfo)); } default: errx(1, "Unsupported type: %T", t); diff --git a/test/structs.tm b/test/structs.tm index 840ee84..ad8e7ad 100644 --- a/test/structs.tm +++ b/test/structs.tm @@ -1,7 +1,7 @@ struct Pair(x,y:Int) struct Mixed(x:Int, text:Text) -struct LinkedList(x:Int, next=!LinkedList) +struct LinkedList(x:Int, next=!@LinkedList) struct Password(text:Text; secret) func test_literals(): diff --git a/test/tables.tm b/test/tables.tm index 8609e52..4c61bb7 100644 --- a/test/tables.tm +++ b/test/tables.tm @@ -18,7 +18,7 @@ func main(): >> #t = 2 >> t.default - = ?%999 + = @%999? >> t.fallback = !{Text:Int} @@ -42,7 +42,7 @@ func main(): >> t2.default = !Int >> t2.fallback - = ?%{"one":1, "two":2; default=999} + = @%{"one":1, "two":2; default=999}? t2_str := "" for k,v in t2: diff --git a/typecheck.c b/typecheck.c index 5f17856..ad99a3b 100644 --- a/typecheck.c +++ b/typecheck.c @@ -330,8 +330,11 @@ type_t *get_type(env_t *env, ast_t *ast) if (!ast) return NULL; switch (ast->tag) { case Nil: { - type_t *pointed = parse_type_ast(env, Match(ast, Nil)->type); - return Type(PointerType, .is_optional=true, .pointed=pointed); + type_t *t = parse_type_ast(env, Match(ast, Nil)->type); + if (t->tag != PointerType) + code_err(ast, "This type is not a pointer type, so it doesn't work with a '!' nil expression"); + auto ptr = Match(t, PointerType); + return Type(PointerType, .is_optional=true, .pointed=ptr->pointed, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly); } case Bool: { return Type(BoolType); @@ -385,6 +388,16 @@ type_t *get_type(env_t *env, ast_t *ast) code_err(ast, "'&' stack references can only be used on variables or fields of variables"); } + case Optional: { + ast_t *value = Match(ast, Optional)->value; + type_t *t = get_type(env, value); + if (t->tag != PointerType) + code_err(ast, "This value is not a pointer, it has type %T, so it can't be optional", t); + auto ptr = Match(t, PointerType); + if (ptr->is_optional) + code_err(ast, "This value is already optional, it can't be converted to optional"); + return Type(PointerType, .pointed=ptr->pointed, .is_optional=true, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly); + } case TextLiteral: return TEXT_TYPE; case TextJoin: { const char *lang = Match(ast, TextJoin)->lang; diff --git a/types.c b/types.c index 0a379dd..d7114a9 100644 --- a/types.c +++ b/types.c @@ -47,9 +47,9 @@ CORD type_to_cord(type_t *t) { } case PointerType: { auto ptr = Match(t, PointerType); - CORD sigil = ptr->is_stack ? "&" : (ptr->is_optional ? "?" : "@"); + CORD sigil = ptr->is_stack ? "&" : "@"; if (ptr->is_readonly) sigil = CORD_cat(sigil, "%"); - return CORD_cat(sigil, type_to_cord(ptr->pointed)); + return CORD_all(sigil, type_to_cord(ptr->pointed), ptr->is_optional ? "?" : CORD_EMPTY); } case EnumType: { auto tagged = Match(t, EnumType);