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", optional_tagged("expression", data.expr), xml_escape(data.output))
T(Use, "", 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);