Allow uninitialized variables when there's a sensible empty value

(defaults to empty/zero value)
This commit is contained in:
Bruce Hill 2025-04-06 19:20:07 -04:00
parent bc93dea818
commit 1d2e55f53d
19 changed files with 157 additions and 76 deletions

View File

@ -141,7 +141,7 @@ struct _TermState(
)
func apply(old,new:_TermState -> Text)
sequences : &[Text] = &[]
sequences : &[Text]
_toggle2(sequences, old.bold, old.dim, new.bold, new.dim, "1", "2", "22")
_toggle2(sequences, old.italic, old.fraktur, new.italic, new.fraktur, "3", "20", "23")
_toggle(sequences, old.underline, new.underline, "4", "24")

View File

@ -54,8 +54,8 @@ struct Command(command:Text, args:[Text]=[], env:{Text=Text}={})
if input.length > 0
(&input_bytes).insert_all(input.bytes())
stdout : [Byte] = []
stderr : [Byte] = []
stdout : [Byte]
stderr : [Byte]
status := run_command(command.command, command.args, command.env, input_bytes, &stdout, &stderr)
if inline C : Bool { WIFEXITED(_$status) }

View File

@ -12,7 +12,7 @@ struct ConnectionQueue(_connections:@[Int32]=@[], _mutex=pthread_mutex_t.new(),
func dequeue(queue:ConnectionQueue -> Int32)
conn : Int32? = none
conn : Int32?
queue._mutex.lock()

View File

@ -17,7 +17,7 @@ use ./connection-queue.tm
func serve(port:Int32, handler:func(request:HTTPRequest -> HTTPResponse), num_threads=16)
connections := ConnectionQueue()
workers : &[@pthread_t] = &[]
workers : &[@pthread_t]
for i in num_threads
workers.insert(pthread_t.new(func()
repeat
@ -114,7 +114,7 @@ enum RouteEntry(ServeFile(file:Path), Redirect(destination:Text))
return HTTPResponse("Found", 302, headers={"Location"=destination})
func load_routes(directory:Path -> {Text=RouteEntry})
routes : &{Text=RouteEntry} = &{}
routes : &{Text=RouteEntry}
for file in (directory ++ (./*)).glob()
skip unless file.is_file()
contents := file.read() or skip

View File

@ -8,7 +8,7 @@ struct HTTPResponse(code:Int, body:Text)
enum _Method(GET, POST, PUT, PATCH, DELETE)
func _send(method:_Method, url:Text, data:Text?, headers:[Text]=[] -> HTTPResponse)
chunks : @[Text] = @[]
chunks : @[Text]
save_chunk := func(chunk:CString, size:Int64, n:Int64)
chunks.insert(inline C:Text {
Text$format("%.*s", _$size*_$n, _$chunk)

View File

@ -11,8 +11,8 @@ _HELP := "
func parse_ini(path:Path -> {Text={Text=Text}})
text := path.read() or exit("Could not read INI file: $\[31;1]$(path)$\[]")
sections : @{Text=@{Text=Text}} = @{}
current_section : @{Text=Text} = @{}
sections : @{Text=@{Text=Text}}
current_section : @{Text=Text}
# Line wraps:
text = text.replace_pattern($Pat/\{1 nl}{0+space}/, " ")

View File

@ -56,7 +56,7 @@ func main()
my_numbers := [10, 20, 30]
# Empty arrays require specifying the type:
empty_array : [Int] = []
empty_array : [Int]
>> empty_array.length
= 0
@ -120,7 +120,7 @@ func main()
= 0
# Empty tables require specifying the key and value types:
empty_table : {Text=Int} = {}
empty_table : {Text=Int}
# Tables can be iterated over either by key or key,value:
for key in table

View File

@ -3,7 +3,7 @@ use <stdio.h>
timestamp_format := CString("%F %T")
logfiles : @|Path| = @||
logfiles : @|Path|
func _timestamp(->Text)
c_str := inline C:CString {

View File

@ -11,7 +11,7 @@ _HELP := "
"
func find_urls(path:Path -> [Text])
urls : @[Text] = @[]
urls : @[Text]
if path.is_directory()
for f in path.children()
urls.insert_all(find_urls(f))
@ -40,7 +40,7 @@ func main(paths:[Path])
say("Already installed: $url")
skip
alias : Text? = none
alias : Text?
curl_flags := ["-L"]
if github := url_without_protocol.pattern_captures($Pat"github.com/{!/}/{!/}#{..}")
user := github[1]

View File

@ -16,7 +16,7 @@ func _get_file_dependencies(file:Path -> |Dependency|)
say("Could not read file: $file")
return ||
deps : @|Dependency| = @||
deps : @|Dependency|
if lines := file.by_line()
for line in lines
if line.matches_pattern($Pat/use {..}.tm/)
@ -36,8 +36,8 @@ func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency=|Dependen
_get_file_dependencies(path)
is Module(module)
dir := (~/.local/share/tomo/installed/$module)
module_deps : @|Dependency| = @||
visited : @|Path| = @||
module_deps : @|Dependency|
visited : @|Path|
unvisited := @|f.resolved() for f in dir.files() if f.extension() == ".tm"|
while unvisited.length > 0
file := unvisited.items[-1]
@ -58,7 +58,7 @@ func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency=|Dependen
_build_dependency_graph(dep2, dependencies)
func get_dependency_graph(dep:Dependency -> {Dependency=|Dependency|})
graph : @{Dependency=|Dependency|} = @{}
graph : @{Dependency=|Dependency|}
_build_dependency_graph(dep, graph)
return graph
@ -88,7 +88,7 @@ func _draw_tree(dep:Dependency, dependencies:{Dependency=|Dependency|}, already_
_draw_tree(child, dependencies, already_printed, child_prefix, is_child_last)
func draw_tree(dep:Dependency, dependencies:{Dependency=|Dependency|})
printed : @|Dependency| = @||
printed : @|Dependency|
say(_printable_name(dep))
printed.add(dep)
deps := dependencies[dep] or ||

View File

@ -33,7 +33,7 @@ func wrap(text:Text, width:Int, min_split=3, hyphen="-" -> Text)
... and I can't split it without splitting into chunks smaller than $min_split.
")
lines : @[Text] = @[]
lines : @[Text]
line := ""
for word in text.split($/{whitespace}/)
letters := word.split()
@ -93,7 +93,7 @@ func main(files:[Path], width=80, inplace=no, min_split=3, rewrap=yes, hyphen=UN
(/dev/stdout)
first := yes
wrapped_paragraphs : @[Text] = @[]
wrapped_paragraphs : @[Text]
for paragraph in text.split($/{2+ nl}/)
wrapped_paragraphs.insert(
wrap(paragraph, width=width, min_split=min_split, hyphen=hyphen)

View File

@ -31,6 +31,8 @@ static CORD compile_int_to_type(env_t *env, ast_t *ast, type_t *target);
static CORD compile_unsigned_type(type_t *t);
static CORD promote_to_optional(type_t *t, CORD code);
static CORD compile_none(type_t *t);
static CORD compile_empty(type_t *t);
static CORD compile_declared_value(env_t *env, ast_t *declaration_ast);
static CORD compile_to_type(env_t *env, ast_t *ast, type_t *t);
static CORD compile_typed_array(env_t *env, ast_t *ast, type_t *array_type);
static CORD compile_typed_set(env_t *env, ast_t *ast, type_t *set_type);
@ -214,7 +216,8 @@ static void add_closed_vars(Table_t *closed_vars, env_t *enclosing_scope, env_t
break;
}
case Declare: {
add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Declare)->value);
ast_t *value = Match(ast, Declare)->value;
add_closed_vars(closed_vars, enclosing_scope, env, value);
bind_statement(env, ast);
break;
}
@ -334,6 +337,8 @@ static void add_closed_vars(Table_t *closed_vars, env_t *enclosing_scope, env_t
if (condition->tag == Declare) {
env_t *truthy_scope = fresh_scope(env);
bind_statement(truthy_scope, condition);
if (!Match(condition, Declare)->value)
code_err(condition, "This declared variable must have an initial value");
add_closed_vars(closed_vars, enclosing_scope, env, Match(condition, Declare)->value);
ast_t *var = Match(condition, Declare)->var;
type_t *cond_t = get_type(truthy_scope, var);
@ -1132,17 +1137,9 @@ static CORD _compile_statement(env_t *env, ast_t *ast)
CORD test_code;
if (test->expr->tag == Declare) {
auto decl = Match(test->expr, Declare);
const char *varname = Match(decl->var, Var)->name;
if (streq(varname, "_"))
return compile_statement(env, WrapAST(ast, DocTest, .expr=decl->value, .expected=test->expected, .skip_source=test->skip_source));
CORD var = CORD_all("_$", Match(decl->var, Var)->name);
type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value);
if (!t) code_err(decl->value, "I couldn't figure out the type of this value!");
CORD val_code = compile_maybe_incref(env, decl->value, t);
if (t->tag == FunctionType) {
assert(promote(env, decl->value, &val_code, t, Type(ClosureType, t)));
t = Type(ClosureType, t);
}
CORD var = CORD_all("_$", Match(decl->var, Var)->name);
CORD val_code = compile_declared_value(env, test->expr);
setup = CORD_all(compile_declaration(t, var), ";\n");
test_code = CORD_all("(", var, " = ", val_code, ")");
expr_t = t;
@ -1229,17 +1226,16 @@ static CORD _compile_statement(env_t *env, ast_t *ast)
auto decl = Match(ast, Declare);
const char *name = Match(decl->var, Var)->name;
if (streq(name, "_")) { // Explicit discard
return CORD_all("(void)", compile(env, decl->value), ";");
if (decl->value)
return CORD_all("(void)", compile(env, decl->value), ";");
else
return CORD_EMPTY;
} else {
type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value);
if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType)
code_err(ast, "You can't declare a variable with a ", type_to_str(t), " value");
CORD val_code = compile_maybe_incref(env, decl->value, t);
if (t->tag == FunctionType) {
assert(promote(env, decl->value, &val_code, t, Type(ClosureType, t)));
t = Type(ClosureType, t);
}
CORD val_code = compile_declared_value(env, ast);
return CORD_all(compile_declaration(t, CORD_cat("_$", name)), " = ", val_code, ";");
}
}
@ -1792,6 +1788,8 @@ static CORD _compile_statement(env_t *env, ast_t *ast)
auto if_ = Match(ast, If);
ast_t *condition = if_->condition;
if (condition->tag == Declare) {
if (Match(condition, Declare)->value == NULL)
code_err(condition, "This declaration must have a value");
env_t *truthy_scope = fresh_scope(env);
CORD code = CORD_all("IF_DECLARE(", compile_statement(truthy_scope, condition), ", ");
bind_statement(truthy_scope, condition);
@ -2474,6 +2472,96 @@ CORD compile_none(type_t *t)
}
}
CORD compile_empty(type_t *t)
{
if (t == NULL)
compiler_err(NULL, NULL, NULL, "I can't compile a value with no type");
if (t->tag == OptionalType)
return compile_none(t);
if (t == PATH_TYPE) return "NONE_PATH";
else if (t == PATH_TYPE_TYPE) return "((OptionalPathType_t){})";
switch (t->tag) {
case BigIntType: return "I(0)";
case IntType: {
switch (Match(t, IntType)->bits) {
case TYPE_IBITS8: return "Int8(0)";
case TYPE_IBITS16: return "Int16(0)";
case TYPE_IBITS32: return "Int32(0)";
case TYPE_IBITS64: return "Int64(0)";
default: errx(1, "Invalid integer bit size");
}
break;
}
case ByteType: return "((Byte_t)0)";
case BoolType: return "((Bool_t)no)";
case ArrayType: return "((Array_t){})";
case TableType: case SetType: return "((Table_t){})";
case TextType: return "Text(\"\")";
case CStringType: return "\"\"";
case PointerType: {
auto ptr = Match(t, PointerType);
CORD empty_pointed = compile_empty(ptr->pointed);
return empty_pointed == CORD_EMPTY ? CORD_EMPTY : CORD_all(ptr->is_stack ? "stack(" : "heap(", empty_pointed, ")");
}
case NumType: {
return Match(t, NumType)->bits == TYPE_NBITS32 ? "Num32(0.0f)" : "Num64(0.0)" ;
}
case StructType: {
auto struct_ = Match(t, StructType);
CORD code = CORD_all("((", compile_type(t), "){");
for (arg_t *field = struct_->fields; field; field = field->next) {
CORD empty_field = field->default_val
? compile(struct_->env, field->default_val)
: compile_empty(field->type);
if (empty_field == CORD_EMPTY)
return CORD_EMPTY;
code = CORD_all(code, empty_field);
if (field->next)
code = CORD_all(code, ", ");
}
return CORD_all(code, "})");
}
case EnumType: {
auto enum_ = Match(t, EnumType);
tag_t *tag = enum_->tags;
assert(tag);
assert(tag->type);
if (Match(tag->type, StructType)->fields)
return CORD_all("((", compile_type(t), "){.$tag=", String(tag->tag_value), ", .", tag->name, "=", compile_empty(tag->type), "})");
else
return CORD_all("((", compile_type(t), "){.$tag=", String(tag->tag_value), "})");
}
default: return CORD_EMPTY;
}
}
static CORD compile_declared_value(env_t *env, ast_t *declare_ast)
{
auto decl = Match(declare_ast, Declare);
type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value);
if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType)
code_err(declare_ast, "You can't declare a variable with a ", type_to_str(t), " value");
if (decl->value) {
CORD val_code = compile_maybe_incref(env, decl->value, t);
if (t->tag == FunctionType) {
assert(promote(env, decl->value, &val_code, t, Type(ClosureType, t)));
t = Type(ClosureType, t);
}
return val_code;
} else {
CORD val_code = compile_empty(t);
if (val_code == CORD_EMPTY)
code_err(declare_ast, "This type (", type_to_str(t), ") cannot be uninitialized. You must provide a value.");
return val_code;
}
}
ast_t *add_to_table_comprehension(ast_t *entry, ast_t *subject)
{
auto e = Match(entry, TableEntry);
@ -3377,6 +3465,8 @@ CORD compile(env_t *env, ast_t *ast)
CORD condition_code;
if (condition->tag == Declare) {
auto decl = Match(condition, Declare);
if (decl->value == NULL)
code_err(condition, "This declaration must have a value");
type_t *condition_type =
decl->type ? parse_type_ast(env, decl->type)
: get_type(env, Match(condition, Declare)->value);
@ -4121,17 +4211,9 @@ CORD compile_top_level_code(env_t *env, ast_t *ast)
const char *decl_name = Match(decl->var, Var)->name;
CORD full_name = CORD_all(namespace_prefix(env, env->namespace), decl_name);
type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value);
if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType)
code_err(ast, "You can't declare a variable with a ", type_to_str(t), " value");
CORD val_code = compile_maybe_incref(env, decl->value, t);
if (t->tag == FunctionType) {
assert(promote(env, decl->value, &val_code, t, Type(ClosureType, t)));
t = Type(ClosureType, t);
}
CORD val_code = compile_declared_value(env, ast);
bool is_private = decl_name[0] == '_';
if (is_constant(env, decl->value)) {
if ((decl->value && is_constant(env, decl->value)) || (!decl->value && !has_heap_memory(t))) {
set_binding(env, decl_name, t, full_name);
return CORD_all(
is_private ? "static " : CORD_EMPTY,
@ -4215,17 +4297,9 @@ static void initialize_vars_and_statics(env_t *env, ast_t *ast)
auto decl = Match(stmt->ast, Declare);
const char *decl_name = Match(decl->var, Var)->name;
CORD full_name = CORD_all(namespace_prefix(env, env->namespace), decl_name);
type_t *t = get_type(env, decl->value);
if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType)
code_err(stmt->ast, "You can't declare a variable with a ", type_to_str(t), " value");
CORD val_code = compile_maybe_incref(env, decl->value, t);
if (t->tag == FunctionType) {
assert(promote(env, decl->value, &val_code, t, Type(ClosureType, t)));
t = Type(ClosureType, t);
}
if (!is_constant(env, decl->value)) {
type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value);
CORD val_code = compile_declared_value(env, stmt->ast);
if ((decl->value && !is_constant(env, decl->value)) || (!decl->value && has_heap_memory(t))) {
env->code->variable_initializers = CORD_all(
env->code->variable_initializers,
with_source_info(
@ -4406,7 +4480,7 @@ CORD compile_statement_namespace_header(env_t *env, Path_t header_path, ast_t *a
if (is_private)
return CORD_EMPTY;
type_t *t = get_type(env, decl->value);
type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value);
if (t->tag == FunctionType)
t = Type(ClosureType, t);
assert(t->tag != ModuleType);
@ -4414,7 +4488,7 @@ CORD compile_statement_namespace_header(env_t *env, Path_t header_path, ast_t *a
code_err(ast, "You can't declare a variable with a ", type_to_str(t), " value");
return CORD_all(
compile_statement_type_header(env, header_path, decl->value),
decl->value ? compile_statement_type_header(env, header_path, decl->value) : CORD_EMPTY,
"extern ", compile_declaration(t, CORD_cat(namespace_prefix(env, env->namespace), decl_name)), ";\n");
}
case FunctionDef: {

View File

@ -1677,13 +1677,15 @@ PARSER(parse_declaration) {
spaces(&pos);
type_ast_t *type = optional(ctx, &pos, parse_type);
spaces(&pos);
if (!match(&pos, "=")) return NULL;
ast_t *val = optional(ctx, &pos, parse_extended_expr);
if (!val) {
if (optional(ctx, &pos, parse_use))
parser_err(ctx, start, pos, "'use' statements are only allowed at the top level of a file");
else
parser_err(ctx, pos, eol(pos), "This is not a valid expression");
ast_t *val = NULL;
if (match(&pos, "=")) {
val = optional(ctx, &pos, parse_extended_expr);
if (!val) {
if (optional(ctx, &pos, parse_use))
parser_err(ctx, start, pos, "'use' statements are only allowed at the top level of a file");
else
parser_err(ctx, pos, eol(pos), "This is not a valid expression");
}
}
return NewAST(ctx->file, start, pos, Declare, .var=var, .type=type, .value=val);
}

View File

@ -303,10 +303,11 @@ void bind_statement(env_t *env, ast_t *statement)
return;
if (get_binding(env, name))
code_err(decl->var, "A ", type_to_str(get_binding(env, name)->type), " called ", quoted(name), " has already been defined");
bind_statement(env, decl->value);
if (decl->value)
bind_statement(env, decl->value);
type_t *type = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value);
if (!type)
code_err(decl->value, "I couldn't figure out the type of this value");
code_err(statement, "I couldn't figure out the type of this value");
if (type->tag == FunctionType)
type = Type(ClosureType, type);
CORD prefix = namespace_prefix(env, env->namespace);

View File

@ -3,6 +3,10 @@ func main()
>> nums : [Num32] = []
= []
do
>> nums : [Num32]
= []
do
>> arr := [10, 20, 30]
= [10, 20, 30]
@ -104,7 +108,7 @@ func main()
>> heap := @[(i * 1337) mod 37 for i in 10]
>> heap.heapify()
>> heap
heap_order : @[Int] = @[]
heap_order : @[Int]
repeat
heap_order.insert(heap.heap_pop() or stop)
>> heap_order[] == heap_order.sorted()

View File

@ -1,6 +1,6 @@
func main()
x := 123
nums : @[Int] = @[]
nums : @[Int]
do
defer
nums.insert(x)

View File

@ -8,11 +8,11 @@ func returns_imported_type(->ImportedType)
return get_value() # Imported from ./use_import.tm
func main()
>> empty : [vectors.Vec2] = []
>> empty : [vectors.Vec2]
>> returns_vec()
= Vec2(x=1, y=2)
>> imported : [ImportedType] = []
>> imported : [ImportedType]
>> returns_imported_type()
= ImportedType("Hello")

View File

@ -25,7 +25,7 @@ func main()
= ["AB", "BC", "CD"]
do
result : @[Text] = @[]
result : @[Text]
for foo in pairwise(values)
result.insert("$(foo.x)$(foo.y)")
>> result[]

View File

@ -4,7 +4,7 @@ func main()
>> (+: [10, 20, 30])
= 60?
>> empty_ints : [Int] = []
>> empty_ints : [Int]
>> (+: empty_ints)
= none