123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 | // This file defines how to compile optionals and null
#include "../environment.h"
#include "../naming.h"
#include "../stdlib/datatypes.h"
#include "../stdlib/text.h"
#include "../stdlib/util.h"
#include "../typecheck.h"
#include "../types.h"
#include "compilation.h"
#include "indexing.h"
Text_t optional_into_nonnone(type_t *t, Text_t value) {
if (t->tag == OptionalType) t = Match(t, OptionalType)->type;
switch (t->tag) {
case IntType:
case ByteType: return Texts(value, ".value");
case StructType: return Texts(value, ".value");
default: return value;
}
}
public
Text_t promote_to_optional(type_t *t, Text_t code) {
if (t->tag == IntType) {
switch (Match(t, IntType)->bits) {
case TYPE_IBITS8: return Texts("((OptionalInt8_t){.has_value=true, .value=", code, "})");
case TYPE_IBITS16: return Texts("((OptionalInt16_t){.has_value=true, .value=", code, "})");
case TYPE_IBITS32: return Texts("((OptionalInt32_t){.has_value=true, .value=", code, "})");
case TYPE_IBITS64: return Texts("((OptionalInt64_t){.has_value=true, .value=", code, "})");
default: errx(1, "Unsupported in type: %s", Text$as_c_string(type_to_text(t)));
}
return code;
} else if (t->tag == ByteType) {
return Texts("((OptionalByte_t){.has_value=true, .value=", code, "})");
} else if (t->tag == StructType) {
return Texts("((", compile_type(Type(OptionalType, .type = t)), "){.has_value=true, .value=", code, "})");
} else {
return code;
}
}
public
Text_t compile_none(type_t *t) {
if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a `none` value with no type");
if (t->tag == OptionalType) t = Match(t, OptionalType)->type;
if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a `none` value with no type");
switch (t->tag) {
case BigIntType: return Text("NONE_INT");
case IntType: {
switch (Match(t, IntType)->bits) {
case TYPE_IBITS8: return Text("NONE_INT8");
case TYPE_IBITS16: return Text("NONE_INT16");
case TYPE_IBITS32: return Text("NONE_INT32");
case TYPE_IBITS64: return Text("NONE_INT64");
default: errx(1, "Invalid integer bit size");
}
break;
}
case BoolType: return Text("NONE_BOOL");
case ByteType: return Text("NONE_BYTE");
case ListType: return Text("NONE_LIST");
case TableType: return Text("NONE_TABLE");
case TextType: return Text("NONE_TEXT");
case CStringType: return Text("NULL");
case PathType: return Text("NONE_PATH");
case PointerType: return Texts("((", compile_type(t), ")NULL)");
case ClosureType: return Text("NONE_CLOSURE");
case NumType: return Text("nan(\"none\")");
case StructType: return Texts("((", compile_type(Type(OptionalType, .type = t)), "){.has_value=false})");
case EnumType: {
env_t *enum_env = Match(t, EnumType)->env;
return Texts("((", compile_type(t), "){", namespace_name(enum_env, enum_env->namespace, Text("none")), "})");
}
default: compiler_err(NULL, NULL, NULL, "none isn't implemented for this type: ", type_to_text(t));
}
return EMPTY_TEXT;
}
public
Text_t check_none(type_t *t, Text_t value) {
t = Match(t, OptionalType)->type;
// NOTE: these use statement expressions ({...;}) because some compilers
// complain about excessive parens around equality comparisons
switch (t->tag) {
case PointerType:
case FunctionType:
case CStringType:
case PathType: return Texts("(", value, " == NULL)");
case BigIntType: return Texts("((", value, ").small == 0)");
case ClosureType: return Texts("((", value, ").fn == NULL)");
case NumType: return Texts(Match(t, NumType)->bits == TYPE_NBITS64 ? "Num$isnan(" : "Num32$isnan(", value, ")");
case ListType: return Texts("((", value, ").data == NULL)");
case TableType: return Texts("((", value, ").entries.data == NULL)");
case BoolType: return Texts("((", value, ") == NONE_BOOL)");
case TextType: return Texts("((", value, ").tag == TEXT_NONE)");
case IntType:
case ByteType:
case StructType: return Texts("!(", value, ").has_value");
case EnumType: return Texts("((", value, ").$tag == 0)");
default: {
print_err("Optional check not implemented for: ", type_to_text(t));
return EMPTY_TEXT;
}
}
}
public
Text_t compile_non_optional(env_t *env, ast_t *ast) {
ast_t *value = Match(ast, NonOptional)->value;
if (value->tag == Index && Match(value, Index)->index != NULL) return compile_indexing(env, value, true);
type_t *value_t = get_type(env, value);
if (value_t->tag == PointerType) {
// Dereference pointers automatically
return compile_non_optional(env, WrapAST(ast, NonOptional, WrapAST(ast, Index, .indexed = value)));
}
int64_t line = get_line_number(ast->file, ast->start);
if (value_t->tag == EnumType) {
// For this case:
// enum Foo(FirstField, SecondField(msg:Text))
// e := ...
// e!
// We desugar into `e.FirstField!` using the first enum field
tag_t *first_tag = Match(value_t, EnumType)->tags;
if (!first_tag) code_err(ast, "'!' cannot be used on an empty enum");
return compile_non_optional(
env, WrapAST(ast, NonOptional, WrapAST(value, FieldAccess, .fielded = value, .field = first_tag->name)));
} else if (value->tag == FieldAccess
&& value_type(get_type(env, Match(value, FieldAccess)->fielded))->tag == EnumType) {
type_t *enum_t = value_type(get_type(env, Match(value, FieldAccess)->fielded));
DeclareMatch(e, enum_t, EnumType);
DeclareMatch(f, value, FieldAccess);
for (tag_t *tag = e->tags; tag; tag = tag->next) {
if (streq(f->field, tag->name)) {
Text_t tag_name = namespace_name(e->env, e->env->namespace, Texts("tag$", tag->name));
return Texts(
"({ ", compile_declaration(enum_t, Text("_test_enum")), " = ",
compile_to_pointer_depth(env, f->fielded, 0, true), ";",
"if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", "fail_source(",
quoted_str(f->fielded->file->filename), ", ", (int64_t)(f->fielded->start - f->fielded->file->text),
", ", (int64_t)(f->fielded->end - f->fielded->file->text), ", ",
"Text$concat(Text(\"This was expected to be ", tag->name, ", but it was: \"), ",
expr_as_text(Text("_test_enum"), enum_t, Text("false")), ", Text(\"\\n\")));\n}\n",
compile_maybe_incref(
env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), tag->type),
"; })");
}
}
code_err(ast, "The field '", f->field, "' is not a valid tag name of ", type_to_text(enum_t));
} else {
Text_t value_code = compile(env, value);
return Texts("({ ", compile_declaration(value_t, Text("opt")), " = ", value_code, "; ", "if unlikely (",
check_none(value_t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(",
quoted_str(value->file->filename), ", ", (int64_t)(value->start - value->file->text), ", ",
(int64_t)(value->end - value->file->text), ", ",
"Text(\"This was expected to be a value, but it's `none`\\n\"));\n",
optional_into_nonnone(value_t, Text("opt")), "; })");
}
}
|