Add optional types

This commit is contained in:
Bruce Hill 2024-09-11 01:31:31 -04:00
parent 89234e34e2
commit 7126755275
20 changed files with 566 additions and 384 deletions

View File

@ -30,7 +30,8 @@ CFLAGS_PLACEHOLDER="$$(echo -e '\033[2m<flags...>\033[m')"
LDLIBS=-lgc -lcord -lm -lunistring -lgmp -ldl
BUILTIN_OBJS=builtins/siphash.o builtins/array.o builtins/bool.o builtins/channel.o builtins/nums.o builtins/functions.o builtins/integers.o \
builtins/pointer.o builtins/memory.o builtins/text.o builtins/thread.o builtins/c_string.o builtins/table.o \
builtins/types.o builtins/util.o builtins/files.o builtins/range.o builtins/shell.o builtins/path.o builtins/nextline.o
builtins/types.o builtins/util.o builtins/files.o builtins/range.o builtins/shell.o builtins/path.o builtins/nextline.o \
builtins/optionals.o
TESTS=$(patsubst %.tm,%.tm.testresult,$(wildcard test/*.tm))
all: libtomo.so tomo

5
ast.c
View File

@ -168,13 +168,14 @@ CORD type_ast_to_xml(type_ast_t *t)
#define T(type, ...) case type: { auto data = t->__data.type; (void)data; return CORD_asprintf(__VA_ARGS__); }
T(UnknownTypeAST, "<UnknownType/>")
T(VarTypeAST, "%s", data.name)
T(PointerTypeAST, "<PointerType is_optional=\"%s\" is_stack=\"%s\" is_readonly=\"%s\">%r</PointerType>",
data.is_optional ? "yes" : "no", data.is_stack ? "yes" : "no", data.is_readonly ? "yes" : "no", type_ast_to_xml(data.pointed))
T(PointerTypeAST, "<PointerType is_stack=\"%s\" is_readonly=\"%s\">%r</PointerType>",
data.is_stack ? "yes" : "no", data.is_readonly ? "yes" : "no", type_ast_to_xml(data.pointed))
T(ArrayTypeAST, "<ArrayType>%r</ArrayType>", type_ast_to_xml(data.item))
T(SetTypeAST, "<TableType>%r</TableType>", type_ast_to_xml(data.item))
T(ChannelTypeAST, "<ChannelType>%r</ChannelType>", type_ast_to_xml(data.item))
T(TableTypeAST, "<TableType>%r %r</TableType>", type_ast_to_xml(data.key), type_ast_to_xml(data.value))
T(FunctionTypeAST, "<FunctionType>%r %r</FunctionType>", arg_list_to_xml(data.args), type_ast_to_xml(data.ret))
T(OptionalTypeAST, "<OptionalType>%r</OptionalType>", data.type)
#undef T
default: return CORD_EMPTY;
}

6
ast.h
View File

@ -61,6 +61,7 @@ typedef enum {
ChannelTypeAST,
TableTypeAST,
FunctionTypeAST,
OptionalTypeAST,
} type_ast_e;
typedef struct tag_ast_s {
@ -83,7 +84,7 @@ struct type_ast_s {
} VarTypeAST;
struct {
type_ast_t *pointed;
bool is_optional:1, is_stack:1, is_readonly:1;
bool is_stack:1, is_readonly:1;
} PointerTypeAST;
struct {
type_ast_t *item;
@ -98,6 +99,9 @@ struct type_ast_s {
arg_ast_t *args;
type_ast_t *ret;
} FunctionTypeAST;
struct {
type_ast_t *type;
} OptionalTypeAST;
} __data;
};

View File

@ -16,6 +16,7 @@
#include "files.h"
#include "functions.h"
#include "integers.h"
#include "optionals.h"
#include "pointer.h"
#include "siphash.h"
#include "string.h"
@ -113,6 +114,9 @@ PUREFUNC public uint64_t generic_hash(const void *obj, const TypeInfo *type)
case ArrayInfo: return Array$hash(obj, type);
case ChannelInfo: return Channel$hash((const channel_t**)obj, type);
case TableInfo: return Table$hash(obj, type);
case OptionalInfo: {
errx(1, "Optional hash not implemented");
}
case EmptyStruct: return 0;
case CustomInfo:
if (!type->CustomInfo.hash)
@ -135,6 +139,9 @@ PUREFUNC public int32_t generic_compare(const void *x, const void *y, const Type
case ArrayInfo: return Array$compare(x, y, type);
case ChannelInfo: return Channel$compare((const channel_t**)x, (const channel_t**)y, type);
case TableInfo: return Table$compare(x, y, type);
case OptionalInfo: {
errx(1, "Optional compare not implemented");
}
case EmptyStruct: return 0;
case CustomInfo:
if (!type->CustomInfo.compare)
@ -157,6 +164,9 @@ PUREFUNC public bool generic_equal(const void *x, const void *y, const TypeInfo
case ChannelInfo: return Channel$equal((const channel_t**)x, (const channel_t**)y, type);
case TableInfo: return Table$equal(x, y, type);
case EmptyStruct: return true;
case OptionalInfo: {
errx(1, "Optional equal not implemented");
}
case CustomInfo:
if (!type->CustomInfo.equal)
goto use_generic_compare;
@ -177,6 +187,7 @@ public Text_t generic_as_text(const void *obj, bool colorize, const TypeInfo *ty
case ChannelInfo: return Channel$as_text((const channel_t**)obj, colorize, type);
case TableInfo: return Table$as_text(obj, colorize, type);
case TypeInfoInfo: return Type$as_text(obj, colorize, type);
case OptionalInfo: return Optional$as_text(obj, colorize, type);
case EmptyStruct: return colorize ?
Text$concat(Text("\x1b[0;1m"), Text$from_str(type->EmptyStruct.name), Text("\x1b[m()"))
: Text$concat(Text$from_str(type->EmptyStruct.name), Text("()"));

49
builtins/optionals.c Normal file
View File

@ -0,0 +1,49 @@
// Optional types
#include "bool.h"
#include "datatypes.h"
#include "text.h"
#include "util.h"
public const Array_t NULL_ARRAY = {.length=-1};
public const Bool_t NULL_BOOL = -1;
public const Int_t NULL_INT = {.small=0};
public const Table_t NULL_TABLE = {.entries.length=-1};
public const closure_t NULL_CLOSURE = {.fn=NULL};
public const Text_t NULL_TEXT = {.length=-1};
static inline bool is_null(const void *obj, const TypeInfo *non_optional_type)
{
if (non_optional_type == &Int$info)
return ((Int_t*)obj)->small == 0;
else if (non_optional_type == &Bool$info)
return *((Bool_t*)obj) == NULL_BOOL;
else if (non_optional_type == &Num$info)
return isnan(*((Num_t*)obj));
switch (non_optional_type->tag) {
case PointerInfo: return *(void**)obj == NULL;
case TextInfo: return ((Text_t*)obj)->length < 0;
case ArrayInfo: return ((Array_t*)obj)->length < 0;
case TableInfo: return ((Table_t*)obj)->entries.length < 0;
case FunctionInfo: return *(void**)obj == NULL;
default: {
Text_t t = generic_as_text(NULL, false, non_optional_type);
errx(1, "is_null() not implemented for: %k", &t);
}
}
}
#pragma GCC diagnostic ignored "-Wstack-protector"
public Text_t Optional$as_text(const void *obj, bool colorize, const TypeInfo *type)
{
if (!obj)
return Text$concat(generic_as_text(obj, colorize, type->OptionalInfo.type), Text("?"));
if (is_null(obj, type->OptionalInfo.type))
return Text$concat(colorize ? Text("\x1b[31m!") : Text("!"), generic_as_text(NULL, false, type->OptionalInfo.type),
colorize ? Text("\x1b[m") : Text(""));
return Text$concat(generic_as_text(obj, colorize, type->OptionalInfo.type), colorize ? Text("\x1b[33m?\x1b[m") : Text("?"));
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1

25
builtins/optionals.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
// Optional types
#include <gc/cord.h>
#include <stdbool.h>
#include <stdint.h>
#include "types.h"
#include "util.h"
#define Bool_t bool
#define yes (Bool_t)true
#define no (Bool_t)false
extern const Bool_t NULL_BOOL;
extern const Table_t NULL_TABLE;
extern const Array_t NULL_ARRAY;
extern const Int_t NULL_INT;
extern const closure_t NULL_CLOSURE;
extern const Text_t NULL_TEXT;
Text_t Optional$as_text(const void *obj, bool colorize, const TypeInfo *type);
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -27,10 +27,6 @@ public Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo *type
text = Text$concat(Text("\x1b[34;1m"), Text$from_str(ptr_info.sigil), typename, Text("\x1b[m"));
else
text = Text$concat(Text$from_str(ptr_info.sigil), typename);
if (ptr_info.is_optional)
text = Text$concat(text, Text("?"));
return text;
}
const void *ptr = *(const void**)x;
@ -55,8 +51,6 @@ public Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo *type
Text(".."),
Int32$as_text(&depth, false, &Int32$info),
colorize ? Text("\x1b[m") : Text(""));
if (ptr_info.is_optional)
text = Text$concat(text, colorize ? Text("\x1b[34;1m?\x1b[m") : Text("?"));
return text;
}
}
@ -73,9 +67,6 @@ public Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo *type
text = Text$concat(Text("\x1b[34;1m"), Text$from_str(ptr_info.sigil), Text("\x1b[m"), pointed);
else
text = Text$concat(Text$from_str(ptr_info.sigil), pointed);
if (ptr_info.is_optional)
text = Text$concat(text, Text("?"));
return text;
}

View File

@ -21,6 +21,7 @@
#include "macros.h"
#include "memory.h"
#include "nums.h"
#include "optionals.h"
#include "path.h"
#include "pointer.h"
#include "range.h"

View File

@ -39,6 +39,7 @@ public Text_t Func$as_text(const void *fn, bool colorize, const TypeInfo *type)
{
(void)fn;
Text_t text = Text$from_str(type->FunctionInfo.type_str);
if (fn) text = Text$concat(text, Text(": ..."));
if (fn && colorize)
text = Text$concat(Text("\x1b[32;1m"), text, Text("\x1b[m"));
return text;

View File

@ -17,7 +17,8 @@ typedef Text_t (*text_fn_t)(const void*, bool, const struct TypeInfo*);
typedef struct TypeInfo {
int64_t size, align;
struct { // Anonymous tagged union for convenience
enum { CustomInfo, PointerInfo, TextInfo, ArrayInfo, ChannelInfo, TableInfo, FunctionInfo, TypeInfoInfo, OpaqueInfo, EmptyStruct } tag;
enum { CustomInfo, PointerInfo, TextInfo, ArrayInfo, ChannelInfo, TableInfo, FunctionInfo,
OptionalInfo, TypeInfoInfo, OpaqueInfo, EmptyStruct } tag;
union {
struct {
equal_fn_t equal;
@ -27,7 +28,6 @@ typedef struct TypeInfo {
} CustomInfo;
struct {
const char *sigil;
bool is_optional;
const struct TypeInfo *pointed;
} PointerInfo;
struct {
@ -45,6 +45,9 @@ typedef struct TypeInfo {
struct {
const char *type_str;
} TypeInfoInfo;
struct {
const struct TypeInfo *type;
} OptionalInfo;
#pragma GCC diagnostic ignored "-Wpedantic"
struct {} OpaqueInfo;
struct {
@ -54,8 +57,8 @@ typedef struct TypeInfo {
};
} TypeInfo;
#define Pointer$info(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 Pointer$info(sigil_expr, pointed_info) &((TypeInfo){.size=sizeof(void*), .align=__alignof__(void*), \
.tag=PointerInfo, .PointerInfo={.sigil=sigil_expr, .pointed=pointed_info}})
#define Array$info(item_info) &((TypeInfo){.size=sizeof(Array_t), .align=__alignof__(Array_t), \
.tag=ArrayInfo, .ArrayInfo.item=item_info})
#define Set$info(item_info) &((TypeInfo){.size=sizeof(Table_t), .align=__alignof__(Table_t), \
@ -70,6 +73,8 @@ typedef struct TypeInfo {
.tag=FunctionInfo, .FunctionInfo.type_str=typestr})
#define TypeInfo$info(typestr) &((TypeInfo){.size=sizeof(TypeInfo), .align=__alignof__(TypeInfo), \
.tag=TypeInfoInfo, .TypeInfoInfo.type_str=typestr})
#define Optional$info(t) &((TypeInfo){.size=(t)->size, .align=(t)->align, \
.tag=OptionalInfo, .OptionalInfo.type=t})
extern const TypeInfo TypeInfo$info;
extern const TypeInfo Void$info;

131
compile.c
View File

@ -55,13 +55,17 @@ static bool promote(env_t *env, CORD *code, type_t *actual, type_t *needed)
}
// Automatic dereferencing:
if (actual->tag == PointerType && !Match(actual, PointerType)->is_optional
if (actual->tag == PointerType
&& can_promote(Match(actual, PointerType)->pointed, needed)) {
*code = CORD_all("*(", *code, ")");
return promote(env, code, Match(actual, PointerType)->pointed, needed);
}
// Optional and stack ref promotion:
// Optional promotion:
if (needed->tag == OptionalType && type_eq(actual, Match(needed, OptionalType)->type))
return true;
// Stack ref promotion:
if (actual->tag == PointerType && needed->tag == PointerType)
return true;
@ -194,6 +198,12 @@ CORD compile_type(type_t *t)
auto e = Match(t, EnumType);
return CORD_all(namespace_prefix(e->env->libname, e->env->namespace->parent), e->name, "_t");
}
case OptionalType: {
type_t *nonnull = Match(t, OptionalType)->type;
if (!supports_optionals(nonnull))
compiler_err(NULL, NULL, NULL, "Optional types are not supported for: %T", t);
return compile_type(nonnull);
}
case TypeInfoType: return "TypeInfo";
default: compiler_err(NULL, NULL, NULL, "Compiling type is not implemented for type with tag %d", t->tag);
}
@ -213,11 +223,12 @@ static CORD compile_lvalue(env_t *env, ast_t *ast)
if (ast->tag == Index) {
auto index = Match(ast, Index);
type_t *container_t = get_type(env, index->indexed);
if (!index->index && container_t->tag == PointerType) {
if (Match(container_t, PointerType)->is_optional)
code_err(index->indexed, "This pointer might be null, so it can't be safely used as an assignment target");
if (container_t->tag == OptionalType)
code_err(index->indexed, "This value might be null, so it can't be safely used as an assignment target");
if (!index->index && container_t->tag == PointerType)
return compile(env, ast);
}
container_t = value_type(container_t);
if (container_t->tag == ArrayType) {
CORD target_code = compile_to_pointer_depth(env, index->indexed, 1, false);
@ -269,6 +280,36 @@ static CORD compile_inline_block(env_t *env, ast_t *ast)
return code;
}
static CORD compile_optional_into_nonnull(env_t *env, binding_t *b)
{
(void)env;
// TODO: implement
return (b->code);
}
static CORD compile_optional_check(env_t *env, ast_t *ast)
{
type_t *t = get_type(env, ast);
t = Match(t, OptionalType)->type;
if (t->tag == PointerType || t->tag == FunctionType)
return CORD_all("(", compile(env, ast), " != NULL)");
else if (t->tag == BigIntType)
return CORD_all("((", compile(env, ast), ").small != 0)");
else if (t->tag == ClosureType)
return CORD_all("((", compile(env, ast), ").fn != NULL)");
else if (t->tag == NumType)
return CORD_all("!isnan(", compile(env, ast), ")");
else if (t->tag == ArrayType)
return CORD_all("((", compile(env, ast), ").length >= 0)");
else if (t->tag == TableType || t->tag == SetType)
return CORD_all("((", compile(env, ast), ").entries.length >= 0)");
else if (t->tag == BoolType)
return CORD_all("((", compile(env, ast), ") != NULL_BOOL)");
else if (t->tag == TextType)
return CORD_all("((", compile(env, ast), ").length >= 0)");
errx(1, "Optional check not implemented for: %T", t);
}
CORD compile_statement(env_t *env, ast_t *ast)
{
switch (ast->tag) {
@ -280,21 +321,6 @@ CORD compile_statement(env_t *env, ast_t *ast)
auto when = Match(ast, When);
type_t *subject_t = get_type(env, when->subject);
if (subject_t->tag == PointerType) {
ast_t *var = when->clauses->args->ast;
CORD var_code = compile(env, var);
env_t *non_null_scope = fresh_scope(env);
auto ptr = Match(subject_t, PointerType);
type_t *non_optional_t = Type(PointerType, .pointed=ptr->pointed, .is_stack=ptr->is_stack,
.is_readonly=ptr->is_readonly, .is_optional=false);
set_binding(non_null_scope, Match(var, Var)->name, new(binding_t, .type=non_optional_t, .code=var_code));
return CORD_all(
"{\n",
compile_declaration(subject_t, var_code), " = ", compile(env, when->subject), ";\n"
"if (", var_code, ")\n", compile_statement(non_null_scope, when->clauses->body),
"\nelse\n", compile_statement(env, when->else_body), "\n}");
}
auto enum_t = Match(subject_t, EnumType);
CORD code = CORD_all("{ ", compile_type(subject_t), " subject = ", compile(env, when->subject), ";\n"
"switch (subject.tag) {");
@ -742,7 +768,7 @@ CORD compile_statement(env_t *env, ast_t *ast)
"static Table_t cache = {};\n",
compile_type(args_t), " args = {", all_args, "};\n"
"const TypeInfo *table_type = Table$info(", compile_type_info(env, args_t), ", ", compile_type_info(env, ret_t), ");\n",
compile_declaration(Type(PointerType, .pointed=ret_t, .is_optional=true), "cached"), " = Table$get_raw(cache, &args, table_type);\n"
compile_declaration(Type(PointerType, .pointed=ret_t), "cached"), " = Table$get_raw(cache, &args, table_type);\n"
"if (cached) return *cached;\n",
compile_declaration(ret_t, "ret"), " = ", name, "$uncached(", all_args, ");\n",
pop_code,
@ -1172,19 +1198,32 @@ CORD compile_statement(env_t *env, ast_t *ast)
auto if_ = Match(ast, If);
type_t *cond_t = get_type(env, if_->condition);
if (cond_t->tag == PointerType) {
if (!Match(cond_t, PointerType)->is_optional)
code_err(if_->condition, "This pointer will always be non-null, so it should not be used in a conditional.");
} else if (cond_t->tag != BoolType && cond_t->tag != TextType) {
code_err(if_->condition, "This pointer will always be non-null, so it should not be used in a conditional.");
} else if (cond_t->tag != BoolType && cond_t->tag != TextType && cond_t->tag != OptionalType) {
code_err(if_->condition, "Only boolean values, optional pointers, and text can be used in conditionals (this is a %T)", cond_t);
}
env_t *truthy_scope = env;
CORD condition;
if (cond_t->tag == TextType)
if (cond_t->tag == TextType) {
condition = CORD_all("(", compile(env, if_->condition), ").length");
else
} else if (cond_t->tag == OptionalType) {
if (if_->condition->tag == Var) {
truthy_scope = fresh_scope(env);
const char *varname = Match(if_->condition, Var)->name;
binding_t *b = get_binding(env, varname);
binding_t *nonnull_b = new(binding_t);
*nonnull_b = *b;
nonnull_b->type = Match(cond_t, OptionalType)->type;
nonnull_b->code = compile_optional_into_nonnull(env, b);
set_binding(truthy_scope, varname, nonnull_b);
}
condition = compile_optional_check(env, if_->condition);
} else {
condition = compile(env, if_->condition);
}
CORD code = CORD_all("if (", condition, ")", compile_statement(env, if_->body));
CORD code = CORD_all("if (", condition, ")", compile_statement(truthy_scope, if_->body));
if (if_->else_body)
code = CORD_all(code, "\nelse ", compile_statement(env, if_->else_body));
return code;
@ -1280,6 +1319,7 @@ CORD expr_as_text(env_t *env, CORD expr, type_t *t, CORD color)
case TableType: return CORD_asprintf("Table$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t));
case FunctionType: case ClosureType: return CORD_asprintf("Func$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t));
case PointerType: return CORD_asprintf("Pointer$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t));
case OptionalType: return CORD_asprintf("Optional$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t));
case StructType: case EnumType:
return CORD_asprintf("(%r)->CustomInfo.as_text(stack(%r), %r, %r)",
compile_type_info(env, t), expr, color, compile_type_info(env, t));
@ -1317,8 +1357,6 @@ CORD compile_to_pointer_depth(env_t *env, ast_t *ast, int64_t target_depth, bool
++depth;
} else {
auto ptr = Match(t, PointerType);
if (ptr->is_optional)
code_err(ast, "You can't dereference this value, since it's not guaranteed to be non-null");
val = CORD_all("*(", val, ")");
t = ptr->pointed;
--depth;
@ -1327,8 +1365,6 @@ CORD compile_to_pointer_depth(env_t *env, ast_t *ast, int64_t target_depth, bool
while (t->tag == PointerType) {
auto ptr = Match(t, PointerType);
if (ptr->is_optional)
code_err(ast, "You can't dereference this value, since it's not guaranteed to be non-null");
t = ptr->pointed;
}
@ -1594,7 +1630,18 @@ CORD compile(env_t *env, ast_t *ast)
switch (ast->tag) {
case Nil: {
type_t *t = parse_type_ast(env, Match(ast, Nil)->type);
return CORD_all("((", compile_type(t), ")NULL)");
switch (t->tag) {
case BigIntType: return "NULL_INT";
case BoolType: return "NULL_BOOL";
case ArrayType: return "NULL_ARRAY";
case TableType: return "NULL_TABLE";
case SetType: return "NULL_TABLE";
case TextType: return "NULL_TEXT";
case PointerType: return CORD_all("((", compile_type(t), ")NULL)");
case ClosureType: return "NULL_CLOSURE";
case NumType: return "nan(\"null\")";
default: code_err(ast, "Nil isn't implemented for this type: %T", t);
}
}
case Bool: return Match(ast, Bool)->b ? "yes" : "no";
case Var: {
@ -1668,8 +1715,8 @@ CORD compile(env_t *env, ast_t *ast)
return CORD_all("((", compile(env, value), ").entries.length == 0)");
else if (t->tag == TextType)
return CORD_all("(", compile(env, value), " == CORD_EMPTY)");
else if (t->tag == PointerType && Match(t, PointerType)->is_optional)
return CORD_all("(", compile(env, value), " == NULL)");
else if (t->tag == OptionalType)
return CORD_all("!(", compile_optional_check(env, value), ")");
code_err(ast, "I don't know how to negate values of type %T", t);
}
@ -2058,7 +2105,7 @@ CORD compile(env_t *env, ast_t *ast)
if (!table->entries) {
CORD code = "((Table_t){";
if (table->fallback)
code = CORD_all(code, ".fallback=", compile(env, table->fallback),",");
code = CORD_all(code, ".fallback=heap(", compile(env, table->fallback),")");
return CORD_cat(code, "})");
}
@ -2883,7 +2930,7 @@ CORD compile(env_t *env, ast_t *ast)
"values.data += ", CORD_asprintf("%zu", offset), ";\n"
"values; })");
} else if (streq(f->field, "fallback")) {
return CORD_all("(", compile_to_pointer_depth(env, f->fielded, 0, false), ").fallback");
return CORD_all("({ Table_t *_fallback = (", compile_to_pointer_depth(env, f->fielded, 0, false), ").fallback; _fallback ? *_fallback : NULL_TABLE; })");
}
code_err(ast, "There is no '%s' field on tables", f->field);
}
@ -2903,8 +2950,6 @@ CORD compile(env_t *env, ast_t *ast)
if (indexed_type->tag != PointerType)
code_err(ast, "Only pointers can use the '[]' operator to dereference the entire value.");
auto ptr = Match(indexed_type, PointerType);
if (ptr->is_optional)
code_err(ast, "This pointer is potentially null, so it can't be safely dereferenced");
if (ptr->pointed->tag == ArrayType) {
return CORD_all("({ Array_t *arr = ", compile(env, indexing->indexed), "; ARRAY_INCREF(*arr); *arr; })");
} else if (ptr->pointed->tag == TableType || ptr->pointed->tag == SetType) {
@ -3083,10 +3128,9 @@ CORD compile_type_info(env_t *env, type_t *t)
auto ptr = Match(t, PointerType);
CORD sigil = ptr->is_stack ? "&" : "@";
if (ptr->is_readonly) sigil = CORD_cat(sigil, "%");
return CORD_asprintf("Pointer$info(%r, %r, %s)",
return CORD_asprintf("Pointer$info(%r, %r)",
CORD_quoted(sigil),
compile_type_info(env, ptr->pointed),
ptr->is_optional ? "yes" : "no");
compile_type_info(env, ptr->pointed));
}
case FunctionType: {
return CORD_asprintf("Function$info(%r)", CORD_quoted(type_to_cord(t)));
@ -3094,6 +3138,9 @@ CORD compile_type_info(env_t *env, type_t *t)
case ClosureType: {
return CORD_asprintf("Closure$info(%r)", CORD_quoted(type_to_cord(t)));
}
case OptionalType: {
return CORD_asprintf("Optional$info(%r)", compile_type_info(env, Match(t, OptionalType)->type));
}
case TypeInfoType: return "&TypeInfo$info";
case MemoryType: return "&Memory$info";
case VoidType: return "&Void$info";

View File

@ -287,7 +287,7 @@ env_t *new_compilation_unit(CORD *libname)
)},
{"Shell", Type(TextType, .lang="Shell", .env=namespace_env(env, "Shell")), "Shell_t", "Shell$info", TypedArray(ns_entry_t,
{"escape_text", "Shell$escape_text", "func(text:Text)->Shell"},
{"run", "Shell$run", "func(command:Shell, status=!&Int32?)->Text"},
{"run", "Shell$run", "func(command:Shell, status=!&Int32)->Text"},
)},
{"Text", TEXT_TYPE, "Text_t", "Text$info", TypedArray(ns_entry_t,
{"as_c_string", "Text$as_c_string", "func(text:Text)->CString"},

65
parse.c
View File

@ -84,6 +84,7 @@ static ast_t *parse_comprehension_suffix(parse_ctx_t *ctx, ast_t *lhs);
static ast_t *parse_optional_conditional_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 type_ast_t *parse_non_optional_type(parse_ctx_t *ctx, const char *pos);
static type_ast_t *parse_type(parse_ctx_t *ctx, const char *pos);
static PARSER(parse_array);
static PARSER(parse_block);
@ -576,10 +577,14 @@ type_ast_t *parse_pointer_type(parse_ctx_t *ctx, const char *pos) {
spaces(&pos);
bool is_readonly = match(&pos, "%");
spaces(&pos);
type_ast_t *type = expect(ctx, start, &pos, parse_type,
type_ast_t *type = expect(ctx, start, &pos, parse_non_optional_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);
type_ast_t *ptr_type = NewTypeAST(ctx->file, start, pos, PointerTypeAST, .pointed=type, .is_stack=is_stack, .is_readonly=is_readonly);
spaces(&pos);
if (match(&pos, "?"))
return NewTypeAST(ctx->file, start, pos, OptionalTypeAST, .type=ptr_type);
else
return ptr_type;
}
type_ast_t *parse_type_name(parse_ctx_t *ctx, const char *pos) {
@ -598,7 +603,7 @@ type_ast_t *parse_type_name(parse_ctx_t *ctx, const char *pos) {
return NewTypeAST(ctx->file, start, pos, VarTypeAST, .name=id);
}
type_ast_t *parse_type(parse_ctx_t *ctx, const char *pos) {
type_ast_t *parse_non_optional_type(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
type_ast_t *type = NULL;
bool success = (false
@ -620,12 +625,20 @@ type_ast_t *parse_type(parse_ctx_t *ctx, const char *pos) {
type->end = pos;
}
if (!type) return NULL;
pos = type->end;
return type;
}
type_ast_t *parse_type(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
type_ast_t *type = parse_non_optional_type(ctx, pos);
pos = type->end;
spaces(&pos);
if (match(&pos, "?"))
return NewTypeAST(ctx->file, start, pos, OptionalTypeAST, .type=type);
else
return type;
}
PARSER(parse_num) {
const char *start = pos;
bool negative = match(&pos, "-");
@ -749,7 +762,7 @@ PARSER(parse_table) {
return NULL;
value_type = expect(ctx, pos-1, &pos, parse_type, "I couldn't parse a value type for this table");
whitespace(&pos);
match(&pos, ";");
match(&pos, ",");
}
for (;;) {
@ -891,6 +904,7 @@ PARSER(parse_reduction) {
|| (new_term=parse_field_suffix(ctx, key))
|| (new_term=parse_method_call_suffix(ctx, key))
|| (new_term=parse_fncall_suffix(ctx, key))
|| (new_term=parse_optional_suffix(ctx, key))
);
if (progress) key = new_term;
}
@ -1134,20 +1148,44 @@ PARSER(parse_heap_alloc) {
const char *start = pos;
if (!match(&pos, "@")) return NULL;
spaces(&pos);
ast_t *val = expect(ctx, start, &pos, parse_term, "I expected an expression for this '@'");
ast_t *val = expect(ctx, start, &pos, parse_term_no_suffix, "I expected an expression for this '@'");
for (;;) {
ast_t *new_term;
if ((new_term=parse_index_suffix(ctx, val))
|| (new_term=parse_fncall_suffix(ctx, val))
|| (new_term=parse_field_suffix(ctx, val))) {
val = new_term;
} else break;
}
pos = val->end;
ast_t *ast = NewAST(ctx->file, start, pos, HeapAllocate, .value=val);
ast_t *optional = parse_optional_suffix(ctx, ast);
return optional ? optional : ast;
if (optional) ast = optional;
return ast;
}
PARSER(parse_stack_reference) {
const char *start = pos;
if (!match(&pos, "&")) return NULL;
spaces(&pos);
ast_t *val = expect(ctx, start, &pos, parse_term, "I expected an expression for this '&'");
ast_t *val = expect(ctx, start, &pos, parse_term_no_suffix, "I expected an expression for this '&'");
for (;;) {
ast_t *new_term;
if ((new_term=parse_index_suffix(ctx, val))
|| (new_term=parse_fncall_suffix(ctx, val))
|| (new_term=parse_field_suffix(ctx, val))) {
val = new_term;
} else break;
}
pos = val->end;
ast_t *ast = NewAST(ctx->file, start, pos, StackReference, .value=val);
ast_t *optional = parse_optional_suffix(ctx, ast);
return optional ? optional : ast;
if (optional) ast = optional;
return ast;
}
PARSER(parse_not) {
@ -1425,6 +1463,7 @@ PARSER(parse_lambda) {
spaces(&pos);
expect_closing(ctx, &pos, ")", "I was expecting a ')' to finish this anonymous function's arguments");
ast_t *body = optional(ctx, &pos, parse_block);
if (!body) body = NewAST(ctx->file, pos, pos, Block, .statements=NULL);
return NewAST(ctx->file, start, pos, Lambda, .id=ctx->next_lambda_id++, .args=args, .body=body);
}
@ -1488,6 +1527,7 @@ PARSER(parse_term) {
|| (new_term=parse_field_suffix(ctx, term))
|| (new_term=parse_method_call_suffix(ctx, term))
|| (new_term=parse_fncall_suffix(ctx, term))
|| (new_term=parse_optional_suffix(ctx, term))
);
if (progress) term = new_term;
}
@ -1630,6 +1670,7 @@ static ast_t *parse_infix_expr(parse_ctx_t *ctx, const char *pos, int min_tightn
|| (new_term=parse_field_suffix(ctx, key))
|| (new_term=parse_method_call_suffix(ctx, key))
|| (new_term=parse_fncall_suffix(ctx, key))
|| (new_term=parse_optional_suffix(ctx, key))
);
if (progress) key = new_term;
}

4
repl.c
View File

@ -139,7 +139,7 @@ const TypeInfo *type_to_type_info(type_t *t)
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, .pointed=pointed_info, .is_optional=ptr->is_optional}};
.tag=PointerInfo, .PointerInfo={.sigil=sigil, .pointed=pointed_info}};
return memcpy(GC_MALLOC(sizeof(TypeInfo)), &pointer_info, sizeof(TypeInfo));
}
default: errx(1, "Unsupported type: %T", t);
@ -494,8 +494,6 @@ void eval(env_t *env, ast_t *ast, void *dest)
}
case PointerType: {
auto ptr = Match(indexed_t, PointerType);
if (ptr->is_optional)
repl_err(ast, "You can't dereference an optional pointer because it might be null");
size_t pointed_size = type_size(ptr->pointed);
void *pointer;
eval(env, index->indexed, &pointer);

View File

@ -168,8 +168,8 @@ func main():
>> [10, 20, 30]:find(999)
= 0
>> [10, 20]:first(func(i:&Int): i:is_prime())
= !Int
>> [4, 5, 6]:first(func(i:&Int): i:is_prime())
= @%5?
# >> [10, 20]:first(func(i:&Int): i:is_prime())
# = !Int
# >> [4, 5, 6]:first(func(i:&Int): i:is_prime())
# = @%5?

View File

@ -1,15 +1,123 @@
func maybe_int(should_i:Bool)->Int?:
if should_i:
return 123
else:
return !Int
func maybe_array(should_i:Bool)->[Int]?:
if should_i:
return [10, 20, 30]
else:
return ![Int]
func maybe_bool(should_i:Bool)->Bool?:
if should_i:
return no
else:
return !Bool
func maybe_text(should_i:Bool)->Text?:
if should_i:
return "Hello"
else:
return !Text
func maybe_num(should_i:Bool)->Num?:
if should_i:
return 12.3
else:
return !Num
func maybe_lambda(should_i:Bool)-> func()?:
if should_i:
return func(): say("hi!")
else:
return !func()
func main():
>> opt := @5?
when opt is @nonnull:
>> nonnull[]
= 5
else:
fail("Oops")
>> 5?
= 5? : Int?
>> opt = !@Int
when opt is @nonnull:
fail("Oops")
>> if no:
!Int
else:
>> opt
5
= 5? : Int?
do:
!! Ints:
>> yep := maybe_int(yes)
= 123?
>> nope := maybe_int(no)
= !Int
>> if yep: >> yep
else: fail("Falsey: $yep")
>> if nope:
fail("Truthy: $nope")
else: !! Falsey: $nope
do:
!! ...
!! Arrays:
>> yep := maybe_array(yes)
= [10, 20, 30]?
>> nope := maybe_array(no)
= ![Int]
>> if yep: >> yep
else: fail("Falsey: $yep")
>> if nope:
fail("Truthy: $nope")
else: !! Falsey: $nope
do:
!! ...
!! Bools:
>> yep := maybe_bool(yes)
= no?
>> nope := maybe_bool(no)
= !Bool
>> if yep: >> yep
else: fail("Falsey: $yep")
>> if nope:
fail("Truthy: $nope")
else: !! Falsey: $nope
do:
!! ...
!! Text:
>> yep := maybe_text(yes)
= "Hello"?
>> nope := maybe_text(no)
= !Text
>> if yep: >> yep
else: fail("Falsey: $yep")
>> if nope:
fail("Truthy: $nope")
else: !! Falsey: $nope
do:
!! ...
!! Nums:
>> yep := maybe_num(yes)
= 12.3?
>> nope := maybe_num(no)
= !Num
>> if yep: >> yep
else: fail("Falsey: $yep")
>> if nope:
fail("Truthy: $nope")
else: !! Falsey: $nope
do:
!! ...
!! Lambdas:
>> yep := maybe_lambda(yes)
= func(): ...?
>> nope := maybe_lambda(no)
= !func()
>> if yep: >> yep
else: fail("Falsey: $yep")
>> if nope:
fail("Truthy: $nope")
else: !! Falsey: $nope

View File

@ -38,7 +38,7 @@ func main():
>> t2.length
= 1
>> t2.fallback
= @%{"one":1, "two":2}?
= {"one":1, "two":2}?
t2_str := ""
for k,v in t2:

View File

@ -43,7 +43,7 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast)
type_t *pointed_t = parse_type_ast(env, ptr->pointed);
if (pointed_t->tag == VoidType)
code_err(ast, "Void pointers are not supported. You probably meant 'Memory' instead of 'Void'");
return Type(PointerType, .is_optional=ptr->is_optional, .pointed=pointed_t, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly);
return Type(PointerType, .pointed=pointed_t, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly);
}
case ArrayTypeAST: {
type_ast_t *item_type = Match(ast, ArrayTypeAST)->item;
@ -109,6 +109,13 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast)
REVERSE_LIST(type_args);
return Type(ClosureType, Type(FunctionType, .args=type_args, .ret=ret_t));
}
case OptionalTypeAST: {
auto opt = Match(ast, OptionalTypeAST);
type_t *t = parse_type_ast(env, opt->type);
if (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType)
code_err(ast, "Optional %T types are not supported.", t);
return Type(OptionalType, .type=t);
}
case UnknownTypeAST: code_err(ast, "I don't know how to get this type");
}
errx(1, "Unreachable");
@ -438,60 +445,43 @@ type_t *get_method_type(env_t *env, ast_t *self, const char *name)
type_t *get_clause_type(env_t *env, type_t *subject_t, when_clause_t *clause)
{
if (subject_t->tag == PointerType) {
if (!Match(subject_t, PointerType)->is_optional)
code_err(clause->body, "This %T pointer type is not optional, so this 'when' statement is tautological", subject_t);
assert(subject_t->tag == EnumType);
tag_t * const tags = Match(subject_t, EnumType)->tags;
const char *tag_name = Match(clause->tag_name, Var)->name;
if (!streq(tag_name, "@"))
code_err(clause->tag_name, "'when' clauses on optional pointers only support @var, not tags like '%s'", tag_name);
assert(clause->args);
env_t *scope = fresh_scope(env);
auto ptr = Match(subject_t, PointerType);
set_binding(scope, Match(clause->args->ast, Var)->name,
new(binding_t, .type=Type(PointerType, .pointed=ptr->pointed, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly)));
return get_type(scope, clause->body);
} else {
assert(subject_t->tag == EnumType);
tag_t * const tags = Match(subject_t, EnumType)->tags;
const char *tag_name = Match(clause->tag_name, Var)->name;
type_t *tag_type = NULL;
for (tag_t *tag = tags; tag; tag = tag->next) {
if (streq(tag->name, tag_name)) {
tag_type = tag->type;
break;
}
const char *tag_name = Match(clause->tag_name, Var)->name;
type_t *tag_type = NULL;
for (tag_t *tag = tags; tag; tag = tag->next) {
if (streq(tag->name, tag_name)) {
tag_type = tag->type;
break;
}
if (!tag_type)
code_err(clause->tag_name, "There is no tag '%s' for the type %T", tag_name, subject_t);
// Don't return early so we validate the tags
if (!clause->args)
return get_type(env, clause->body);
env_t *scope = fresh_scope(env);
auto tag_struct = Match(tag_type, StructType);
if (!clause->args->next && tag_struct->fields && tag_struct->fields->next) {
set_binding(scope, Match(clause->args->ast, Var)->name, new(binding_t, .type=tag_type));
} else {
ast_list_t *var = clause->args;
arg_t *field = tag_struct->fields;
while (var || field) {
if (!var)
code_err(clause->tag_name, "The field %T.%s.%s wasn't accounted for", subject_t, tag_name, field->name);
if (!field)
code_err(var->ast, "This is one more field than %T has", subject_t);
set_binding(scope, Match(var->ast, Var)->name, new(binding_t, .type=field->type));
var = var->next;
field = field->next;
}
}
return get_type(scope, clause->body);
}
if (!tag_type)
code_err(clause->tag_name, "There is no tag '%s' for the type %T", tag_name, subject_t);
// Don't return early so we validate the tags
if (!clause->args)
return get_type(env, clause->body);
env_t *scope = fresh_scope(env);
auto tag_struct = Match(tag_type, StructType);
if (!clause->args->next && tag_struct->fields && tag_struct->fields->next) {
set_binding(scope, Match(clause->args->ast, Var)->name, new(binding_t, .type=tag_type));
} else {
ast_list_t *var = clause->args;
arg_t *field = tag_struct->fields;
while (var || field) {
if (!var)
code_err(clause->tag_name, "The field %T.%s.%s wasn't accounted for", subject_t, tag_name, field->name);
if (!field)
code_err(var->ast, "This is one more field than %T has", subject_t);
set_binding(scope, Match(var->ast, Var)->name, new(binding_t, .type=field->type));
var = var->next;
field = field->next;
}
}
return get_type(scope, clause->body);
}
type_t *get_type(env_t *env, ast_t *ast)
@ -500,10 +490,7 @@ type_t *get_type(env_t *env, ast_t *ast)
switch (ast->tag) {
case Nil: {
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);
return Type(OptionalType, .type=t);
}
case Bool: {
return Type(BoolType);
@ -533,7 +520,7 @@ type_t *get_type(env_t *env, ast_t *ast)
type_t *pointed = get_type(env, Match(ast, HeapAllocate)->value);
if (has_stack_memory(pointed))
code_err(ast, "Stack references cannot be moved to the heap because they may outlive the stack frame they were created in.");
return Type(PointerType, .is_optional=false, .pointed=pointed);
return Type(PointerType, .pointed=pointed);
}
case StackReference: {
// Supported:
@ -558,10 +545,10 @@ type_t *get_type(env_t *env, ast_t *ast)
type_t *ref_type = get_type(env, value);
type_t *base_type = get_type(env, base);
if (base_type->tag == PointerType) {
if (base_type->tag == OptionalType) {
code_err(base, "This value might be null, so it can't be safely dereferenced");
} else if (base_type->tag == PointerType) {
auto ptr = Match(base_type, PointerType);
if (ptr->is_optional)
code_err(base, "This value might be null, so it can't be safely dereferenced");
return Type(PointerType, .pointed=ref_type, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly);
} else if (base->tag == Var) {
return Type(PointerType, .pointed=ref_type, .is_stack=true);
@ -573,12 +560,9 @@ type_t *get_type(env_t *env, ast_t *ast)
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)
if (t->tag == OptionalType)
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);
return Type(OptionalType, .type=t);
}
case TextLiteral: return TEXT_TYPE;
case TextJoin: {
@ -740,12 +724,11 @@ type_t *get_type(env_t *env, ast_t *ast)
case Index: {
auto indexing = Match(ast, Index);
type_t *indexed_t = get_type(env, indexing->indexed);
if (indexed_t->tag == PointerType && !indexing->index) {
auto ptr = Match(indexed_t, PointerType);
if (ptr->is_optional)
code_err(ast, "You're attempting to dereference a pointer whose type indicates it could be nil");
return ptr->pointed;
}
if (indexed_t->tag == OptionalType && !indexing->index)
code_err(ast, "You're attempting to dereference a value whose type indicates it could be nil");
if (indexed_t->tag == PointerType && !indexing->index)
return Match(indexed_t, PointerType)->pointed;
type_t *value_t = value_type(indexed_t);
if (value_t->tag == ArrayType) {
@ -791,7 +774,7 @@ type_t *get_type(env_t *env, ast_t *ast)
else if (streq(call->name, "clear")) return Type(VoidType);
else if (streq(call->name, "counts")) return Type(TableType, .key_type=item_type, .value_type=INT_TYPE);
else if (streq(call->name, "find")) return INT_TYPE;
else if (streq(call->name, "first")) return Type(PointerType, .pointed=item_type, .is_optional=true, .is_readonly=true);
else if (streq(call->name, "first")) return Type(OptionalType, .type=Type(PointerType, .pointed=item_type, .is_readonly=true));
else if (streq(call->name, "from")) return self_value_t;
else if (streq(call->name, "has")) return Type(BoolType);
else if (streq(call->name, "heap_pop")) return item_type;
@ -845,8 +828,7 @@ type_t *get_type(env_t *env, ast_t *ast)
code_err(ast, "The table method :get_or_null() is only supported for tables whose value type is a pointer, not %T",
table->value_type);
auto ptr = Match(table->value_type, PointerType);
return Type(PointerType, .pointed=ptr->pointed, .is_stack=ptr->is_stack,
.is_readonly=ptr->is_readonly, .is_optional=true);
return Type(OptionalType, .type=Type(PointerType, .pointed=ptr->pointed, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly));
} else if (streq(call->name, "has")) return Type(BoolType);
else if (streq(call->name, "remove")) return Type(VoidType);
else if (streq(call->name, "set")) return Type(VoidType);
@ -939,7 +921,7 @@ type_t *get_type(env_t *env, ast_t *ast)
type_t *t = get_type(env, Match(ast, Not)->value);
if (t->tag == IntType || t->tag == NumType || t->tag == BoolType)
return t;
if (t->tag == PointerType && Match(t, PointerType)->is_optional)
if (t->tag == OptionalType)
return Type(BoolType);
ast_t *value = Match(ast, Not)->value;
@ -1005,12 +987,14 @@ type_t *get_type(env_t *env, ast_t *ast)
return lhs_t;
} else if (rhs_t->tag == AbortType || rhs_t->tag == ReturnType) {
return lhs_t;
} else if (rhs_t->tag == OptionalType) {
if (can_promote(lhs_t, rhs_t))
return rhs_t;
} else if (lhs_t->tag == PointerType && rhs_t->tag == PointerType) {
auto lhs_ptr = Match(lhs_t, PointerType);
auto rhs_ptr = Match(rhs_t, PointerType);
if (type_eq(lhs_ptr->pointed, rhs_ptr->pointed))
return Type(PointerType, .pointed=lhs_ptr->pointed, .is_optional=lhs_ptr->is_optional || rhs_ptr->is_optional,
.is_readonly=lhs_ptr->is_readonly || rhs_ptr->is_readonly);
return Type(PointerType, .pointed=lhs_ptr->pointed, .is_readonly=lhs_ptr->is_readonly || rhs_ptr->is_readonly);
} else if (is_int_type(lhs_t) && is_int_type(rhs_t)) {
return get_math_type(env, ast, lhs_t, rhs_t);
}
@ -1024,15 +1008,17 @@ type_t *get_type(env_t *env, ast_t *ast)
return lhs_t;
} else if (is_int_type(lhs_t) && is_int_type(rhs_t)) {
return get_math_type(env, ast, lhs_t, rhs_t);
} else if (lhs_t->tag == OptionalType) {
if (can_promote(rhs_t, lhs_t))
return rhs_t;
} else if (lhs_t->tag == PointerType) {
auto lhs_ptr = Match(lhs_t, PointerType);
if (rhs_t->tag == AbortType || rhs_t->tag == ReturnType) {
return Type(PointerType, .pointed=lhs_ptr->pointed, .is_optional=false, .is_readonly=lhs_ptr->is_readonly);
return Type(PointerType, .pointed=lhs_ptr->pointed, .is_readonly=lhs_ptr->is_readonly);
} else if (rhs_t->tag == PointerType) {
auto rhs_ptr = Match(rhs_t, PointerType);
if (type_eq(rhs_ptr->pointed, lhs_ptr->pointed))
return Type(PointerType, .pointed=lhs_ptr->pointed, .is_optional=lhs_ptr->is_optional && rhs_ptr->is_optional,
.is_readonly=lhs_ptr->is_readonly || rhs_ptr->is_readonly);
return Type(PointerType, .pointed=lhs_ptr->pointed, .is_readonly=lhs_ptr->is_readonly || rhs_ptr->is_readonly);
}
}
code_err(ast, "I can't figure out the type of this `or` expression because the left side is a %T, but the right side is a %T",
@ -1157,7 +1143,6 @@ type_t *get_type(env_t *env, ast_t *ast)
REVERSE_LIST(args);
type_t *ret = get_type(scope, lambda->body);
assert(ret);
if (ret->tag == ReturnType)
ret = Match(ret, ReturnType)->ret;
if (ret->tag == AbortType)
@ -1192,104 +1177,77 @@ type_t *get_type(env_t *env, ast_t *ast)
auto when = Match(ast, When);
type_t *subject_t = get_type(env, when->subject);
type_t *overall_t = NULL;
if (subject_t->tag == PointerType) {
if (!Match(subject_t, PointerType)->is_optional)
code_err(when->subject, "This %T pointer type is not optional, so this 'when' statement is tautological", subject_t);
bool handled_at = false;
for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
const char *tag_name = Match(clause->tag_name, Var)->name;
if (!streq(tag_name, "@"))
code_err(clause->tag_name, "'when' clauses on optional pointers only support @var, not tags like '%s'", tag_name);
if (handled_at)
code_err(clause->tag_name, "This 'when' statement has already handled the case of non-null pointers!");
handled_at = true;
assert(clause->args);
type_t *clause_type = get_clause_type(env, subject_t, clause);
type_t *merged = type_or_type(overall_t, clause_type);
if (!merged)
code_err(clause->body, "The type of this branch is %T, which conflicts with the earlier branch type of %T",
clause_type, overall_t);
overall_t = merged;
}
if (!handled_at)
code_err(ast, "This 'when' statement doesn't handle non-null pointers");
if (!when->else_body)
code_err(ast, "This 'when' statement doesn't handle null pointers");
return overall_t;
} else if (subject_t->tag == EnumType) {
tag_t * const tags = Match(subject_t, EnumType)->tags;
typedef struct match_s {
tag_t *tag;
bool handled;
struct match_s *next;
} match_t;
match_t *matches = NULL;
for (tag_t *tag = tags; tag; tag = tag->next)
matches = new(match_t, .tag=tag, .handled=false, .next=matches);
for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
const char *tag_name = Match(clause->tag_name, Var)->name;
CORD valid_tags = CORD_EMPTY;
for (match_t *m = matches; m; m = m->next) {
if (streq(m->tag->name, tag_name)) {
if (m->handled)
code_err(clause->tag_name, "This tag was already handled earlier");
m->handled = true;
goto found_matching_tag;
}
if (valid_tags) valid_tags = CORD_cat(valid_tags, ", ");
valid_tags = CORD_cat(valid_tags, m->tag->name);
}
code_err(clause->tag_name, "There is no tag '%s' for the type %T (valid tags: %s)",
tag_name, subject_t, CORD_to_char_star(valid_tags));
found_matching_tag:;
}
for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
type_t *clause_type = get_clause_type(env, subject_t, clause);
type_t *merged = type_or_type(overall_t, clause_type);
if (!merged)
code_err(clause->body, "The type of this branch is %T, which conflicts with the earlier branch type of %T",
clause_type, overall_t);
overall_t = merged;
}
if (when->else_body) {
bool any_unhandled = false;
for (match_t *m = matches; m; m = m->next) {
if (!m->handled) {
any_unhandled = true;
break;
}
}
// HACK: `while when ...` is handled by the parser adding an implicit
// `else: stop`, which has an empty source code span.
if (!any_unhandled && when->else_body->end > when->else_body->start)
code_err(when->else_body, "This 'else' block will never run because every tag is handled");
type_t *else_t = get_type(env, when->else_body);
type_t *merged = type_or_type(overall_t, else_t);
if (!merged)
code_err(when->else_body,
"I was expecting this block to have a %T value (based on earlier clauses), but it actually has a %T value.",
overall_t, else_t);
return merged;
} else {
CORD unhandled = CORD_EMPTY;
for (match_t *m = matches; m; m = m->next) {
if (!m->handled)
unhandled = unhandled ? CORD_all(unhandled, ", ", m->tag->name) : m->tag->name;
}
if (unhandled)
code_err(ast, "This 'when' statement doesn't handle the tags: %s", CORD_to_const_char_star(unhandled));
return overall_t;
}
} else {
if (subject_t->tag != EnumType)
code_err(when->subject, "'when' statements are only for enum types and optional pointers, not %T", subject_t);
tag_t * const tags = Match(subject_t, EnumType)->tags;
typedef struct match_s {
tag_t *tag;
bool handled;
struct match_s *next;
} match_t;
match_t *matches = NULL;
for (tag_t *tag = tags; tag; tag = tag->next)
matches = new(match_t, .tag=tag, .handled=false, .next=matches);
for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
const char *tag_name = Match(clause->tag_name, Var)->name;
CORD valid_tags = CORD_EMPTY;
for (match_t *m = matches; m; m = m->next) {
if (streq(m->tag->name, tag_name)) {
if (m->handled)
code_err(clause->tag_name, "This tag was already handled earlier");
m->handled = true;
goto found_matching_tag;
}
if (valid_tags) valid_tags = CORD_cat(valid_tags, ", ");
valid_tags = CORD_cat(valid_tags, m->tag->name);
}
code_err(clause->tag_name, "There is no tag '%s' for the type %T (valid tags: %s)",
tag_name, subject_t, CORD_to_char_star(valid_tags));
found_matching_tag:;
}
for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
type_t *clause_type = get_clause_type(env, subject_t, clause);
type_t *merged = type_or_type(overall_t, clause_type);
if (!merged)
code_err(clause->body, "The type of this branch is %T, which conflicts with the earlier branch type of %T",
clause_type, overall_t);
overall_t = merged;
}
if (when->else_body) {
bool any_unhandled = false;
for (match_t *m = matches; m; m = m->next) {
if (!m->handled) {
any_unhandled = true;
break;
}
}
// HACK: `while when ...` is handled by the parser adding an implicit
// `else: stop`, which has an empty source code span.
if (!any_unhandled && when->else_body->end > when->else_body->start)
code_err(when->else_body, "This 'else' block will never run because every tag is handled");
type_t *else_t = get_type(env, when->else_body);
type_t *merged = type_or_type(overall_t, else_t);
if (!merged)
code_err(when->else_body,
"I was expecting this block to have a %T value (based on earlier clauses), but it actually has a %T value.",
overall_t, else_t);
return merged;
} else {
CORD unhandled = CORD_EMPTY;
for (match_t *m = matches; m; m = m->next) {
if (!m->handled)
unhandled = unhandled ? CORD_all(unhandled, ", ", m->tag->name) : m->tag->name;
}
if (unhandled)
code_err(ast, "This 'when' statement doesn't handle the tags: %s", CORD_to_const_char_star(unhandled));
return overall_t;
}
}

163
types.c
View File

@ -65,12 +65,15 @@ CORD type_to_cord(type_t *t) {
auto ptr = Match(t, PointerType);
CORD sigil = ptr->is_stack ? "&" : "@";
if (ptr->is_readonly) sigil = CORD_cat(sigil, "%");
return CORD_all(sigil, type_to_cord(ptr->pointed), ptr->is_optional ? "?" : CORD_EMPTY);
return CORD_all(sigil, type_to_cord(ptr->pointed));
}
case EnumType: {
auto tagged = Match(t, EnumType);
return tagged->name;
}
case OptionalType: {
return CORD_all(type_to_cord(Match(t, OptionalType)->type), "?");
}
case TypeInfoType: {
return CORD_all("TypeInfo(", Match(t, TypeInfoType)->name, ")");
}
@ -111,21 +114,20 @@ bool type_eq(type_t *a, type_t *b)
bool type_is_a(type_t *t, type_t *req)
{
if (type_eq(t, req)) return true;
if (req->tag == OptionalType)
return type_is_a(t, Match(req, OptionalType)->type);
if (t->tag == PointerType && req->tag == PointerType) {
auto t_ptr = Match(t, PointerType);
auto req_ptr = Match(req, PointerType);
if (type_eq(t_ptr->pointed, req_ptr->pointed))
return (!t_ptr->is_stack && !t_ptr->is_optional && req_ptr->is_stack)
|| (!t_ptr->is_stack && req_ptr->is_optional);
return (!t_ptr->is_stack && req_ptr->is_stack) || (!t_ptr->is_stack);
}
return false;
}
static type_t *non_optional(type_t *t)
{
if (t->tag != PointerType) return t;
auto ptr = Match(t, PointerType);
return ptr->is_optional ? Type(PointerType, .is_optional=false, .pointed=ptr->pointed) : t;
return t->tag == OptionalType ? Match(t, OptionalType)->type : t;
}
PUREFUNC type_t *value_type(type_t *t)
@ -217,6 +219,7 @@ PUREFUNC bool has_heap_memory(type_t *t)
case TableType: return true;
case SetType: return true;
case PointerType: return true;
case OptionalType: return has_heap_memory(Match(t, OptionalType)->type);
case BigIntType: return true;
case StructType: {
for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
@ -243,6 +246,7 @@ PUREFUNC bool can_send_over_channel(type_t *t)
case ChannelType: return true;
case TableType: return true;
case PointerType: return false;
case OptionalType: return can_send_over_channel(Match(t, OptionalType)->type);
case StructType: {
for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
if (!can_send_over_channel(field->type))
@ -265,6 +269,7 @@ PUREFUNC bool has_stack_memory(type_t *t)
{
switch (t->tag) {
case PointerType: return Match(t, PointerType)->is_stack;
case OptionalType: return has_stack_memory(Match(t, OptionalType)->type);
default: return false;
}
}
@ -294,11 +299,13 @@ PUREFUNC bool can_promote(type_t *actual, type_t *needed)
return true;
// Automatic dereferencing:
if (actual->tag == PointerType && !Match(actual, PointerType)->is_optional
&& can_promote(Match(actual, PointerType)->pointed, needed))
if (actual->tag == PointerType && can_promote(Match(actual, PointerType)->pointed, needed))
return true;
// Optional promotion:
if (needed->tag == OptionalType && can_promote(actual, Match(needed, OptionalType)->type))
return true;
if (needed->tag == PointerType && actual->tag == PointerType) {
auto needed_ptr = Match(needed, PointerType);
auto actual_ptr = Match(actual, PointerType);
@ -309,9 +316,6 @@ PUREFUNC bool can_promote(type_t *actual, type_t *needed)
else if (actual_ptr->is_stack && !needed_ptr->is_stack)
// Can't use &x for a function that wants a @Foo or ?Foo
return false;
else if (actual_ptr->is_optional && !needed_ptr->is_optional)
// Can't use !Foo for a function that wants @Foo
return false;
else if (actual_ptr->is_readonly && !needed_ptr->is_readonly)
// Can't use pointer to readonly data when we need a pointer that can write to the data
return false;
@ -354,66 +358,6 @@ PUREFUNC bool can_promote(type_t *actual, type_t *needed)
return false;
}
PUREFUNC bool can_leave_uninitialized(type_t *t)
{
switch (t->tag) {
case PointerType: return Match(t, PointerType)->is_optional;
case ArrayType: case IntType: case NumType: case BoolType:
return true;
case ChannelType: return false;
case StructType: {
for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
if (!can_leave_uninitialized(field->type))
return false;
}
return true;
}
case EnumType: {
for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
if (tag->type && !can_leave_uninitialized(tag->type))
return false;
}
return true;
}
default: return false;
}
}
PUREFUNC static bool _can_have_cycles(type_t *t, Table_t *seen)
{
switch (t->tag) {
case ArrayType: return _can_have_cycles(Match(t, ArrayType)->item_type, seen);
case ChannelType: return _can_have_cycles(Match(t, ChannelType)->item_type, seen);
case TableType: {
auto table = Match(t, TableType);
return _can_have_cycles(table->key_type, seen) || _can_have_cycles(table->value_type, seen);
}
case SetType: return _can_have_cycles(Match(t, SetType)->item_type, seen);
case StructType: {
for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) {
if (_can_have_cycles(field->type, seen))
return true;
}
return false;
}
case PointerType: return _can_have_cycles(Match(t, PointerType)->pointed, seen);
case EnumType: {
for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
if (tag->type && _can_have_cycles(tag->type, seen))
return true;
}
return false;
}
default: return false;
}
}
PUREFUNC bool can_have_cycles(type_t *t)
{
Table_t seen = {0};
return _can_have_cycles(t, &seen);
}
PUREFUNC bool is_int_type(type_t *t)
{
return t->tag == IntType || t->tag == BigIntType;
@ -424,48 +368,15 @@ PUREFUNC bool is_numeric_type(type_t *t)
return t->tag == IntType || t->tag == BigIntType || t->tag == NumType;
}
type_t *replace_type(type_t *t, type_t *target, type_t *replacement)
PUREFUNC bool supports_optionals(type_t *t)
{
if (type_eq(t, target))
return replacement;
#define COPY(t) memcpy(GC_MALLOC(sizeof(type_t)), (t), sizeof(type_t))
#define REPLACED_MEMBER(t, tag, member) ({ t = memcpy(GC_MALLOC(sizeof(type_t)), (t), sizeof(type_t)); Match((struct type_s*)(t), tag)->member = replace_type(Match((t), tag)->member, target, replacement); t; })
switch (t->tag) {
case ArrayType: return REPLACED_MEMBER(t, ArrayType, item_type);
case SetType: return REPLACED_MEMBER(t, SetType, item_type);
case ChannelType: return REPLACED_MEMBER(t, ChannelType, item_type);
case TableType: {
t = REPLACED_MEMBER(t, TableType, key_type);
t = REPLACED_MEMBER(t, TableType, value_type);
return t;
}
case FunctionType: {
auto fn = Match(t, FunctionType);
t = REPLACED_MEMBER(t, FunctionType, ret);
arg_t *args = LIST_MAP(fn->args, old_arg, .type=replace_type(old_arg->type, target, replacement));
Match((struct type_s*)t, FunctionType)->args = args;
return t;
}
case StructType: {
auto struct_ = Match(t, StructType);
arg_t *fields = LIST_MAP(struct_->fields, field, .type=replace_type(field->type, target, replacement));
t = COPY(t);
Match((struct type_s*)t, StructType)->fields = fields;
return t;
}
case PointerType: return REPLACED_MEMBER(t, PointerType, pointed);
case EnumType: {
auto tagged = Match(t, EnumType);
tag_t *tags = LIST_MAP(tagged->tags, tag, .type=replace_type(tag->type, target, replacement));
t = COPY(t);
Match((struct type_s*)t, EnumType)->tags = tags;
return t;
}
default: return t;
case BoolType: case CStringType: case BigIntType: case NumType: case TextType:
case ArrayType: case SetType: case TableType: case FunctionType: case ClosureType:
case PointerType:
return true;
default: return false;
}
#undef COPY
#undef REPLACED_MEMBER
}
PUREFUNC size_t type_size(type_t *t)
@ -495,6 +406,20 @@ PUREFUNC size_t type_size(type_t *t)
case FunctionType: return sizeof(void*);
case ClosureType: return sizeof(struct {void *fn, *userdata;});
case PointerType: return sizeof(void*);
case OptionalType: {
type_t *nonnull = Match(t, OptionalType)->type;
switch (nonnull->tag) {
case IntType:
switch (Match(t, IntType)->bits) {
case TYPE_IBITS64: return sizeof(struct { int64_t x; bool is_null; });
case TYPE_IBITS32: return sizeof(int64_t);
case TYPE_IBITS16: return sizeof(int32_t);
case TYPE_IBITS8: return sizeof(int16_t);
default: errx(1, "Invalid integer bit size");
}
default: return type_size(nonnull);
}
}
case StructType: {
arg_t *fields = Match(t, StructType)->fields;
size_t size = t->tag == StructType ? 0 : sizeof(void*);
@ -558,6 +483,20 @@ PUREFUNC size_t type_align(type_t *t)
case FunctionType: return __alignof__(void*);
case ClosureType: return __alignof__(struct {void *fn, *userdata;});
case PointerType: return __alignof__(void*);
case OptionalType: {
type_t *nonnull = Match(t, OptionalType)->type;
switch (nonnull->tag) {
case IntType:
switch (Match(t, IntType)->bits) {
case TYPE_IBITS64: return __alignof__(struct { int64_t x; bool is_null; });
case TYPE_IBITS32: return __alignof__(int64_t);
case TYPE_IBITS16: return __alignof__(int32_t);
case TYPE_IBITS8: return __alignof__(int16_t);
default: errx(1, "Invalid integer bit size");
}
default: return type_align(nonnull);
}
}
case StructType: {
arg_t *fields = Match(t, StructType)->fields;
size_t align = t->tag == StructType ? 0 : sizeof(void*);
@ -633,9 +572,9 @@ type_t *get_field_type(type_t *t, const char *field_name)
else if (streq(field_name, "values"))
return Type(ArrayType, Match(t, TableType)->value_type);
else if (streq(field_name, "default"))
return Type(PointerType, .pointed=Match(t, TableType)->value_type, .is_readonly=true, .is_optional=true);
return Type(OptionalType, Match(t, TableType)->value_type);
else if (streq(field_name, "fallback"))
return Type(PointerType, .pointed=t, .is_readonly=true, .is_optional=true);
return Type(OptionalType, .type=t);
return NULL;
}
case ArrayType: {

10
types.h
View File

@ -57,6 +57,7 @@ struct type_s {
PointerType,
StructType,
EnumType,
OptionalType,
TypeInfoType,
ModuleType,
} tag;
@ -97,7 +98,7 @@ struct type_s {
} ClosureType;
struct {
type_t *pointed;
bool is_optional:1, is_stack:1, is_readonly:1;
bool is_stack:1, is_readonly:1;
} PointerType;
struct {
const char *name;
@ -111,6 +112,9 @@ struct type_s {
bool opaque;
struct env_s *env;
} EnumType;
struct {
type_t *type;
} OptionalType;
struct {
const char *name;
type_t *type;
@ -139,11 +143,9 @@ PUREFUNC bool has_heap_memory(type_t *t);
PUREFUNC bool has_stack_memory(type_t *t);
PUREFUNC bool can_send_over_channel(type_t *t);
PUREFUNC bool can_promote(type_t *actual, type_t *needed);
PUREFUNC bool can_leave_uninitialized(type_t *t);
PUREFUNC bool can_have_cycles(type_t *t);
PUREFUNC bool is_int_type(type_t *t);
PUREFUNC bool is_numeric_type(type_t *t);
type_t *replace_type(type_t *t, type_t *target, type_t *replacement);
PUREFUNC bool supports_optionals(type_t *t);
PUREFUNC size_t type_size(type_t *t);
PUREFUNC size_t type_align(type_t *t);
PUREFUNC size_t padded_type_size(type_t *t);