Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b5a0a729e6 | |||
| b26dd47aef | |||
| b55e6bfd53 | |||
| dbb06f6ee4 | |||
| 906c664a65 | |||
| 9e96d74f6a | |||
| a8908908df | |||
| a69a4a5894 |
@@ -862,10 +862,23 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
// arithmetic BinOp (or vice-versa). Without this check the
|
// arithmetic BinOp (or vice-versa). Without this check the
|
||||||
// fallthrough to str_eq produces str_eq(int_value, int_value)
|
// fallthrough to str_eq produces str_eq(int_value, int_value)
|
||||||
// which reads the integer as a char* and segfaults.
|
// which reads the integer as a char* and segfaults.
|
||||||
|
// EITHER side provably Int is enough. Requiring BOTH meant a call
|
||||||
|
// whose return type codegen cannot infer poisoned the operator:
|
||||||
|
// getint(5) == a -> str_eq(getint(5), a)
|
||||||
|
// even with `a` declared Int. str_eq then reads an integer as a
|
||||||
|
// char* and segfaults. Only an integer LITERAL on one side forced
|
||||||
|
// the numeric form, so the bug was invisible in the common case.
|
||||||
|
//
|
||||||
|
// Loosening to OR is strictly safer: when one side is a known Int,
|
||||||
|
// str_eq is always wrong (it dereferences that int), while numeric
|
||||||
|
// comparison is at worst a wrong answer on an already ill-typed
|
||||||
|
// program. When neither side is Int nothing changes, so string
|
||||||
|
// comparison is untouched.
|
||||||
if is_int_expr(left) {
|
if is_int_expr(left) {
|
||||||
if is_int_expr(right) {
|
return "(" + left_c + " == " + right_c + ")"
|
||||||
return "(" + left_c + " == " + right_c + ")"
|
}
|
||||||
}
|
if is_int_expr(right) {
|
||||||
|
return "(" + left_c + " == " + right_c + ")"
|
||||||
}
|
}
|
||||||
// Float literal or negative float literal: use plain == (bit-equal
|
// Float literal or negative float literal: use plain == (bit-equal
|
||||||
// el_val_t comparison). This handles `r0 == 3.0`, `neg == -3.0`, etc.
|
// el_val_t comparison). This handles `r0 == 3.0`, `neg == -3.0`, etc.
|
||||||
@@ -921,10 +934,12 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
}
|
}
|
||||||
// Same mixed Ident/BinOp fix as EqEq: use is_int_expr to detect
|
// Same mixed Ident/BinOp fix as EqEq: use is_int_expr to detect
|
||||||
// integer-typed operands before falling through to !str_eq.
|
// integer-typed operands before falling through to !str_eq.
|
||||||
|
// Either side Int is enough — see the EqEq note above.
|
||||||
if is_int_expr(left) {
|
if is_int_expr(left) {
|
||||||
if is_int_expr(right) {
|
return "(" + left_c + " != " + right_c + ")"
|
||||||
return "(" + left_c + " != " + right_c + ")"
|
}
|
||||||
}
|
if is_int_expr(right) {
|
||||||
|
return "(" + left_c + " != " + right_c + ")"
|
||||||
}
|
}
|
||||||
// Float-typed operands use plain != (bit-equal comparison).
|
// Float-typed operands use plain != (bit-equal comparison).
|
||||||
if is_float_expr(left) {
|
if is_float_expr(left) {
|
||||||
@@ -1495,6 +1510,11 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
|
|||||||
if str_eq(ltype, "Int") {
|
if str_eq(ltype, "Int") {
|
||||||
add_int_name(name)
|
add_int_name(name)
|
||||||
}
|
}
|
||||||
|
// Same as params: Bool is an int in the value model. Without this a
|
||||||
|
// `let ok: Bool = ...` compared to another Bool lowered to str_eq.
|
||||||
|
if str_eq(ltype, "Bool") {
|
||||||
|
add_int_name(name)
|
||||||
|
}
|
||||||
if str_eq(ltype, "Float") {
|
if str_eq(ltype, "Float") {
|
||||||
add_float_name(name)
|
add_float_name(name)
|
||||||
}
|
}
|
||||||
@@ -3112,6 +3132,15 @@ fn build_int_names_for_params(params: [Map<String, Any>]) -> Bool {
|
|||||||
if str_eq(ptype, "Int") {
|
if str_eq(ptype, "Int") {
|
||||||
add_int_name(pname)
|
add_int_name(pname)
|
||||||
}
|
}
|
||||||
|
// Bool is an integer in the value model (type_to_c maps Bool -> "int";
|
||||||
|
// el_runtime.h: "Bool -> el_val_t (0 = false, nonzero = true)"), but
|
||||||
|
// Bool names were registered nowhere. So `cond == want` between two
|
||||||
|
// Bool params fell through to str_eq and dereferenced 0 or 1 as a
|
||||||
|
// char* — an immediate segfault. Track them as int-like, which is what
|
||||||
|
// they are.
|
||||||
|
if str_eq(ptype, "Bool") {
|
||||||
|
add_int_name(pname)
|
||||||
|
}
|
||||||
if str_eq(ptype, "Float") {
|
if str_eq(ptype, "Float") {
|
||||||
add_float_name(pname)
|
add_float_name(pname)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,6 +419,22 @@ fn resolve_imports(src_path: String) -> String {
|
|||||||
if !str_eq(already, "") { return "" }
|
if !str_eq(already, "") { return "" }
|
||||||
state_set(seen_key, "1")
|
state_set(seen_key, "1")
|
||||||
|
|
||||||
|
// A missing file must be a hard error, never an empty string.
|
||||||
|
//
|
||||||
|
// fs_read returns "" both for "file is empty" and "file does not exist", and
|
||||||
|
// this function used the value without distinguishing them. So a broken
|
||||||
|
// import path — a typo, a moved file, a relative path resolved from the
|
||||||
|
// wrong working directory — compiled CLEANLY: exit 0, empty stderr, and a
|
||||||
|
// program silently missing everything it imported. Observed 2026-08-15:
|
||||||
|
// eleven consecutive "successful" compiles that had included no runtime at
|
||||||
|
// all, and a wrong conclusion drawn from them before anyone noticed.
|
||||||
|
//
|
||||||
|
// Missing dependency, confident success. fs_exists separates the two cases,
|
||||||
|
// so a genuinely empty file still resolves to "" and is fine.
|
||||||
|
if !fs_exists(src_path) {
|
||||||
|
println("elc: cannot resolve import: " + src_path)
|
||||||
|
exit_program(1)
|
||||||
|
}
|
||||||
let source: String = fs_read(src_path)
|
let source: String = fs_read(src_path)
|
||||||
let dir: String = dirname_of(src_path)
|
let dir: String = dirname_of(src_path)
|
||||||
let lines: [String] = str_split(source, "\n")
|
let lines: [String] = str_split(source, "\n")
|
||||||
|
|||||||
@@ -476,12 +476,14 @@ typedef struct {
|
|||||||
static ElList* list_alloc(int64_t cap) {
|
static ElList* list_alloc(int64_t cap) {
|
||||||
if (cap < 4) cap = 4;
|
if (cap < 4) cap = 4;
|
||||||
ElList* lst = malloc(sizeof(ElList));
|
ElList* lst = malloc(sizeof(ElList));
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += sizeof(ElList);
|
||||||
if (!lst) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
if (!lst) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
||||||
lst->hdr.magic = EL_MAGIC_LIST;
|
lst->hdr.magic = EL_MAGIC_LIST;
|
||||||
lst->hdr.refcount = 1;
|
lst->hdr.refcount = 1;
|
||||||
lst->length = 0;
|
lst->length = 0;
|
||||||
lst->capacity = cap;
|
lst->capacity = cap;
|
||||||
lst->elems = malloc((size_t)cap * sizeof(el_val_t));
|
lst->elems = malloc((size_t)cap * sizeof(el_val_t));
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += (size_t)cap * sizeof(el_val_t);
|
||||||
if (!lst->elems) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
if (!lst->elems) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
||||||
return lst;
|
return lst;
|
||||||
}
|
}
|
||||||
@@ -531,6 +533,7 @@ el_val_t el_list_append(el_val_t listv, el_val_t elem) {
|
|||||||
if (old->length >= old->capacity) {
|
if (old->length >= old->capacity) {
|
||||||
int64_t new_cap = old->capacity > 0 ? old->capacity * 2 : 4;
|
int64_t new_cap = old->capacity > 0 ? old->capacity * 2 : 4;
|
||||||
el_val_t* grown = realloc(old->elems, (size_t)new_cap * sizeof(el_val_t));
|
el_val_t* grown = realloc(old->elems, (size_t)new_cap * sizeof(el_val_t));
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += (size_t)new_cap * sizeof(el_val_t);
|
||||||
if (!grown) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
if (!grown) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
||||||
old->elems = grown;
|
old->elems = grown;
|
||||||
old->capacity = new_cap;
|
old->capacity = new_cap;
|
||||||
@@ -543,12 +546,14 @@ el_val_t el_list_append(el_val_t listv, el_val_t elem) {
|
|||||||
int64_t new_cap = old->length + 1;
|
int64_t new_cap = old->length + 1;
|
||||||
if (new_cap < 4) new_cap = 4;
|
if (new_cap < 4) new_cap = 4;
|
||||||
ElList* fresh = malloc(sizeof(ElList));
|
ElList* fresh = malloc(sizeof(ElList));
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += sizeof(ElList);
|
||||||
if (!fresh) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
if (!fresh) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
||||||
fresh->hdr.magic = EL_MAGIC_LIST;
|
fresh->hdr.magic = EL_MAGIC_LIST;
|
||||||
fresh->hdr.refcount = 1;
|
fresh->hdr.refcount = 1;
|
||||||
fresh->length = old->length + 1;
|
fresh->length = old->length + 1;
|
||||||
fresh->capacity = new_cap;
|
fresh->capacity = new_cap;
|
||||||
fresh->elems = malloc((size_t)new_cap * sizeof(el_val_t));
|
fresh->elems = malloc((size_t)new_cap * sizeof(el_val_t));
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += (size_t)new_cap * sizeof(el_val_t);
|
||||||
if (!fresh->elems) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
if (!fresh->elems) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
||||||
if (old->length > 0) {
|
if (old->length > 0) {
|
||||||
memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t));
|
memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t));
|
||||||
@@ -570,12 +575,14 @@ el_val_t el_list_clone(el_val_t listv) {
|
|||||||
if (cap < old->length) cap = old->length;
|
if (cap < old->length) cap = old->length;
|
||||||
if (cap < 4) cap = 4;
|
if (cap < 4) cap = 4;
|
||||||
ElList* fresh = malloc(sizeof(ElList));
|
ElList* fresh = malloc(sizeof(ElList));
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += sizeof(ElList);
|
||||||
if (!fresh) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
if (!fresh) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
||||||
fresh->hdr.magic = EL_MAGIC_LIST;
|
fresh->hdr.magic = EL_MAGIC_LIST;
|
||||||
fresh->hdr.refcount = 1;
|
fresh->hdr.refcount = 1;
|
||||||
fresh->length = old->length;
|
fresh->length = old->length;
|
||||||
fresh->capacity = cap;
|
fresh->capacity = cap;
|
||||||
fresh->elems = malloc((size_t)cap * sizeof(el_val_t));
|
fresh->elems = malloc((size_t)cap * sizeof(el_val_t));
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += (size_t)cap * sizeof(el_val_t);
|
||||||
if (!fresh->elems) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
if (!fresh->elems) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
||||||
if (old->length > 0) {
|
if (old->length > 0) {
|
||||||
memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t));
|
memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t));
|
||||||
@@ -596,6 +603,7 @@ typedef struct {
|
|||||||
static ElMap* map_alloc(int64_t cap) {
|
static ElMap* map_alloc(int64_t cap) {
|
||||||
if (cap < 4) cap = 4;
|
if (cap < 4) cap = 4;
|
||||||
ElMap* m = malloc(sizeof(ElMap));
|
ElMap* m = malloc(sizeof(ElMap));
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += sizeof(ElMap);
|
||||||
if (!m) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
if (!m) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
||||||
m->hdr.magic = EL_MAGIC_MAP;
|
m->hdr.magic = EL_MAGIC_MAP;
|
||||||
m->hdr.refcount = 1;
|
m->hdr.refcount = 1;
|
||||||
@@ -671,6 +679,7 @@ el_val_t el_map_set(el_val_t mapv, el_val_t keyv, el_val_t value) {
|
|||||||
int64_t new_cap = m->count + 1;
|
int64_t new_cap = m->count + 1;
|
||||||
if (new_cap < 4) new_cap = 4;
|
if (new_cap < 4) new_cap = 4;
|
||||||
ElMap* fresh = malloc(sizeof(ElMap));
|
ElMap* fresh = malloc(sizeof(ElMap));
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += sizeof(ElMap);
|
||||||
if (!fresh) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
if (!fresh) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
||||||
fresh->hdr.magic = EL_MAGIC_MAP;
|
fresh->hdr.magic = EL_MAGIC_MAP;
|
||||||
fresh->hdr.refcount = 1;
|
fresh->hdr.refcount = 1;
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
fn getstr(x: String) -> String { return x }
|
||||||
|
fn getint(x: Int) -> Int { return x }
|
||||||
|
fn ok(label: String) -> Void { println("ok " + label) }
|
||||||
|
fn bad(label: String) -> Void { println("FAIL " + label) }
|
||||||
|
|
||||||
|
let s1: String = "hello"
|
||||||
|
let s2: String = "hello"
|
||||||
|
let s3: String = "world"
|
||||||
|
let i1: Int = 5
|
||||||
|
let i2: Int = 5
|
||||||
|
let i3: Int = 9
|
||||||
|
|
||||||
|
if "abc" == "abc" { ok("str literal eq") } else { bad("str literal eq") }
|
||||||
|
if "abc" == "xyz" { bad("str literal ne") } else { ok("str literal ne") }
|
||||||
|
if s1 == s2 { ok("str var eq") } else { bad("str var eq") }
|
||||||
|
if s1 == s3 { bad("str var ne") } else { ok("str var ne") }
|
||||||
|
if getstr("hi") == "hi" { ok("str call vs literal") } else { bad("str call vs literal") }
|
||||||
|
if s1 == getstr("hello") { ok("str var vs call") } else { bad("str var vs call") }
|
||||||
|
if s1 == getstr("nope") { bad("str var vs call ne") } else { ok("str var vs call ne") }
|
||||||
|
if i1 == i2 { ok("int var eq") } else { bad("int var eq") }
|
||||||
|
if i1 == i3 { bad("int var ne") } else { ok("int var ne") }
|
||||||
|
if getint(5) == i1 { ok("int call vs var") } else { bad("int call vs var") }
|
||||||
|
if getint(9) == i1 { bad("int call vs var ne") } else { ok("int call vs var ne") }
|
||||||
|
if s1 != s3 { ok("str NOTEQ") } else { bad("str NOTEQ") }
|
||||||
|
if s1 != s2 { bad("str NOTEQ same") } else { ok("str NOTEQ same") }
|
||||||
|
if i1 != i3 { ok("int NOTEQ") } else { bad("int NOTEQ") }
|
||||||
|
if getint(9) != i1 { ok("int call NOTEQ") } else { bad("int call NOTEQ") }
|
||||||
|
println("done")
|
||||||
Reference in New Issue
Block a user