aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compile.c78
-rw-r--r--examples/game/game.tm6
-rw-r--r--test/optionals.tm28
-rw-r--r--typecheck.c40
4 files changed, 121 insertions, 31 deletions
diff --git a/compile.c b/compile.c
index a1f4d649..de5531cc 100644
--- a/compile.c
+++ b/compile.c
@@ -2953,30 +2953,72 @@ CORD compile(env_t *env, ast_t *ast)
}
case If: {
auto if_ = Match(ast, If);
- if (!if_->else_body)
- code_err(ast, "'if' expressions can only be used if you also have an 'else' block");
+ ast_t *condition = if_->condition;
+ CORD decl_code = CORD_EMPTY;
+ env_t *truthy_scope = env, *falsey_scope = env;
- type_t *t = get_type(env, ast);
- if (t->tag == VoidType || t->tag == AbortType)
- code_err(ast, "This expression has a %T type, but it needs to have a real value", t);
+ type_t *condition_type;
+ if (condition->tag == Declare) {
+ condition_type = get_type(env, Match(condition, Declare)->value);
- CORD condition;
- if (get_type(env, if_->condition)->tag == TextType)
- condition = CORD_all("(", compile(env, if_->condition), ").length");
- else
- condition = compile(env, if_->condition);
+ const char *varname = Match(Match(condition, Declare)->var, Var)->name;
+ falsey_scope = fresh_scope(env);
+ bind_statement(falsey_scope, condition);
+ binding_t *b = get_binding(falsey_scope, varname);
+ assert(b);
+
+ truthy_scope = fresh_scope(env);
+ set_binding(truthy_scope, varname,
+ new(binding_t, .type=Match(condition_type, OptionalType)->type, .code=b->code));
+
+ decl_code = compile_statement(env, condition);
+ condition = Match(condition, Declare)->var;
+ } else if (condition->tag == Var) {
+ condition_type = get_type(env, condition);
+ if (condition_type->tag == OptionalType) {
+ truthy_scope = fresh_scope(env);
+ const char *varname = Match(if_->condition, Var)->name;
+ binding_t *b = get_binding(env, varname);
+ if (!b) code_err(condition, "I don't know what this variable refers to");
+ set_binding(truthy_scope, varname,
+ new(binding_t, .type=Match(condition_type, OptionalType)->type, .code=b->code));
+ }
+ } else {
+ condition_type = get_type(env, condition);
+ }
+
+ if (condition_type->tag == PointerType)
+ code_err(condition, "This pointer will always be non-null, so it should not be used in a conditional.");
+
+ CORD condition_code;
+ if (condition_type->tag == TextType) {
+ condition_code = CORD_all("(", compile(env, condition), ").length");
+ } else if (condition_type->tag == ArrayType) {
+ condition_code = CORD_all("(", compile(env, condition), ").length");
+ } else if (condition_type->tag == TableType || condition_type->tag == SetType) {
+ condition_code = CORD_all("(", compile(env, condition), ").entries.length");
+ } else if (condition_type->tag == OptionalType) {
+ condition_code = CORD_all("!", check_null(condition_type, compile(env, condition)));
+ } else if (condition_type->tag == BoolType) {
+ condition_code = compile(env, condition);
+ } else {
+ code_err(condition, "%T values cannot be used for conditionals", condition_type);
+ }
- type_t *true_type = get_type(env, if_->body);
- type_t *false_type = get_type(env, if_->else_body);
+ type_t *true_type = get_type(truthy_scope, if_->body);
+ type_t *false_type = get_type(falsey_scope, if_->else_body);
if (true_type->tag == AbortType || true_type->tag == ReturnType)
- return CORD_all("({ if (", condition, ") ", compile_statement(env, if_->body),
- "\n", compile(env, if_->else_body), "; })");
+ return CORD_all("({ ", decl_code, "if (", condition_code, ") ", compile_statement(truthy_scope, if_->body),
+ "\n", compile(falsey_scope, if_->else_body), "; })");
else if (false_type->tag == AbortType || false_type->tag == ReturnType)
- return CORD_all("({ if (!(", condition, ")) ", compile_statement(env, if_->else_body),
- "\n", compile(env, if_->body), "; })");
+ return CORD_all("({ ", decl_code, "if (!(", condition_code, ")) ", compile_statement(falsey_scope, if_->else_body),
+ "\n", compile(truthy_scope, if_->body), "; })");
+ else if (decl_code != CORD_EMPTY)
+ return CORD_all("({ ", decl_code, "(", condition_code, ") ? ", compile(truthy_scope, if_->body), " : ",
+ compile(falsey_scope, if_->else_body), ";})");
else
- return CORD_all("((", condition, ") ? ",
- compile(env, if_->body), " : ", compile(env, if_->else_body), ")");
+ return CORD_all("((", condition_code, ") ? ",
+ compile(truthy_scope, if_->body), " : ", compile(falsey_scope, if_->else_body), ")");
}
case Reduction: {
auto reduction = Match(ast, Reduction);
diff --git a/examples/game/game.tm b/examples/game/game.tm
index 362eae04..c7020f55 100644
--- a/examples/game/game.tm
+++ b/examples/game/game.tm
@@ -2,15 +2,15 @@
use libraylib.so
use <raylib.h>
use <raymath.h>
-use file
use ./world.tm
-func main(map="map.txt"):
+func main(map=(./map.txt)):
extern InitWindow:func(w:Int32, h:Int32, title:CString)->Void
InitWindow(1600, 900, "raylib [core] example - 2d camera")
- map_contents := when read(map) is Success(m): m
+ map_contents := if contents := map:read():
+ contents
else: exit(code=1, "Could not find the game map: $map")
World.CURRENT:load_map(map_contents)
diff --git a/test/optionals.tm b/test/optionals.tm
index 6d7db835..ced42274 100644
--- a/test/optionals.tm
+++ b/test/optionals.tm
@@ -284,3 +284,31 @@ func main():
= {!Int}
>> [5?, !Int, !Int, 6?]:sorted()
= [!Int, !Int, 5?, 6?]
+
+ do:
+ >> value := if var := 5?:
+ var
+ else:
+ 0
+ = 5
+
+ do:
+ >> value := if var := !Int:
+ var
+ else:
+ 0
+ = 0
+
+ do:
+ >> opt := 5?
+ >> if opt:
+ >> opt
+ else:
+ >> opt
+
+ do:
+ >> opt := !Int
+ >> if opt:
+ >> opt
+ else:
+ >> opt
diff --git a/typecheck.c b/typecheck.c
index 5f40b078..933f1f90 100644
--- a/typecheck.c
+++ b/typecheck.c
@@ -1161,18 +1161,38 @@ type_t *get_type(env_t *env, ast_t *ast)
case If: {
auto if_ = Match(ast, If);
- type_t *true_t = get_type(env, if_->body);
- if (if_->else_body) {
- type_t *false_t = get_type(env, if_->else_body);
- type_t *t_either = type_or_type(true_t, false_t);
- if (!t_either)
- code_err(if_->else_body,
- "I was expecting this block to have a %T value (based on earlier clauses), but it actually has a %T value.",
- true_t, false_t);
- return t_either;
- } else {
+ if (!if_->else_body)
return Type(VoidType);
+
+ env_t *truthy_scope = env;
+ env_t *falsey_scope = env;
+ if (if_->condition->tag == Declare) {
+ type_t *condition_type = get_type(env, Match(if_->condition, Declare)->value);
+ falsey_scope = fresh_scope(env);
+ bind_statement(falsey_scope, if_->condition);
+
+ truthy_scope = fresh_scope(env);
+ const char *varname = Match(Match(if_->condition, Declare)->var, Var)->name;
+ set_binding(truthy_scope, varname,
+ new(binding_t, .type=Match(condition_type, OptionalType)->type));
+ } else if (if_->condition->tag == Var) {
+ type_t *condition_type = get_type(env, if_->condition);
+ if (condition_type->tag == OptionalType) {
+ truthy_scope = fresh_scope(env);
+ const char *varname = Match(if_->condition, Var)->name;
+ set_binding(truthy_scope, varname,
+ new(binding_t, .type=Match(condition_type, OptionalType)->type));
+ }
}
+
+ type_t *true_t = get_type(truthy_scope, if_->body);
+ type_t *false_t = get_type(falsey_scope, if_->else_body);
+ type_t *t_either = type_or_type(true_t, false_t);
+ if (!t_either)
+ code_err(if_->else_body,
+ "I was expecting this block to have a %T value (based on earlier clauses), but it actually has a %T value.",
+ true_t, false_t);
+ return t_either;
}
case When: {