diff --git a/dist/platform/elc b/dist/platform/elc index b1e96c9..c52f4fe 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 e3e2559..785e7ae 100644 --- a/dist/platform/elc.c +++ b/dist/platform/elc.c @@ -49,6 +49,9 @@ el_val_t cg_stmts(el_val_t stmts, el_val_t indent, el_val_t declared); el_val_t param_decl(el_val_t param, el_val_t idx); el_val_t params_to_c(el_val_t params); el_val_t transform_implicit_return(el_val_t body); +el_val_t is_int_name(el_val_t name); +el_val_t add_int_name(el_val_t name); +el_val_t build_int_names_for_params(el_val_t params); el_val_t cg_fn(el_val_t stmt); el_val_t is_fndef(el_val_t stmt); el_val_t is_top_level_decl(el_val_t stmt); @@ -878,8 +881,13 @@ el_val_t parse_params(el_val_t tokens, el_val_t pos) { el_val_t pname = tok_value(tokens, p); p = (p + 1); p = expect(tokens, p, EL_STR("Colon")); + el_val_t ptype = EL_STR(""); + el_val_t kt = tok_kind(tokens, p); + if (str_eq(kt, EL_STR("Ident"))) { + ptype = tok_value(tokens, p); + } p = skip_type(tokens, p); - el_val_t param = el_map_new(1, "name", pname); + el_val_t param = el_map_new(2, "name", pname, "type", ptype); params = native_list_append(params, param); el_val_t k2 = tok_kind(tokens, p); if (str_eq(k2, EL_STR("Comma"))) { @@ -1309,16 +1317,21 @@ el_val_t parse_stmt(el_val_t tokens, el_val_t pos) { el_val_t p = (pos + 1); el_val_t name = tok_value(tokens, p); p = (p + 1); + el_val_t ltype = EL_STR(""); el_val_t k2 = tok_kind(tokens, p); if (str_eq(k2, EL_STR("Colon"))) { p = (p + 1); + el_val_t kt = tok_kind(tokens, p); + if (str_eq(kt, EL_STR("Ident"))) { + ltype = tok_value(tokens, p); + } p = skip_type(tokens, p); } p = expect(tokens, p, EL_STR("Eq")); el_val_t r = parse_expr(tokens, p); el_val_t val = el_get_field(r, EL_STR("node")); p = el_get_field(r, EL_STR("pos")); - return make_result(el_map_new(3, "stmt", EL_STR("Let"), "name", name, "value", val), p); + return make_result(el_map_new(4, "stmt", EL_STR("Let"), "name", name, "value", val, "type", ltype), p); } if (str_eq(k, EL_STR("Return"))) { el_val_t p = (pos + 1); @@ -1690,6 +1703,18 @@ el_val_t cg_expr(el_val_t expr) { el_val_t op_c = binop_to_c(op); return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" ")), op_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)) { + el_val_t op_c = binop_to_c(op); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" ")), op_c), EL_STR(" ")), right_c), EL_STR(")")); + } + } + } + } if (str_eq(left_kind, EL_STR("Call"))) { return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_str_concat("), left_c), EL_STR(", ")), right_c), EL_STR(")")); } @@ -1898,6 +1923,14 @@ el_val_t cg_stmt(el_val_t stmt, el_val_t indent, el_val_t declared) { el_val_t name = el_get_field(stmt, EL_STR("name")); el_val_t val = el_get_field(stmt, EL_STR("value")); el_val_t val_c = cg_expr(val); + el_val_t ltype = el_get_field(stmt, EL_STR("type")); + if (str_eq(ltype, EL_STR("Int"))) { + add_int_name(name); + } + el_val_t vk = el_get_field(val, EL_STR("expr")); + if (str_eq(vk, EL_STR("Int"))) { + add_int_name(name); + } if (list_contains(declared, name)) { emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(indent, name), EL_STR(" = ")), val_c), EL_STR(";"))); return declared; @@ -2118,6 +2151,48 @@ el_val_t transform_implicit_return(el_val_t body) { return 0; } +el_val_t is_int_name(el_val_t name) { + el_val_t csv = state_get(EL_STR("__int_names")); + if (str_eq(csv, EL_STR(""))) { + return 0; + } + return str_contains(csv, el_str_concat(el_str_concat(EL_STR(","), name), EL_STR(","))); + return 0; +} + +el_val_t add_int_name(el_val_t name) { + el_val_t csv = state_get(EL_STR("__int_names")); + if (str_eq(csv, EL_STR(""))) { + csv; + EL_NULL; + EL_STR(","); + } + el_val_t key = el_str_concat(el_str_concat(EL_STR(","), name), EL_STR(",")); + if (str_contains(csv, key)) { + return 1; + } + state_set(EL_STR("__int_names"), el_str_concat(el_str_concat(csv, name), EL_STR(","))); + return 1; + return 0; +} + +el_val_t build_int_names_for_params(el_val_t params) { + state_set(EL_STR("__int_names"), EL_STR(",")); + el_val_t np = native_list_len(params); + el_val_t pi = 0; + while (pi < np) { + el_val_t param = native_list_get(params, pi); + el_val_t pname = el_get_field(param, EL_STR("name")); + el_val_t ptype = el_get_field(param, EL_STR("type")); + if (str_eq(ptype, EL_STR("Int"))) { + add_int_name(pname); + } + pi = (pi + 1); + } + return 1; + return 0; +} + el_val_t cg_fn(el_val_t stmt) { el_val_t fn_name = el_get_field(stmt, EL_STR("name")); if (str_eq(fn_name, EL_STR("main"))) { @@ -2127,6 +2202,7 @@ el_val_t cg_fn(el_val_t stmt) { el_val_t body = el_get_field(stmt, EL_STR("body")); el_val_t ret_type = el_get_field(stmt, EL_STR("ret_type")); el_val_t params_c = params_to_c(params); + build_int_names_for_params(params); emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_val_t "), fn_name), EL_STR("(")), params_c), EL_STR(") {"))); el_val_t decl = native_list_empty(); el_val_t np = native_list_len(params); diff --git a/dist/platform/elc.prev b/dist/platform/elc.prev new file mode 100755 index 0000000..b1e96c9 Binary files /dev/null and b/dist/platform/elc.prev differ diff --git a/el-compiler/src/codegen.el b/el-compiler/src/codegen.el index 4292cbc..4a4923e 100644 --- a/el-compiler/src/codegen.el +++ b/el-compiler/src/codegen.el @@ -170,6 +170,23 @@ fn cg_expr(expr: Map) -> String { let op_c: String = binop_to_c(op) return "(" + left_c + " " + op_c + " " + right_c + ")" } + // Type-driven dispatch: if both sides are Idents declared + // with type Int (parameters annotated `: Int` or let bindings + // annotated `: Int`), this is arithmetic, not concat. The + // current-function int-name set is maintained by cg_fn / + // cg_stmt via state_set("__int_names", csv). + 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) { + let op_c: String = binop_to_c(op) + return "(" + left_c + " " + op_c + " " + right_c + ")" + } + } + } + } if left_kind == "Call" { return "el_str_concat(" + left_c + ", " + right_c + ")" } @@ -188,8 +205,8 @@ fn cg_expr(expr: Map) -> String { return "el_str_concat(" + left_c + ", " + right_c + ")" } } - // Ident + Ident or Ident + unknown — assume string concat - // (This is the ambiguous case: El uses + for both string and integer ops) + // Ident + Ident or Ident + unknown without int-typed evidence — + // fall back to string concat (the historical heuristic). if left_kind == "Ident" { return "el_str_concat(" + left_c + ", " + right_c + ")" } @@ -413,6 +430,17 @@ fn cg_stmt(stmt: Map, indent: String, declared: [String]) -> [Strin let name: String = stmt["name"] let val = stmt["value"] let val_c: String = cg_expr(val) + // If the binding is annotated `: Int` and val is an Int literal, + // register `name` in the per-function int-name set so that later + // `name + ...` dispatches to arithmetic, not concat. + let ltype: String = stmt["type"] + if str_eq(ltype, "Int") { + add_int_name(name) + } + let vk: String = val["expr"] + if str_eq(vk, "Int") { + add_int_name(name) + } if list_contains(declared, name) { emit_line(indent + name + " = " + val_c + ";") return declared @@ -627,6 +655,41 @@ fn transform_implicit_return(body: [Map]) -> [Map] { body } +// Test whether `name` is currently registered as an Int-typed identifier +// for the function being codegened. The set is maintained as a comma- +// bounded CSV in process state; cg_fn seeds it from typed parameters, +// cg_stmt extends it from typed `let` bindings. +fn is_int_name(name: String) -> Bool { + let csv: String = state_get("__int_names") + if str_eq(csv, "") { return false } + return str_contains(csv, "," + name + ",") +} + +fn add_int_name(name: String) -> Bool { + let csv: String = state_get("__int_names") + if str_eq(csv, "") { csv = "," } + let key: String = "," + name + "," + if str_contains(csv, key) { return true } + state_set("__int_names", csv + name + ",") + return true +} + +fn build_int_names_for_params(params: [Map]) -> Bool { + state_set("__int_names", ",") + let np: Int = native_list_len(params) + let pi = 0 + while pi < np { + let param = native_list_get(params, pi) + let pname: String = param["name"] + let ptype: String = param["type"] + if str_eq(ptype, "Int") { + add_int_name(pname) + } + let pi = pi + 1 + } + return true +} + fn cg_fn(stmt: Map) -> Void { let fn_name: String = stmt["name"] // Skip El's `fn main()` — C provides its own main() for top-level stmts @@ -636,6 +699,9 @@ fn cg_fn(stmt: Map) -> Void { let body = stmt["body"] let ret_type: String = stmt["ret_type"] let params_c: String = params_to_c(params) + // Seed the per-function int-name set so the `+` codegen can dispatch + // arithmetic vs concat on type-annotated identifiers. + build_int_names_for_params(params) emit_line("el_val_t " + fn_name + "(" + params_c + ") {") // Seed declared with parameter names so reassignment works let decl = native_list_empty() diff --git a/el-compiler/src/parser.el b/el-compiler/src/parser.el index 488b107..d1339a8 100644 --- a/el-compiler/src/parser.el +++ b/el-compiler/src/parser.el @@ -119,8 +119,15 @@ fn parse_params(tokens: [Map], pos: Int) -> Map { let pname = tok_value(tokens, p) let p = p + 1 let p = expect(tokens, p, "Colon") + // Capture the leading type identifier so codegen can dispatch + // arithmetic vs string-concat on `+` based on declared types. + let ptype = "" + let kt = tok_kind(tokens, p) + if kt == "Ident" { + let ptype = tok_value(tokens, p) + } let p = skip_type(tokens, p) - let param = { "name": pname } + let param = { "name": pname, "type": ptype } let params = native_list_append(params, param) let k2 = tok_kind(tokens, p) if k2 == "Comma" { @@ -533,17 +540,24 @@ fn parse_stmt(tokens: [Map], pos: Int) -> Map { let p = pos + 1 let name = tok_value(tokens, p) let p = p + 1 + let ltype = "" let k2 = tok_kind(tokens, p) - // optional type annotation: name: Type + // optional type annotation: name: Type — capture the leading + // identifier so codegen can dispatch arithmetic vs concat on + // `+` between two typed Idents. if k2 == "Colon" { let p = p + 1 + let kt = tok_kind(tokens, p) + if kt == "Ident" { + let ltype = tok_value(tokens, p) + } let p = skip_type(tokens, p) } let p = expect(tokens, p, "Eq") let r = parse_expr(tokens, p) let val = r["node"] let p = r["pos"] - return make_result({ "stmt": "Let", "name": name, "value": val }, p) + return make_result({ "stmt": "Let", "name": name, "value": val, "type": ltype }, p) } // return statement diff --git a/elc-combined.el b/elc-combined.el index 2b3799a..aae8375 100644 --- a/elc-combined.el +++ b/elc-combined.el @@ -658,8 +658,15 @@ fn parse_params(tokens: [Map], pos: Int) -> Map { let pname = tok_value(tokens, p) let p = p + 1 let p = expect(tokens, p, "Colon") + // Capture the leading type identifier so codegen can dispatch + // arithmetic vs string-concat on `+` based on declared types. + let ptype = "" + let kt = tok_kind(tokens, p) + if kt == "Ident" { + let ptype = tok_value(tokens, p) + } let p = skip_type(tokens, p) - let param = { "name": pname } + let param = { "name": pname, "type": ptype } let params = native_list_append(params, param) let k2 = tok_kind(tokens, p) if k2 == "Comma" { @@ -1072,17 +1079,24 @@ fn parse_stmt(tokens: [Map], pos: Int) -> Map { let p = pos + 1 let name = tok_value(tokens, p) let p = p + 1 + let ltype = "" let k2 = tok_kind(tokens, p) - // optional type annotation: name: Type + // optional type annotation: name: Type — capture the leading + // identifier so codegen can dispatch arithmetic vs concat on + // `+` between two typed Idents. if k2 == "Colon" { let p = p + 1 + let kt = tok_kind(tokens, p) + if kt == "Ident" { + let ltype = tok_value(tokens, p) + } let p = skip_type(tokens, p) } let p = expect(tokens, p, "Eq") let r = parse_expr(tokens, p) let val = r["node"] let p = r["pos"] - return make_result({ "stmt": "Let", "name": name, "value": val }, p) + return make_result({ "stmt": "Let", "name": name, "value": val, "type": ltype }, p) } // return statement @@ -1479,6 +1493,23 @@ fn cg_expr(expr: Map) -> String { let op_c: String = binop_to_c(op) return "(" + left_c + " " + op_c + " " + right_c + ")" } + // Type-driven dispatch: if both sides are Idents declared + // with type Int (parameters annotated `: Int` or let bindings + // annotated `: Int`), this is arithmetic, not concat. The + // current-function int-name set is maintained by cg_fn / + // cg_stmt via state_set("__int_names", csv). + 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) { + let op_c: String = binop_to_c(op) + return "(" + left_c + " " + op_c + " " + right_c + ")" + } + } + } + } if left_kind == "Call" { return "el_str_concat(" + left_c + ", " + right_c + ")" } @@ -1497,8 +1528,8 @@ fn cg_expr(expr: Map) -> String { return "el_str_concat(" + left_c + ", " + right_c + ")" } } - // Ident + Ident or Ident + unknown — assume string concat - // (This is the ambiguous case: El uses + for both string and integer ops) + // Ident + Ident or Ident + unknown without int-typed evidence — + // fall back to string concat (the historical heuristic). if left_kind == "Ident" { return "el_str_concat(" + left_c + ", " + right_c + ")" } @@ -1722,6 +1753,17 @@ fn cg_stmt(stmt: Map, indent: String, declared: [String]) -> [Strin let name: String = stmt["name"] let val = stmt["value"] let val_c: String = cg_expr(val) + // If the binding is annotated `: Int` and val is an Int literal, + // register `name` in the per-function int-name set so that later + // `name + ...` dispatches to arithmetic, not concat. + let ltype: String = stmt["type"] + if str_eq(ltype, "Int") { + add_int_name(name) + } + let vk: String = val["expr"] + if str_eq(vk, "Int") { + add_int_name(name) + } if list_contains(declared, name) { emit_line(indent + name + " = " + val_c + ";") return declared @@ -1936,6 +1978,41 @@ fn transform_implicit_return(body: [Map]) -> [Map] { body } +// Test whether `name` is currently registered as an Int-typed identifier +// for the function being codegened. The set is maintained as a comma- +// bounded CSV in process state; cg_fn seeds it from typed parameters, +// cg_stmt extends it from typed `let` bindings. +fn is_int_name(name: String) -> Bool { + let csv: String = state_get("__int_names") + if str_eq(csv, "") { return false } + return str_contains(csv, "," + name + ",") +} + +fn add_int_name(name: String) -> Bool { + let csv: String = state_get("__int_names") + if str_eq(csv, "") { csv = "," } + let key: String = "," + name + "," + if str_contains(csv, key) { return true } + state_set("__int_names", csv + name + ",") + return true +} + +fn build_int_names_for_params(params: [Map]) -> Bool { + state_set("__int_names", ",") + let np: Int = native_list_len(params) + let pi = 0 + while pi < np { + let param = native_list_get(params, pi) + let pname: String = param["name"] + let ptype: String = param["type"] + if str_eq(ptype, "Int") { + add_int_name(pname) + } + let pi = pi + 1 + } + return true +} + fn cg_fn(stmt: Map) -> Void { let fn_name: String = stmt["name"] // Skip El's `fn main()` — C provides its own main() for top-level stmts @@ -1945,6 +2022,9 @@ fn cg_fn(stmt: Map) -> Void { let body = stmt["body"] let ret_type: String = stmt["ret_type"] let params_c: String = params_to_c(params) + // Seed the per-function int-name set so the `+` codegen can dispatch + // arithmetic vs concat on type-annotated identifiers. + build_int_names_for_params(params) emit_line("el_val_t " + fn_name + "(" + params_c + ") {") // Seed declared with parameter names so reassignment works let decl = native_list_empty() @@ -2074,7 +2154,7 @@ fn compile(source: String) -> String { // result to args()[1]. Then run: // cc -o el_runtime.c -// CLI driver — equivalent to: elc +// CLI driver let _argv: [String] = args() let _src_path: String = native_list_get(_argv, 0) let _source: String = fs_read(_src_path)