diff --git a/dist/platform/elc b/dist/platform/elc index 1ed0940..7b21bab 100755 Binary files a/dist/platform/elc and b/dist/platform/elc differ diff --git a/dist/platform/elc.c b/dist/platform/elc.c index c97779d..941891d 100644 --- a/dist/platform/elc.c +++ b/dist/platform/elc.c @@ -1898,6 +1898,17 @@ el_val_t cg_expr(el_val_t expr) { if (str_eq(right_kind, EL_STR("Bool"))) { return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" == ")), right_c), EL_STR(")")); } + if (str_eq(left_kind, EL_STR("Ident"))) { + if (str_eq(right_kind, EL_STR("Ident"))) { + el_val_t lname = el_get_field(left, EL_STR("name")); + el_val_t rname = el_get_field(right, EL_STR("name")); + if (is_int_name(lname)) { + if (is_int_name(rname)) { + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" == ")), right_c), EL_STR(")")); + } + } + } + } if (str_eq(left_kind, EL_STR("Str"))) { return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("str_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")")); } @@ -1930,6 +1941,17 @@ el_val_t cg_expr(el_val_t expr) { if (str_eq(right_kind, EL_STR("Bool"))) { return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" != ")), right_c), EL_STR(")")); } + if (str_eq(left_kind, EL_STR("Ident"))) { + if (str_eq(right_kind, EL_STR("Ident"))) { + el_val_t lname = el_get_field(left, EL_STR("name")); + el_val_t rname = el_get_field(right, EL_STR("name")); + if (is_int_name(lname)) { + if (is_int_name(rname)) { + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" != ")), right_c), EL_STR(")")); + } + } + } + } if (str_eq(left_kind, EL_STR("Str"))) { return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("!str_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")")); } @@ -1989,7 +2011,7 @@ el_val_t cg_expr(el_val_t expr) { el_val_t obj = el_get_field(expr, EL_STR("object")); el_val_t field = el_get_field(expr, EL_STR("field")); el_val_t obj_c = cg_expr(obj); - return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_get_field("), obj_c), EL_STR(", ")), c_str_lit(field)), EL_STR(")")); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_get_field("), obj_c), EL_STR(", EL_STR(")), c_str_lit(field)), EL_STR("))")); } if (str_eq(kind, EL_STR("Index"))) { el_val_t obj = el_get_field(expr, EL_STR("object")); @@ -2005,6 +2027,9 @@ el_val_t cg_expr(el_val_t expr) { if (str_eq(kind, EL_STR("Array"))) { el_val_t elems = el_get_field(expr, EL_STR("elems")); el_val_t n = native_list_len(elems); + if (n == 0) { + return EL_STR("el_list_empty()"); + } el_val_t items = EL_STR(""); el_val_t i = 0; while (i < n) { diff --git a/el-compiler/runtime/el_runtime.c b/el-compiler/runtime/el_runtime.c index daf4dac..3f9028f 100644 --- a/el-compiler/runtime/el_runtime.c +++ b/el-compiler/runtime/el_runtime.c @@ -11,9 +11,18 @@ * Link requirements: -lcurl (HTTP client + LLM), -lpthread (HTTP server). */ +/* Feature-test macros must be set before any standard headers. _GNU_SOURCE + * exposes clock_gettime/CLOCK_REALTIME, strcasecmp, and the dlfcn extensions + * (RTLD_DEFAULT) — all of which macOS hands us without asking but glibc on + * Debian gates behind an explicit opt-in. */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + #include "el_runtime.h" #include +#include /* strcasecmp */ #include #include #include @@ -877,7 +886,14 @@ static int http_read_request(int fd, char** out_method, char** out_path, char* hp = sp2 + 1; while (hp < hdr_end) { char* line_end = strstr(hp, "\r\n"); - if (!line_end || line_end >= hdr_end) break; + /* line_end == hdr_end means we're on the LAST header line — its + * trailing \r\n is the same \r\n that begins the \r\n\r\n header + * terminator. Process this line; only stop when line_end is past + * hdr_end (which means the parser walked off the end of the + * header block). The previous condition (line_end >= hdr_end) + * silently dropped any Content-Length that appeared as the last + * header — exactly what real curl/clients tend to emit. */ + if (!line_end || line_end > hdr_end) break; if (strncasecmp(hp, "Content-Length:", 15) == 0) { content_length = strtol(hp + 15, NULL, 10); if (content_length < 0) content_length = 0; @@ -1705,6 +1721,29 @@ el_val_t time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit) { return (el_val_t)d; } +/* Block the calling thread for `secs` seconds. Negative values are clamped + * to 0. Used by El programs that poll external resources (e.g. RunPod + * /status, Engram readiness probes). */ +el_val_t sleep_secs(el_val_t secs) { + int64_t s = (int64_t)secs; + if (s < 0) s = 0; + struct timespec ts; + ts.tv_sec = (time_t)s; + ts.tv_nsec = 0; + nanosleep(&ts, NULL); + return 0; +} + +el_val_t sleep_ms(el_val_t ms) { + int64_t m = (int64_t)ms; + if (m < 0) m = 0; + struct timespec ts; + ts.tv_sec = (time_t)(m / 1000LL); + ts.tv_nsec = (long)((m % 1000LL) * 1000000LL); + nanosleep(&ts, NULL); + return 0; +} + /* ── UUID v4 ─────────────────────────────────────────────────────────────── */ static int _el_uuid_seeded = 0; diff --git a/el-compiler/runtime/el_runtime.h b/el-compiler/runtime/el_runtime.h index 685f642..014d2ee 100644 --- a/el-compiler/runtime/el_runtime.h +++ b/el-compiler/runtime/el_runtime.h @@ -158,6 +158,8 @@ el_val_t json_array_len(el_val_t json_str); el_val_t time_now(void); el_val_t time_now_utc(void); +el_val_t sleep_secs(el_val_t secs); +el_val_t sleep_ms(el_val_t ms); el_val_t time_format(el_val_t ts, el_val_t fmt); el_val_t time_to_parts(el_val_t ts); el_val_t time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz); diff --git a/el-compiler/src/codegen.el b/el-compiler/src/codegen.el index 5406e50..6d9252d 100644 --- a/el-compiler/src/codegen.el +++ b/el-compiler/src/codegen.el @@ -253,9 +253,12 @@ fn cg_expr(expr: Map) -> String { } // String equality: use str_eq() when either side is a string literal or ident. - // Use plain == when comparing integer literals. + // Use plain == when comparing integer literals OR when both sides are + // identifiers tracked in __int_names (typed Int via `let x: Int = ...`). + // Without the int-name check, `seen == idx` between two Int locals + // miscompiles to str_eq(seen, idx), strcmp'ing what are integer values + // dressed as char* — segfault on the first non-printable byte. if op == "EqEq" { - // Integer literal on either side → arithmetic comparison if left_kind == "Int" { return "(" + left_c + " == " + right_c + ")" } @@ -268,6 +271,17 @@ fn cg_expr(expr: Map) -> String { if right_kind == "Bool" { return "(" + left_c + " == " + right_c + ")" } + if left_kind == "Ident" { + if right_kind == "Ident" { + let lname: String = left["name"] + let rname: String = right["name"] + if is_int_name(lname) { + if is_int_name(rname) { + return "(" + left_c + " == " + right_c + ")" + } + } + } + } if left_kind == "Str" { return "str_eq(" + left_c + ", " + right_c + ")" } @@ -301,6 +315,17 @@ fn cg_expr(expr: Map) -> String { if right_kind == "Bool" { return "(" + left_c + " != " + right_c + ")" } + if left_kind == "Ident" { + if right_kind == "Ident" { + let lname: String = left["name"] + let rname: String = right["name"] + if is_int_name(lname) { + if is_int_name(rname) { + return "(" + left_c + " != " + right_c + ")" + } + } + } + } if left_kind == "Str" { return "!str_eq(" + left_c + ", " + right_c + ")" } @@ -372,7 +397,12 @@ fn cg_expr(expr: Map) -> String { let obj = expr["object"] let field: String = expr["field"] let obj_c: String = cg_expr(obj) - return "el_get_field(" + obj_c + ", " + c_str_lit(field) + ")" + // el_get_field takes el_val_t for both args, so the field name + // string literal must be wrapped in EL_STR(). Without the wrap + // the C compiler treats the bare const char* as an int64 (warns + // -Wint-conversion) and the runtime reads gibberish at the address + // when looking up the key. + return "el_get_field(" + obj_c + ", EL_STR(" + c_str_lit(field) + "))" } if kind == "Index" { @@ -395,6 +425,9 @@ fn cg_expr(expr: Map) -> String { if kind == "Array" { let elems = expr["elems"] let n: Int = native_list_len(elems) + // Empty literal: el_list_new(0, ) generates malformed C (trailing + // comma in a varargs call). Emit el_list_empty() directly. + if n == 0 { return "el_list_empty()" } let items = "" let i = 0 while i < n { diff --git a/elc-combined.el b/elc-combined.el index 1fd5608..e068919 100644 --- a/elc-combined.el +++ b/elc-combined.el @@ -1703,9 +1703,11 @@ fn cg_expr(expr: Map) -> String { } // String equality: use str_eq() when either side is a string literal or ident. - // Use plain == when comparing integer literals. + // Use plain == when comparing integer literals OR when both sides are + // identifiers tracked in __int_names. Without the int-name check, + // `seen == idx` between two Int locals miscompiles to str_eq on what + // are integer values dressed as char* — segfault. if op == "EqEq" { - // Integer literal on either side → arithmetic comparison if left_kind == "Int" { return "(" + left_c + " == " + right_c + ")" } @@ -1718,6 +1720,17 @@ fn cg_expr(expr: Map) -> String { if right_kind == "Bool" { return "(" + left_c + " == " + right_c + ")" } + if left_kind == "Ident" { + if right_kind == "Ident" { + let lname: String = left["name"] + let rname: String = right["name"] + if is_int_name(lname) { + if is_int_name(rname) { + return "(" + left_c + " == " + right_c + ")" + } + } + } + } if left_kind == "Str" { return "str_eq(" + left_c + ", " + right_c + ")" } @@ -1751,6 +1764,17 @@ fn cg_expr(expr: Map) -> String { if right_kind == "Bool" { return "(" + left_c + " != " + right_c + ")" } + if left_kind == "Ident" { + if right_kind == "Ident" { + let lname: String = left["name"] + let rname: String = right["name"] + if is_int_name(lname) { + if is_int_name(rname) { + return "(" + left_c + " != " + right_c + ")" + } + } + } + } if left_kind == "Str" { return "!str_eq(" + left_c + ", " + right_c + ")" } @@ -1822,7 +1846,7 @@ fn cg_expr(expr: Map) -> String { let obj = expr["object"] let field: String = expr["field"] let obj_c: String = cg_expr(obj) - return "el_get_field(" + obj_c + ", " + c_str_lit(field) + ")" + return "el_get_field(" + obj_c + ", EL_STR(" + c_str_lit(field) + "))" } if kind == "Index" { @@ -1845,6 +1869,7 @@ fn cg_expr(expr: Map) -> String { if kind == "Array" { let elems = expr["elems"] let n: Int = native_list_len(elems) + if n == 0 { return "el_list_empty()" } let items = "" let i = 0 while i < n {