diff --git a/dist/platform/elc b/dist/platform/elc index fb7502c..d3abc23 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 52ef58a..14b2b87 100644 --- a/dist/platform/elc.c +++ b/dist/platform/elc.c @@ -37,6 +37,9 @@ el_val_t parse_binop(el_val_t tokens, el_val_t pos, el_val_t min_prec); el_val_t parse_expr(el_val_t tokens, el_val_t pos); el_val_t parse_stmt(el_val_t tokens, el_val_t pos); el_val_t parse(el_val_t tokens); +el_val_t nibble_to_hex(el_val_t n); +el_val_t byte_to_hex2(el_val_t b); +el_val_t is_hex_digit_byte(el_val_t b); el_val_t c_escape(el_val_t s); el_val_t c_str_lit(el_val_t s); el_val_t el_type_to_c(el_val_t type_str); @@ -126,6 +129,9 @@ el_val_t js_str_lit(el_val_t s); el_val_t js_emit_line(el_val_t line); el_val_t js_emit_blank(void); el_val_t js_binop(el_val_t op); +el_val_t js_is_async_builtin(el_val_t name); +el_val_t js_register_async_fn(el_val_t name); +el_val_t js_is_async_fn(el_val_t name); el_val_t js_is_int_name(el_val_t name); el_val_t js_add_int_name(el_val_t name); el_val_t js_build_int_names_for_params(el_val_t params); @@ -146,14 +152,32 @@ el_val_t js_cg_fn(el_val_t stmt); el_val_t js_is_fndef(el_val_t stmt); el_val_t js_is_top_level_decl(el_val_t stmt); el_val_t codegen_js(el_val_t stmts, el_val_t source); +el_val_t codegen_js_bundle(el_val_t stmts, el_val_t source, el_val_t runtime_content); +el_val_t codegen_js_inner(el_val_t stmts, el_val_t source, el_val_t bundle_mode, el_val_t runtime_content); +el_val_t js_strip_es_exports(el_val_t content); el_val_t compile(el_val_t source); el_val_t compile_js(el_val_t source); +el_val_t compile_js_with_bundle(el_val_t source, el_val_t runtime_path); el_val_t compile_dispatch(el_val_t tgt, el_val_t source); +el_val_t compile_dispatch_bundle(el_val_t tgt, el_val_t source, el_val_t runtime_path); el_val_t detect_target(el_val_t argv); el_val_t strip_flags(el_val_t argv); +el_val_t detect_emit_header(el_val_t argv); +el_val_t detect_bundle(el_val_t argv); +el_val_t detect_minify(el_val_t argv); +el_val_t detect_obfuscate(el_val_t argv); +el_val_t make_temp_path(el_val_t suffix); +el_val_t js_reserved_names(void); +el_val_t find_node_tool(el_val_t tool_name, el_val_t src_dir); +el_val_t apply_minify(el_val_t js_path, el_val_t out_path, el_val_t src_dir); +el_val_t apply_obfuscate(el_val_t js_path, el_val_t out_path, el_val_t src_dir); +el_val_t resolve_runtime_path(el_val_t src_path); +el_val_t type_node_to_el(el_val_t t); +el_val_t emit_header(el_val_t stmts, el_val_t hdr_path); el_val_t dirname_of(el_val_t path); el_val_t parse_import_line(el_val_t trimmed, el_val_t dir); el_val_t resolve_imports(el_val_t src_path); +el_val_t run_with_postprocess(el_val_t tgt, el_val_t source, el_val_t src_path, el_val_t do_bundle, el_val_t do_obfuscate, el_val_t argc, el_val_t positional); el_val_t lex_is_digit(el_val_t ch) { if (str_eq(ch, EL_STR("0"))) { @@ -514,13 +538,16 @@ el_val_t keyword_kind(el_val_t word) { if (str_eq(word, EL_STR("vessel"))) { return EL_STR("Vessel"); } + if (str_eq(word, EL_STR("extern"))) { + return EL_STR("Extern"); + } return EL_STR(""); return 0; } el_val_t scan_digits(el_val_t chars, el_val_t start, el_val_t total) { el_val_t i = start; - el_val_t text = EL_STR(""); + el_val_t parts = native_list_empty(); el_val_t running = 1; while (running) { if (i >= total) { @@ -528,20 +555,20 @@ el_val_t scan_digits(el_val_t chars, el_val_t start, el_val_t total) { } else { el_val_t ch = native_list_get(chars, i); if (lex_is_digit(ch)) { - text = el_str_concat(text, ch); + parts = native_list_append(parts, ch); i = (i + 1); } else { running = 0; } } } - return el_map_new(2, "text", text, "pos", i); + return el_map_new(2, "text", str_join(parts, EL_STR("")), "pos", i); return 0; } el_val_t scan_ident(el_val_t chars, el_val_t start, el_val_t total) { el_val_t i = start; - el_val_t text = EL_STR(""); + el_val_t parts = native_list_empty(); el_val_t running = 1; while (running) { if (i >= total) { @@ -549,14 +576,14 @@ el_val_t scan_ident(el_val_t chars, el_val_t start, el_val_t total) { } else { el_val_t ch = native_list_get(chars, i); if (is_alnum_or_underscore(ch)) { - text = el_str_concat(text, ch); + parts = native_list_append(parts, ch); i = (i + 1); } else { running = 0; } } } - return el_map_new(2, "text", text, "pos", i); + return el_map_new(2, "text", str_join(parts, EL_STR("")), "pos", i); return 0; } @@ -619,7 +646,7 @@ el_val_t looks_like_code(el_val_t s) { el_val_t strip_code_comments(el_val_t s) { el_val_t chars = native_string_chars(s); el_val_t total = native_list_len(chars); - el_val_t out = EL_STR(""); + el_val_t out_parts = native_list_empty(); el_val_t i = 0; el_val_t in_squote = 0; el_val_t in_dquote = 0; @@ -639,11 +666,11 @@ el_val_t strip_code_comments(el_val_t s) { } if (in_js_string) { if (str_eq(ch, EL_STR("\\"))) { - out = el_str_concat(out, ch); + out_parts = native_list_append(out_parts, ch); el_val_t next_i = (i + 1); if (next_i < total) { el_val_t nc = native_list_get(chars, next_i); - out = el_str_concat(out, nc); + out_parts = native_list_append(out_parts, nc); prev = nc; i = (next_i + 1); } else { @@ -668,7 +695,7 @@ el_val_t strip_code_comments(el_val_t s) { } } } - out = el_str_concat(out, ch); + out_parts = native_list_append(out_parts, ch); prev = ch; i = (i + 1); } @@ -681,7 +708,7 @@ el_val_t strip_code_comments(el_val_t s) { if (str_eq(ch, EL_STR("/"))) { if (str_eq(next_ch, EL_STR("/"))) { if (str_eq(prev, EL_STR(":"))) { - out = el_str_concat(out, ch); + out_parts = native_list_append(out_parts, ch); prev = ch; i = (i + 1); } else { @@ -730,7 +757,7 @@ el_val_t strip_code_comments(el_val_t s) { } prev = EL_STR(""); } else { - out = el_str_concat(out, ch); + out_parts = native_list_append(out_parts, ch); prev = ch; i = (i + 1); } @@ -738,23 +765,23 @@ el_val_t strip_code_comments(el_val_t s) { } else { if (str_eq(ch, EL_STR("'"))) { in_squote = 1; - out = el_str_concat(out, ch); + out_parts = native_list_append(out_parts, ch); prev = ch; i = (i + 1); } else { if (str_eq(ch, EL_STR("\""))) { in_dquote = 1; - out = el_str_concat(out, ch); + out_parts = native_list_append(out_parts, ch); prev = ch; i = (i + 1); } else { if (str_eq(ch, EL_STR("`"))) { in_btick = 1; - out = el_str_concat(out, ch); + out_parts = native_list_append(out_parts, ch); prev = ch; i = (i + 1); } else { - out = el_str_concat(out, ch); + out_parts = native_list_append(out_parts, ch); prev = ch; i = (i + 1); } @@ -763,13 +790,13 @@ el_val_t strip_code_comments(el_val_t s) { } } } - return out; + return str_join(out_parts, EL_STR("")); return 0; } el_val_t scan_string(el_val_t chars, el_val_t start, el_val_t total) { el_val_t i = start; - el_val_t text = EL_STR(""); + el_val_t parts = native_list_empty(); el_val_t running = 1; while (running) { if (i >= total) { @@ -781,26 +808,26 @@ el_val_t scan_string(el_val_t chars, el_val_t start, el_val_t total) { if (next_i < total) { el_val_t next_ch = native_list_get(chars, next_i); if (str_eq(next_ch, EL_STR("\""))) { - text = el_str_concat(text, EL_STR("\"")); + parts = native_list_append(parts, EL_STR("\"")); i = (next_i + 1); } else { if (str_eq(next_ch, EL_STR("n"))) { - text = el_str_concat(text, EL_STR("\n")); + parts = native_list_append(parts, EL_STR("\n")); i = (next_i + 1); } else { if (str_eq(next_ch, EL_STR("t"))) { - text = el_str_concat(text, EL_STR("\t")); + parts = native_list_append(parts, EL_STR("\t")); i = (next_i + 1); } else { if (str_eq(next_ch, EL_STR("r"))) { - text = el_str_concat(text, EL_STR("\r")); + parts = native_list_append(parts, EL_STR("\r")); i = (next_i + 1); } else { if (str_eq(next_ch, EL_STR("\\"))) { - text = el_str_concat(text, EL_STR("\\")); + parts = native_list_append(parts, EL_STR("\\")); i = (next_i + 1); } else { - text = el_str_concat(text, next_ch); + parts = native_list_append(parts, next_ch); i = (next_i + 1); } } @@ -815,13 +842,13 @@ el_val_t scan_string(el_val_t chars, el_val_t start, el_val_t total) { i = (i + 1); running = 0; } else { - text = el_str_concat(text, ch); + parts = native_list_append(parts, ch); i = (i + 1); } } } } - return el_map_new(2, "text", text, "pos", i); + return el_map_new(2, "text", str_join(parts, EL_STR("")), "pos", i); return 0; } @@ -1465,6 +1492,11 @@ el_val_t parse_pattern(el_val_t tokens, el_val_t pos) { if (str_eq(v, EL_STR("_"))) { return make_result(el_map_new(1, "pattern", EL_STR("Wildcard")), (pos + 1)); } + el_val_t next_k = tok_kind(tokens, (pos + 1)); + if (str_eq(next_k, EL_STR("ColonColon"))) { + el_val_t variant_name = tok_value(tokens, (pos + 2)); + return make_result(el_map_new(3, "pattern", EL_STR("Variant"), "enum_name", v, "variant", variant_name), (pos + 3)); + } return make_result(el_map_new(2, "pattern", EL_STR("Binding"), "name", v), (pos + 1)); } if (str_eq(k, EL_STR("Int"))) { @@ -1797,6 +1829,29 @@ el_val_t parse_stmt(el_val_t tokens, el_val_t pos) { p = el_get_field(r, EL_STR("pos")); return make_result(el_map_new(2, "stmt", EL_STR("Return"), "value", val), p); } + if (str_eq(k, EL_STR("Extern"))) { + el_val_t p = (pos + 1); + el_val_t k2 = tok_kind(tokens, p); + if (str_eq(k2, EL_STR("Fn"))) { + p = (p + 1); + el_val_t name = tok_value(tokens, p); + p = (p + 1); + el_val_t r = parse_params(tokens, p); + el_val_t params = el_get_field(r, EL_STR("params")); + p = el_get_field(r, EL_STR("pos")); + el_val_t ret_type = EL_STR(""); + el_val_t k3 = tok_kind(tokens, p); + if (str_eq(k3, EL_STR("Arrow"))) { + p = (p + 1); + el_val_t kt = tok_kind(tokens, p); + if (str_eq(kt, EL_STR("Ident"))) { + ret_type = tok_value(tokens, p); + } + p = skip_type(tokens, p); + } + return make_result(el_map_new(4, "stmt", EL_STR("ExternFn"), "name", name, "params", params, "ret_type", ret_type), p); + } + } if (str_eq(k, EL_STR("Fn"))) { el_val_t p = (pos + 1); el_val_t name = tok_value(tokens, p); @@ -1823,6 +1878,10 @@ 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 pk = tok_kind(tokens, p); + if (str_eq(pk, EL_STR("Eq"))) { + p = (p + 1); + } p = expect(tokens, p, EL_STR("LBrace")); el_val_t fields = native_list_empty(); el_val_t running = 1; @@ -2095,29 +2154,72 @@ el_val_t parse(el_val_t tokens) { return 0; } +el_val_t nibble_to_hex(el_val_t n) { + return str_char_at(EL_STR("0123456789abcdef"), n); + return 0; +} + +el_val_t byte_to_hex2(el_val_t b) { + el_val_t hi = (b / 16); + el_val_t lo = (b - (hi * 16)); + return el_str_concat(nibble_to_hex(hi), nibble_to_hex(lo)); + return 0; +} + +el_val_t is_hex_digit_byte(el_val_t b) { + if (b >= 48) { + if (b <= 57) { + return 1; + } + } + if (b >= 65) { + if (b <= 70) { + return 1; + } + } + if (b >= 97) { + if (b <= 102) { + return 1; + } + } + return 0; + return 0; +} + el_val_t c_escape(el_val_t s) { - el_val_t chars = native_string_chars(s); - el_val_t total = native_list_len(chars); - el_val_t out = EL_STR(""); + el_val_t total = str_len(s); + el_val_t parts = native_list_empty(); el_val_t i = 0; + el_val_t prev_was_hex_escape = 0; while (i < total) { - el_val_t ch = native_list_get(chars, i); - if (str_eq(ch, EL_STR("\""))) { - out = el_str_concat(out, EL_STR("\\\"")); + el_val_t bval = str_char_code(s, i); + if (prev_was_hex_escape) { + if (is_hex_digit_byte(bval)) { + parts = native_list_append(parts, EL_STR("\"\"")); + } + } + prev_was_hex_escape = 0; + if (bval == 34) { + parts = native_list_append(parts, EL_STR("\\\"")); } else { - if (str_eq(ch, EL_STR("\\"))) { - out = el_str_concat(out, EL_STR("\\\\")); + if (bval == 92) { + parts = native_list_append(parts, EL_STR("\\\\")); } else { - if (str_eq(ch, EL_STR("\n"))) { - out = el_str_concat(out, EL_STR("\\n")); + if (bval == 10) { + parts = native_list_append(parts, EL_STR("\\n")); } else { - if (str_eq(ch, EL_STR("\r"))) { - out = el_str_concat(out, EL_STR("\\r")); + if (bval == 13) { + parts = native_list_append(parts, EL_STR("\\r")); } else { - if (str_eq(ch, EL_STR("\t"))) { - out = el_str_concat(out, EL_STR("\\t")); + if (bval == 9) { + parts = native_list_append(parts, EL_STR("\\t")); } else { - out = el_str_concat(out, ch); + if (bval >= 128) { + parts = native_list_append(parts, el_str_concat(EL_STR("\\x"), byte_to_hex2(bval))); + prev_was_hex_escape = 1; + } else { + parts = native_list_append(parts, str_char_at(s, i)); + } } } } @@ -2125,7 +2227,7 @@ el_val_t c_escape(el_val_t s) { } i = (i + 1); } - return out; + return str_join(parts, EL_STR("")); return 0; } @@ -2307,6 +2409,30 @@ el_val_t cg_expr(el_val_t expr) { el_val_t right_c = cg_expr(right); el_val_t left_kind = el_get_field(left, EL_STR("expr")); el_val_t right_kind = el_get_field(right, EL_STR("expr")); + if (str_eq(op, EL_STR("Plus"))) { + if (str_eq(left_kind, EL_STR("Str"))) { + 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(")")); + } + if (str_eq(right_kind, EL_STR("Str"))) { + 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(")")); + } + } + if (str_eq(op, EL_STR("EqEq"))) { + 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(")")); + } + if (str_eq(right_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(")")); + } + } + if (str_eq(op, EL_STR("NotEq"))) { + 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(")")); + } + if (str_eq(right_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(")")); + } + } el_val_t left_is_inst = is_instant_expr(left); el_val_t right_is_inst = is_instant_expr(right); el_val_t left_is_dur = is_duration_expr(left); @@ -2661,17 +2787,15 @@ el_val_t cg_expr(el_val_t expr) { el_val_t args = el_get_field(expr, EL_STR("args")); el_val_t arity = native_list_len(args); el_val_t func_kind = el_get_field(func, EL_STR("expr")); - el_val_t args_c = EL_STR(""); + el_val_t args_parts = native_list_empty(); el_val_t i = 0; while (i < arity) { el_val_t arg = native_list_get(args, i); el_val_t arg_c = cg_expr(arg); - if (i > 0) { - args_c = el_str_concat(args_c, EL_STR(", ")); - } - args_c = el_str_concat(args_c, arg_c); + args_parts = native_list_append(args_parts, arg_c); i = (i + 1); } + el_val_t args_c = str_join(args_parts, EL_STR(", ")); if (str_eq(func_kind, EL_STR("Ident"))) { el_val_t fn_name = el_get_field(func, EL_STR("name")); cap_check_call(fn_name); @@ -2684,6 +2808,16 @@ el_val_t cg_expr(el_val_t expr) { } } } + if (str_eq(fn_name, EL_STR("el_from_float"))) { + if (arity == 1) { + el_val_t only_arg = native_list_get(args, 0); + el_val_t arg_kind = el_get_field(only_arg, EL_STR("expr")); + if (str_eq(arg_kind, EL_STR("Float"))) { + el_val_t v = el_get_field(only_arg, EL_STR("value")); + return el_str_concat(el_str_concat(EL_STR("el_from_float("), v), EL_STR(")")); + } + } + } return el_str_concat(el_str_concat(el_str_concat(fn_name, EL_STR("(")), args_c), EL_STR(")")); } if (str_eq(func_kind, EL_STR("Field"))) { @@ -2721,18 +2855,15 @@ el_val_t cg_expr(el_val_t expr) { if (n == 0) { return EL_STR("el_list_empty()"); } - el_val_t items = EL_STR(""); + el_val_t items_parts = native_list_empty(); el_val_t i = 0; while (i < n) { el_val_t elem = native_list_get(elems, i); el_val_t elem_c = cg_expr(elem); - if (i > 0) { - items = el_str_concat(items, EL_STR(", ")); - } - items = el_str_concat(items, elem_c); + items_parts = native_list_append(items_parts, elem_c); i = (i + 1); } - return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_list_new("), native_int_to_str(n)), EL_STR(", ")), items), EL_STR(")")); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_list_new("), native_int_to_str(n)), EL_STR(", ")), str_join(items_parts, EL_STR(", "))), EL_STR(")")); } if (str_eq(kind, EL_STR("Map"))) { el_val_t pairs = el_get_field(expr, EL_STR("pairs")); @@ -2740,20 +2871,17 @@ el_val_t cg_expr(el_val_t expr) { if (n == 0) { return EL_STR("el_map_new(0)"); } - el_val_t items = EL_STR(""); + el_val_t items_parts = native_list_empty(); el_val_t i = 0; while (i < n) { el_val_t pair = native_list_get(pairs, i); el_val_t key = el_get_field(pair, EL_STR("key")); el_val_t val = el_get_field(pair, EL_STR("value")); el_val_t val_c = cg_expr(val); - if (i > 0) { - items = el_str_concat(items, EL_STR(", ")); - } - items = el_str_concat(el_str_concat(el_str_concat(items, c_str_lit(key)), EL_STR(", ")), val_c); + items_parts = native_list_append(items_parts, el_str_concat(el_str_concat(c_str_lit(key), EL_STR(", ")), val_c)); i = (i + 1); } - return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_map_new("), native_int_to_str(n)), EL_STR(", ")), items), EL_STR(")")); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_map_new("), native_int_to_str(n)), EL_STR(", ")), str_join(items_parts, EL_STR(", "))), EL_STR(")")); } if (str_eq(kind, EL_STR("Try"))) { el_val_t inner = el_get_field(expr, EL_STR("inner")); @@ -2789,7 +2917,8 @@ el_val_t cg_match(el_val_t expr) { el_val_t subj_var = el_str_concat(EL_STR("_match_subj_"), id); el_val_t result_var = el_str_concat(EL_STR("_match_result_"), id); el_val_t done_label = el_str_concat(EL_STR("_match_done_"), id); - el_val_t out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("({ el_val_t "), subj_var), EL_STR(" = ")), subj_c), EL_STR("; el_val_t ")), result_var), EL_STR(" = 0; ")); + el_val_t parts = native_list_empty(); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("({ el_val_t "), subj_var), EL_STR(" = ")), subj_c), EL_STR("; el_val_t ")), result_var), EL_STR(" = 0; "))); el_val_t n = native_list_len(arms); el_val_t i = 0; while (i < n) { @@ -2799,19 +2928,19 @@ el_val_t cg_match(el_val_t expr) { el_val_t pkind = el_get_field(pat, EL_STR("pattern")); el_val_t body_c = cg_expr(body); if (str_eq(pkind, EL_STR("Wildcard"))) { - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("{ ")), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{ "), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } "))); } else { if (str_eq(pkind, EL_STR("Binding"))) { el_val_t bname = el_get_field(pat, EL_STR("name")); - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("{ el_val_t ")), bname), EL_STR(" = ")), subj_var), EL_STR("; ")), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{ el_val_t "), bname), EL_STR(" = ")), subj_var), EL_STR("; ")), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } "))); } else { if (str_eq(pkind, EL_STR("LitInt"))) { el_val_t v = el_get_field(pat, EL_STR("value")); - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("if (")), subj_var), EL_STR(" == ")), v), EL_STR(") { ")), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("if ("), subj_var), EL_STR(" == ")), v), EL_STR(") { ")), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } "))); } else { if (str_eq(pkind, EL_STR("LitStr"))) { el_val_t v = el_get_field(pat, EL_STR("value")); - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("if (str_eq(")), subj_var), EL_STR(", EL_STR(")), c_str_lit(v)), EL_STR("))) { ")), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("if (str_eq("), subj_var), EL_STR(", EL_STR(")), c_str_lit(v)), EL_STR("))) { ")), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } "))); } else { if (str_eq(pkind, EL_STR("LitBool"))) { el_val_t v = el_get_field(pat, EL_STR("value")); @@ -2819,9 +2948,14 @@ el_val_t cg_match(el_val_t expr) { if (str_eq(v, EL_STR("true"))) { bv = EL_STR("1"); } - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("if (")), subj_var), EL_STR(" == ")), bv), EL_STR(") { ")), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("if ("), subj_var), EL_STR(" == ")), bv), EL_STR(") { ")), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } "))); } else { - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("{ ")), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } ")); + if (str_eq(pkind, EL_STR("Variant"))) { + el_val_t variant = el_get_field(pat, EL_STR("variant")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("if (str_eq("), subj_var), EL_STR(", EL_STR(")), c_str_lit(variant)), EL_STR("))) { ")), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } "))); + } else { + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{ "), result_var), EL_STR(" = (")), body_c), EL_STR("); goto ")), done_label), EL_STR("; } "))); + } } } } @@ -2829,8 +2963,8 @@ el_val_t cg_match(el_val_t expr) { } i = (i + 1); } - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, done_label), EL_STR(":; ")), result_var), EL_STR("; })")); - return out; + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(done_label, EL_STR(":; ")), result_var), EL_STR("; })"))); + return str_join(parts, EL_STR("")); return 0; } @@ -2848,7 +2982,7 @@ el_val_t next_if_id(void) { el_val_t cg_if_expr_arm(el_val_t stmts, el_val_t result_var) { el_val_t n = native_list_len(stmts); - el_val_t out = EL_STR(""); + el_val_t parts = native_list_empty(); el_val_t i = 0; while (i < n) { el_val_t s = native_list_get(stmts, i); @@ -2861,27 +2995,27 @@ el_val_t cg_if_expr_arm(el_val_t stmts, el_val_t result_var) { el_val_t name = el_get_field(s, EL_STR("name")); el_val_t val = el_get_field(s, EL_STR("value")); el_val_t val_c = cg_expr(val); - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("el_val_t ")), name), EL_STR(" = ")), val_c), EL_STR("; ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_val_t "), name), EL_STR(" = ")), val_c), EL_STR("; "))); } else { if (str_eq(sk, EL_STR("Return"))) { el_val_t val = el_get_field(s, EL_STR("value")); el_val_t val_c = cg_expr(val); - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, result_var), EL_STR(" = (")), val_c), EL_STR("); ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(result_var, EL_STR(" = (")), val_c), EL_STR("); "))); } else { if (str_eq(sk, EL_STR("Expr"))) { el_val_t val = el_get_field(s, EL_STR("value")); el_val_t val_c = cg_expr(val); if (is_last) { - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, result_var), EL_STR(" = (")), val_c), EL_STR("); ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(result_var, EL_STR(" = (")), val_c), EL_STR("); "))); } else { - out = el_str_concat(el_str_concat(el_str_concat(out, EL_STR("(void)(")), val_c), EL_STR("); ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(EL_STR("(void)("), val_c), EL_STR("); "))); } } else { if (str_eq(sk, EL_STR("Assign"))) { el_val_t aname = el_get_field(s, EL_STR("name")); el_val_t aval = el_get_field(s, EL_STR("value")); el_val_t aval_c = cg_expr(aval); - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, aname), EL_STR(" = ")), aval_c), EL_STR("; ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(aname, EL_STR(" = ")), aval_c), EL_STR("; "))); } else { } } @@ -2889,7 +3023,7 @@ el_val_t cg_if_expr_arm(el_val_t stmts, el_val_t result_var) { } i = (i + 1); } - return out; + return str_join(parts, EL_STR("")); return 0; } @@ -3063,6 +3197,9 @@ el_val_t cg_stmt(el_val_t stmt, el_val_t indent, el_val_t declared) { if (str_eq(kind, EL_STR("Import"))) { return declared; } + if (str_eq(kind, EL_STR("ExternFn"))) { + return declared; + } if (str_eq(kind, EL_STR("CgiBlock"))) { return declared; } @@ -3098,14 +3235,7 @@ el_val_t strip_outer_parens(el_val_t s) { i = (i + 1); } if (balanced) { - el_val_t inner = EL_STR(""); - el_val_t j = 1; - while (j < (n - 1)) { - el_val_t ch = native_list_get(chars, j); - inner = el_str_concat(inner, ch); - j = (j + 1); - } - return inner; + return str_slice(s, 1, (n - 1)); } } } @@ -3180,18 +3310,15 @@ el_val_t params_to_c(el_val_t params) { if (n == 0) { return EL_STR("void"); } - el_val_t out = EL_STR(""); + el_val_t parts = native_list_empty(); el_val_t i = 0; while (i < n) { el_val_t param = native_list_get(params, i); el_val_t decl = param_decl(param, i); - if (i > 0) { - out = el_str_concat(out, EL_STR(", ")); - } - out = el_str_concat(out, decl); + parts = native_list_append(parts, decl); i = (i + 1); } - return out; + return str_join(parts, EL_STR(", ")); return 0; } @@ -4077,7 +4204,7 @@ el_val_t emit_cap_violations(void) { if (colon > 0) { el_val_t kind = str_slice(entry, 0, colon); el_val_t fn_name = str_slice(entry, (colon + 1), str_len(entry)); - emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("#error \"capability violation: '"), kind), EL_STR("' programs may not call '")), fn_name), EL_STR("' (self-formation primitive — only 'cgi' programs may use it)\""))); + emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("#error \"capability violation: '"), kind), EL_STR("' programs may not call '")), fn_name), EL_STR("' (self-formation primitive - only 'cgi' programs may use it)\""))); } i = ((i + next_comma) + 1); } @@ -4474,6 +4601,27 @@ el_val_t builtin_arity(el_val_t name) { if (str_eq(name, EL_STR("exit_program"))) { return 1; } + if (str_eq(name, EL_STR("getpid_now"))) { + return 0; + } + if (str_eq(name, EL_STR("stdout_to_file"))) { + return 1; + } + if (str_eq(name, EL_STR("stdout_restore"))) { + return 0; + } + if (str_eq(name, EL_STR("exec_command"))) { + return 1; + } + if (str_eq(name, EL_STR("exec_capture"))) { + return 1; + } + if (str_eq(name, EL_STR("exec"))) { + return 1; + } + if (str_eq(name, EL_STR("exec_bg"))) { + return 1; + } if (str_eq(name, EL_STR("dharma_connect"))) { return 1; } @@ -4959,6 +5107,9 @@ el_val_t is_top_level_decl(el_val_t stmt) { if (str_eq(kind, EL_STR("CgiBlock"))) { return 1; } + if (str_eq(kind, EL_STR("ExternFn"))) { + return 1; + } return 0; return 0; } @@ -5184,7 +5335,7 @@ el_val_t codegen(el_val_t stmts, el_val_t source) { } if (cgi_count >= 1) { if (svc_count >= 1) { - emit_line(EL_STR("#error \"El: program declares both cgi and service blocks (mutually exclusive — pick one)\"")); + emit_line(EL_STR("#error \"El: program declares both cgi and service blocks (mutually exclusive - pick one)\"")); } } el_val_t kind = EL_STR("utility"); @@ -5201,6 +5352,33 @@ el_val_t codegen(el_val_t stmts, el_val_t source) { emit_line(EL_STR("#include ")); emit_line(EL_STR("#include ")); emit_line(EL_STR("#include \"el_runtime.h\"")); + el_val_t imp_n = native_list_len(stmts); + el_val_t imp_i = 0; + while (imp_i < imp_n) { + el_val_t imp_stmt = native_list_get(stmts, imp_i); + el_val_t imp_kind = el_get_field(imp_stmt, EL_STR("stmt")); + if (str_eq(imp_kind, EL_STR("Import"))) { + el_val_t imp_path = el_get_field(imp_stmt, EL_STR("path")); + el_val_t imp_path_len = str_len(imp_path); + el_val_t imp_last_slash = (-1); + el_val_t imp_j = 0; + while (imp_j < imp_path_len) { + el_val_t imp_c = str_slice(imp_path, imp_j, (imp_j + 1)); + if (str_eq(imp_c, EL_STR("/"))) { + imp_last_slash = imp_j; + } + imp_j = (imp_j + 1); + } + el_val_t imp_base = str_slice(imp_path, (imp_last_slash + 1), imp_path_len); + el_val_t imp_base_len = str_len(imp_base); + el_val_t imp_bname = imp_base; + if (str_ends_with(imp_base, EL_STR(".el"))) { + imp_bname = str_slice(imp_base, 0, (imp_base_len - 3)); + } + emit_line(el_str_concat(el_str_concat(EL_STR("#include \""), imp_bname), EL_STR(".elh\""))); + } + imp_i = (imp_i + 1); + } emit_blank(); el_val_t n = native_list_len(stmts); el_val_t i = 0; @@ -5215,6 +5393,12 @@ el_val_t codegen(el_val_t stmts, el_val_t source) { 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(");"))); } } + if (str_eq(kind, EL_STR("ExternFn"))) { + el_val_t fn_name = el_get_field(stmt, EL_STR("name")); + el_val_t params = el_get_field(stmt, EL_STR("params")); + el_val_t params_c = params_to_c(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(");"))); + } i = (i + 1); } emit_blank(); @@ -5242,6 +5426,33 @@ el_val_t codegen(el_val_t stmts, el_val_t source) { if (has_toplevel_lets) { emit_blank(); } + el_val_t has_el_main = 0; + el_val_t has_toplevel_stmts = 0; + i = 0; + while (i < n) { + el_val_t stmt = native_list_get(stmts, i); + el_val_t sk = el_get_field(stmt, EL_STR("stmt")); + if (str_eq(sk, EL_STR("FnDef"))) { + el_val_t fn_name_chk = el_get_field(stmt, EL_STR("name")); + if (str_eq(fn_name_chk, EL_STR("main"))) { + has_el_main = 1; + } + } + if (!is_fndef(stmt)) { + if (!is_top_level_decl(stmt)) { + if (!str_eq(sk, EL_STR("Let"))) { + has_toplevel_stmts = 1; + } + } + } + i = (i + 1); + } + el_val_t is_library = 0; + if (!has_el_main) { + if (!has_toplevel_stmts) { + is_library = 1; + } + } i = 0; while (i < n) { el_val_t stmt = native_list_get(stmts, i); @@ -5250,6 +5461,9 @@ el_val_t codegen(el_val_t stmts, el_val_t source) { } i = (i + 1); } + if (is_library) { + return EL_STR(""); + } emit_line(EL_STR("int main(int _argc, char** _argv) {")); emit_line(EL_STR(" el_runtime_init_args(_argc, _argv);")); if (cgi_count >= 1) { @@ -5331,26 +5545,26 @@ el_val_t codegen(el_val_t stmts, el_val_t source) { el_val_t js_escape(el_val_t s) { el_val_t chars = native_string_chars(s); el_val_t total = native_list_len(chars); - el_val_t out = EL_STR(""); + el_val_t parts = native_list_empty(); el_val_t i = 0; while (i < total) { el_val_t ch = native_list_get(chars, i); if (str_eq(ch, EL_STR("\""))) { - out = el_str_concat(out, EL_STR("\\\"")); + parts = native_list_append(parts, EL_STR("\\\"")); } else { if (str_eq(ch, EL_STR("\\"))) { - out = el_str_concat(out, EL_STR("\\\\")); + parts = native_list_append(parts, EL_STR("\\\\")); } else { if (str_eq(ch, EL_STR("\n"))) { - out = el_str_concat(out, EL_STR("\\n")); + parts = native_list_append(parts, EL_STR("\\n")); } else { if (str_eq(ch, EL_STR("\r"))) { - out = el_str_concat(out, EL_STR("\\r")); + parts = native_list_append(parts, EL_STR("\\r")); } else { if (str_eq(ch, EL_STR("\t"))) { - out = el_str_concat(out, EL_STR("\\t")); + parts = native_list_append(parts, EL_STR("\\t")); } else { - out = el_str_concat(out, ch); + parts = native_list_append(parts, ch); } } } @@ -5358,7 +5572,7 @@ el_val_t js_escape(el_val_t s) { } i = (i + 1); } - return out; + return str_join(parts, EL_STR("")); return 0; } @@ -5421,6 +5635,49 @@ el_val_t js_binop(el_val_t op) { return 0; } +el_val_t js_is_async_builtin(el_val_t name) { + if (str_eq(name, EL_STR("http_get"))) { + return 1; + } + if (str_eq(name, EL_STR("http_post"))) { + return 1; + } + if (str_eq(name, EL_STR("http_post_json"))) { + return 1; + } + if (str_eq(name, EL_STR("http_get_with_headers"))) { + return 1; + } + if (str_eq(name, EL_STR("http_post_with_headers"))) { + return 1; + } + return 0; + return 0; +} + +el_val_t js_register_async_fn(el_val_t name) { + el_val_t csv = state_get(EL_STR("__js_async_fns")); + if (str_eq(csv, EL_STR(""))) { + csv = 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("__js_async_fns"), el_str_concat(el_str_concat(csv, name), EL_STR(","))); + return 1; + return 0; +} + +el_val_t js_is_async_fn(el_val_t name) { + el_val_t csv = state_get(EL_STR("__js_async_fns")); + 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 js_is_int_name(el_val_t name) { el_val_t csv = state_get(EL_STR("__js_int_names")); if (str_eq(csv, EL_STR(""))) { @@ -5753,20 +6010,25 @@ el_val_t js_cg_expr(el_val_t expr) { el_val_t args = el_get_field(expr, EL_STR("args")); el_val_t arity = native_list_len(args); el_val_t func_kind = el_get_field(func, EL_STR("expr")); - el_val_t args_c = EL_STR(""); + el_val_t args_parts = native_list_empty(); el_val_t i = 0; while (i < arity) { el_val_t arg = native_list_get(args, i); el_val_t arg_c = js_cg_expr(arg); - if (i > 0) { - args_c = el_str_concat(args_c, EL_STR(", ")); - } - args_c = el_str_concat(args_c, arg_c); + args_parts = native_list_append(args_parts, arg_c); i = (i + 1); } + el_val_t args_c = str_join(args_parts, EL_STR(", ")); if (str_eq(func_kind, EL_STR("Ident"))) { el_val_t fn_name = el_get_field(func, EL_STR("name")); - return el_str_concat(el_str_concat(el_str_concat(fn_name, EL_STR("(")), args_c), EL_STR(")")); + el_val_t call_expr = el_str_concat(el_str_concat(el_str_concat(fn_name, EL_STR("(")), args_c), EL_STR(")")); + if (js_is_async_builtin(fn_name)) { + return el_str_concat(EL_STR("await "), call_expr); + } + if (js_is_async_fn(fn_name)) { + return el_str_concat(EL_STR("await "), call_expr); + } + return call_expr; } if (str_eq(func_kind, EL_STR("Field"))) { el_val_t obj = el_get_field(func, EL_STR("object")); @@ -5783,6 +6045,12 @@ el_val_t js_cg_expr(el_val_t expr) { if (str_eq(kind, EL_STR("Field"))) { 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_kind = el_get_field(obj, EL_STR("expr")); + if (str_eq(obj_kind, EL_STR("Try"))) { + el_val_t inner = el_get_field(obj, EL_STR("inner")); + el_val_t inner_c = js_cg_expr(inner); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), inner_c), EL_STR(")?.[")), js_str_lit(field)), EL_STR("] ?? null")); + } el_val_t obj_c = js_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(", ")), js_str_lit(field)), EL_STR(")")); } @@ -5792,6 +6060,12 @@ el_val_t js_cg_expr(el_val_t expr) { el_val_t obj_c = js_cg_expr(obj); el_val_t idx_c = js_cg_expr(idx); el_val_t idx_kind = el_get_field(idx, EL_STR("expr")); + el_val_t obj_kind = el_get_field(obj, EL_STR("expr")); + if (str_eq(obj_kind, EL_STR("Try"))) { + el_val_t inner = el_get_field(obj, EL_STR("inner")); + el_val_t inner_c = js_cg_expr(inner); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), inner_c), EL_STR(")?.[")), idx_c), EL_STR("] ?? null")); + } if (str_eq(idx_kind, EL_STR("Str"))) { return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_get_field("), obj_c), EL_STR(", ")), idx_c), EL_STR(")")); } @@ -5803,18 +6077,15 @@ el_val_t js_cg_expr(el_val_t expr) { if (n == 0) { return EL_STR("[]"); } - el_val_t items = EL_STR(""); + el_val_t items_parts = native_list_empty(); el_val_t i = 0; while (i < n) { el_val_t elem = native_list_get(elems, i); el_val_t elem_c = js_cg_expr(elem); - if (i > 0) { - items = el_str_concat(items, EL_STR(", ")); - } - items = el_str_concat(items, elem_c); + items_parts = native_list_append(items_parts, elem_c); i = (i + 1); } - return el_str_concat(el_str_concat(EL_STR("["), items), EL_STR("]")); + return el_str_concat(el_str_concat(EL_STR("["), str_join(items_parts, EL_STR(", "))), EL_STR("]")); } if (str_eq(kind, EL_STR("Map"))) { el_val_t pairs = el_get_field(expr, EL_STR("pairs")); @@ -5822,20 +6093,17 @@ el_val_t js_cg_expr(el_val_t expr) { if (n == 0) { return EL_STR("{}"); } - el_val_t items = EL_STR(""); + el_val_t items_parts = native_list_empty(); el_val_t i = 0; while (i < n) { el_val_t pair = native_list_get(pairs, i); el_val_t key = el_get_field(pair, EL_STR("key")); el_val_t val = el_get_field(pair, EL_STR("value")); el_val_t val_c = js_cg_expr(val); - if (i > 0) { - items = el_str_concat(items, EL_STR(", ")); - } - items = el_str_concat(el_str_concat(el_str_concat(items, js_str_lit(key)), EL_STR(": ")), val_c); + items_parts = native_list_append(items_parts, el_str_concat(el_str_concat(js_str_lit(key), EL_STR(": ")), val_c)); i = (i + 1); } - return el_str_concat(el_str_concat(EL_STR("{"), items), EL_STR("}")); + return el_str_concat(el_str_concat(EL_STR("{"), str_join(items_parts, EL_STR(", "))), EL_STR("}")); } if (str_eq(kind, EL_STR("Try"))) { el_val_t inner = el_get_field(expr, EL_STR("inner")); @@ -5871,7 +6139,8 @@ el_val_t js_cg_match(el_val_t expr) { el_val_t subj_c = js_cg_expr(subject); el_val_t id = js_next_match_id(); el_val_t subj_var = el_str_concat(EL_STR("_match_subj_"), id); - el_val_t out = el_str_concat(el_str_concat(EL_STR("(("), subj_var), EL_STR(") => { ")); + el_val_t parts = native_list_empty(); + parts = native_list_append(parts, el_str_concat(el_str_concat(EL_STR("(("), subj_var), EL_STR(") => { "))); el_val_t n = native_list_len(arms); el_val_t i = 0; while (i < n) { @@ -5881,19 +6150,19 @@ el_val_t js_cg_match(el_val_t expr) { el_val_t pkind = el_get_field(pat, EL_STR("pattern")); el_val_t body_c = js_cg_expr(body); if (str_eq(pkind, EL_STR("Wildcard"))) { - out = el_str_concat(el_str_concat(el_str_concat(out, EL_STR("return (")), body_c), EL_STR("); ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(EL_STR("return ("), body_c), EL_STR("); "))); } else { if (str_eq(pkind, EL_STR("Binding"))) { el_val_t bname = el_get_field(pat, EL_STR("name")); - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("{ const ")), bname), EL_STR(" = ")), subj_var), EL_STR("; return (")), body_c), EL_STR("); } ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{ const "), bname), EL_STR(" = ")), subj_var), EL_STR("; return (")), body_c), EL_STR("); } "))); } else { if (str_eq(pkind, EL_STR("LitInt"))) { el_val_t v = el_get_field(pat, EL_STR("value")); - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("if (")), subj_var), EL_STR(" === ")), v), EL_STR(") return (")), body_c), EL_STR("); ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("if ("), subj_var), EL_STR(" === ")), v), EL_STR(") return (")), body_c), EL_STR("); "))); } else { if (str_eq(pkind, EL_STR("LitStr"))) { el_val_t v = el_get_field(pat, EL_STR("value")); - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("if (str_eq(")), subj_var), EL_STR(", ")), js_str_lit(v)), EL_STR(")) return (")), body_c), EL_STR("); ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("if (str_eq("), subj_var), EL_STR(", ")), js_str_lit(v)), EL_STR(")) return (")), body_c), EL_STR("); "))); } else { if (str_eq(pkind, EL_STR("LitBool"))) { el_val_t v = el_get_field(pat, EL_STR("value")); @@ -5901,9 +6170,14 @@ el_val_t js_cg_match(el_val_t expr) { if (str_eq(v, EL_STR("true"))) { bv = EL_STR("true"); } - out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("if (")), subj_var), EL_STR(" === ")), bv), EL_STR(") return (")), body_c), EL_STR("); ")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("if ("), subj_var), EL_STR(" === ")), bv), EL_STR(") return (")), body_c), EL_STR("); "))); } else { - out = el_str_concat(el_str_concat(el_str_concat(out, EL_STR("return (")), body_c), EL_STR("); ")); + if (str_eq(pkind, EL_STR("Variant"))) { + el_val_t variant = el_get_field(pat, EL_STR("variant")); + parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("if (str_eq("), subj_var), EL_STR(", ")), js_str_lit(variant)), EL_STR(")) return (")), body_c), EL_STR("); "))); + } else { + parts = native_list_append(parts, el_str_concat(el_str_concat(EL_STR("return ("), body_c), EL_STR("); "))); + } } } } @@ -5911,8 +6185,8 @@ el_val_t js_cg_match(el_val_t expr) { } i = (i + 1); } - out = el_str_concat(el_str_concat(el_str_concat(out, EL_STR("return null; })(")), subj_c), EL_STR(")")); - return out; + parts = native_list_append(parts, el_str_concat(el_str_concat(EL_STR("return null; })("), subj_c), EL_STR(")"))); + return str_join(parts, EL_STR("")); return 0; } @@ -6016,12 +6290,12 @@ el_val_t js_cg_stmt(el_val_t stmt, el_val_t indent, el_val_t declared) { } if (str_eq(kind, EL_STR("CgiBlock"))) { el_val_t cname = el_get_field(stmt, EL_STR("name")); - js_emit_line(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("// cgi block '")), cname), EL_STR("' — no-op in JS target (server-side concept)"))); + js_emit_line(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("// cgi block '")), cname), EL_STR("' \xe2\x80\x94 no-op in JS target (server-side concept)"))); return declared; } if (str_eq(kind, EL_STR("ServiceBlock"))) { el_val_t sname = el_get_field(stmt, EL_STR("name")); - js_emit_line(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("// service block '")), sname), EL_STR("' — no-op in JS target"))); + js_emit_line(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("// service block '")), sname), EL_STR("' \xe2\x80\x94 no-op in JS target"))); return declared; } return declared; @@ -6056,14 +6330,7 @@ el_val_t js_strip_outer_parens(el_val_t s) { i = (i + 1); } if (balanced) { - el_val_t inner = EL_STR(""); - el_val_t j = 1; - while (j < (n - 1)) { - el_val_t ch = native_list_get(chars, j); - inner = el_str_concat(inner, ch); - j = (j + 1); - } - return inner; + return str_slice(s, 1, (n - 1)); } } } @@ -6124,18 +6391,15 @@ el_val_t js_params_str(el_val_t params) { if (n == 0) { return EL_STR(""); } - el_val_t out = EL_STR(""); + el_val_t parts = native_list_empty(); el_val_t i = 0; while (i < n) { el_val_t param = native_list_get(params, i); el_val_t name = el_get_field(param, EL_STR("name")); - if (i > 0) { - out = el_str_concat(out, EL_STR(", ")); - } - out = el_str_concat(out, name); + parts = native_list_append(parts, name); i = (i + 1); } - return out; + return str_join(parts, EL_STR(", ")); return 0; } @@ -6174,12 +6438,22 @@ el_val_t js_cg_fn(el_val_t stmt) { el_val_t params = el_get_field(stmt, EL_STR("params")); 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 decorator = el_get_field(stmt, EL_STR("decorator")); el_val_t params_str = js_params_str(params); js_build_int_names_for_params(params); - if (str_eq(fn_name, EL_STR("main"))) { - js_emit_line(el_str_concat(el_str_concat(EL_STR("function main("), params_str), EL_STR(") {"))); + if (str_eq(decorator, EL_STR("async"))) { + js_register_async_fn(fn_name); + if (str_eq(fn_name, EL_STR("main"))) { + js_emit_line(el_str_concat(el_str_concat(EL_STR("async function main("), params_str), EL_STR(") {"))); + } else { + js_emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("async function "), fn_name), EL_STR("(")), params_str), EL_STR(") {"))); + } } else { - js_emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("function "), fn_name), EL_STR("(")), params_str), EL_STR(") {"))); + if (str_eq(fn_name, EL_STR("main"))) { + js_emit_line(el_str_concat(el_str_concat(EL_STR("function main("), params_str), EL_STR(") {"))); + } else { + js_emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("function "), fn_name), EL_STR("(")), params_str), EL_STR(") {"))); + } } el_val_t decl = native_list_empty(); el_val_t np = native_list_len(params); @@ -6231,35 +6505,81 @@ el_val_t js_is_top_level_decl(el_val_t stmt) { } el_val_t codegen_js(el_val_t stmts, el_val_t source) { + return codegen_js_inner(stmts, source, 0, EL_STR("")); + return 0; +} + +el_val_t codegen_js_bundle(el_val_t stmts, el_val_t source, el_val_t runtime_content) { + return codegen_js_inner(stmts, source, 1, runtime_content); + return 0; +} + +el_val_t codegen_js_inner(el_val_t stmts, el_val_t source, el_val_t bundle_mode, el_val_t runtime_content) { state_set(EL_STR("__js_int_names"), EL_STR("")); state_set(EL_STR("__js_match_counter"), EL_STR("")); + state_set(EL_STR("__js_async_fns"), EL_STR("")); js_emit_line(EL_STR("// Generated by elc --target=js")); - js_emit_line(EL_STR("// Runtime: foundation/el/el-compiler/runtime/el_runtime.js")); - js_emit_line(EL_STR("import \"./el_runtime.js\";")); - js_emit_line(EL_STR("const {")); - js_emit_line(EL_STR(" println, print, el_str_concat, str_concat, str_eq, str_starts_with, str_ends_with,")); - js_emit_line(EL_STR(" str_len, int_to_str, str_to_int, str_slice, str_contains, str_replace,")); - js_emit_line(EL_STR(" str_to_upper, str_to_lower, str_trim, str_index_of, str_split, str_char_at,")); - js_emit_line(EL_STR(" str_char_code, str_lower, str_upper, el_abs, el_max, el_min,")); - js_emit_line(EL_STR(" el_list_new, el_list_len, el_list_get, el_list_append, el_list_empty, el_list_clone,")); - js_emit_line(EL_STR(" list_push, list_join, list_range,")); - js_emit_line(EL_STR(" el_map_new, el_get_field, el_map_get, el_map_set,")); - js_emit_line(EL_STR(" http_get, http_post, http_post_json,")); - js_emit_line(EL_STR(" fs_read, fs_write, fs_list,")); - js_emit_line(EL_STR(" json_parse, json_stringify, json_get, json_get_string, json_get_int,")); - js_emit_line(EL_STR(" time_now, time_now_utc, sleep_ms, bool_to_str, exit_program,")); - js_emit_line(EL_STR(" el_retain, el_release,")); - js_emit_line(EL_STR(" append, len, get, map_get, map_set,")); - js_emit_line(EL_STR(" native_list_get, native_list_len, native_list_append, native_list_empty,")); - js_emit_line(EL_STR(" native_list_clone, native_string_chars, native_int_to_str,")); - js_emit_line(EL_STR(" args, state_set, state_get, state_del, state_keys, env,")); - js_emit_line(EL_STR(" dharma_connect, dharma_send, dharma_emit, dharma_field, dharma_activate,")); - js_emit_line(EL_STR(" engram_node, engram_search, engram_activate,")); - js_emit_line(EL_STR(" llm_call, llm_call_system,")); - js_emit_line(EL_STR("} = globalThis.__el;")); - js_emit_blank(); + if (bundle_mode) { + js_emit_line(EL_STR("// Bundle mode: runtime inlined, no import statement needed.")); + js_emit_line(EL_STR("")); + js_emit_line(EL_STR(";(function() {")); + js_emit_line(EL_STR("\"use strict\";")); + js_emit_line(js_strip_es_exports(runtime_content)); + js_emit_line(EL_STR("")); + } else { + js_emit_line(EL_STR("// Runtime: foundation/el/el-compiler/runtime/el_runtime.js")); + js_emit_line(EL_STR("import \"./el_runtime.js\";")); + } + if (!bundle_mode) { + js_emit_line(EL_STR("const {")); + js_emit_line(EL_STR(" println, print, el_str_concat, str_concat, str_eq, str_starts_with, str_ends_with,")); + js_emit_line(EL_STR(" str_len, int_to_str, str_to_int, str_slice, str_contains, str_replace,")); + js_emit_line(EL_STR(" str_to_upper, str_to_lower, str_trim, str_index_of, str_split, str_char_at,")); + js_emit_line(EL_STR(" str_char_code, str_lower, str_upper, el_abs, el_max, el_min,")); + js_emit_line(EL_STR(" el_list_new, el_list_len, el_list_get, el_list_append, el_list_empty, el_list_clone,")); + js_emit_line(EL_STR(" list_push, list_join, list_range,")); + js_emit_line(EL_STR(" el_map_new, el_get_field, el_map_get, el_map_set,")); + js_emit_line(EL_STR(" http_get, http_post, http_post_json,")); + js_emit_line(EL_STR(" fs_read, fs_write, fs_list,")); + js_emit_line(EL_STR(" json_parse, json_stringify, json_get, json_get_string, json_get_int,")); + js_emit_line(EL_STR(" time_now, time_now_utc, sleep_ms, bool_to_str, exit_program,")); + js_emit_line(EL_STR(" el_retain, el_release,")); + js_emit_line(EL_STR(" append, len, get, map_get, map_set,")); + js_emit_line(EL_STR(" native_list_get, native_list_len, native_list_append, native_list_empty,")); + js_emit_line(EL_STR(" native_list_clone, native_string_chars, native_int_to_str,")); + js_emit_line(EL_STR(" args, state_set, state_get, state_del, state_keys, env,")); + js_emit_line(EL_STR(" dharma_connect, dharma_send, dharma_emit, dharma_field, dharma_activate,")); + js_emit_line(EL_STR(" engram_node, engram_search, engram_activate,")); + js_emit_line(EL_STR(" llm_call, llm_call_system,")); + js_emit_line(EL_STR(" dom_get_element, dom_get_value, dom_set_value, dom_get_text, dom_set_text,")); + js_emit_line(EL_STR(" dom_set_prop, dom_get_prop, dom_set_style, dom_add_class, dom_remove_class,")); + js_emit_line(EL_STR(" dom_show, dom_hide, dom_listen, dom_query, dom_query_all, dom_create,")); + js_emit_line(EL_STR(" dom_append, dom_remove, dom_is_null,")); + js_emit_line(EL_STR(" dom_set_attr, dom_get_attr, dom_remove_attr, dom_set_html, dom_get_html,")); + js_emit_line(EL_STR(" dom_get_parent, dom_contains_class, dom_get_checked, dom_set_checked,")); + js_emit_line(EL_STR(" set_timeout, set_interval, clear_interval,")); + js_emit_line(EL_STR(" local_storage_get, local_storage_set, local_storage_remove,")); + js_emit_line(EL_STR(" window_location, window_redirect, window_on_load,")); + js_emit_line(EL_STR(" console_log,")); + js_emit_line(EL_STR(" window_set, window_get, native_js, native_js_call,")); + js_emit_line(EL_STR("} = globalThis.__el;")); + js_emit_blank(); + } el_val_t n = native_list_len(stmts); el_val_t i = 0; + while (i < n) { + el_val_t stmt = native_list_get(stmts, i); + el_val_t sk = el_get_field(stmt, EL_STR("stmt")); + if (str_eq(sk, EL_STR("FnDef"))) { + el_val_t dec = el_get_field(stmt, EL_STR("decorator")); + if (str_eq(dec, EL_STR("async"))) { + el_val_t aname = el_get_field(stmt, EL_STR("name")); + js_register_async_fn(aname); + } + } + i = (i + 1); + } + i = 0; while (i < n) { el_val_t stmt = native_list_get(stmts, i); if (js_is_fndef(stmt)) { @@ -6297,10 +6617,37 @@ el_val_t codegen_js(el_val_t stmts, el_val_t source) { js_emit_blank(); js_emit_line(EL_STR("main();")); } + if (bundle_mode) { + js_emit_line(EL_STR("")); + js_emit_line(EL_STR("})();")); + } return EL_STR(""); return 0; } +el_val_t js_strip_es_exports(el_val_t content) { + el_val_t lines = str_split(content, EL_STR("\n")); + el_val_t n = native_list_len(lines); + el_val_t out = native_list_empty(); + el_val_t i = 0; + while (i < n) { + el_val_t line = native_list_get(lines, i); + el_val_t trimmed = str_trim(line); + if (str_starts_with(trimmed, EL_STR("export {"))) { + i = n; + } else { + if (str_starts_with(trimmed, EL_STR("export default"))) { + i = n; + } else { + out = native_list_append(out, line); + } + } + i = (i + 1); + } + return str_join(out, EL_STR("\n")); + return 0; +} + el_val_t compile(el_val_t source) { el_val_t tokens = lex(source); el_val_t stmts = parse(tokens); @@ -6317,6 +6664,19 @@ el_val_t compile_js(el_val_t source) { return 0; } +el_val_t compile_js_with_bundle(el_val_t source, el_val_t runtime_path) { + el_val_t tokens = lex(source); + el_val_t stmts = parse(tokens); + el_release(tokens); + el_val_t runtime_content = fs_read(runtime_path); + if (str_eq(runtime_content, EL_STR(""))) { + println(el_str_concat(EL_STR("el-compiler: warning: --bundle: could not read runtime at "), runtime_path)); + println(EL_STR("el-compiler: warning: bundle output will be incomplete")); + } + return codegen_js_bundle(stmts, source, runtime_content); + return 0; +} + el_val_t compile_dispatch(el_val_t tgt, el_val_t source) { if (str_eq(tgt, EL_STR("js"))) { return compile_js(source); @@ -6325,6 +6685,14 @@ el_val_t compile_dispatch(el_val_t tgt, el_val_t source) { return 0; } +el_val_t compile_dispatch_bundle(el_val_t tgt, el_val_t source, el_val_t runtime_path) { + if (str_eq(tgt, EL_STR("js"))) { + return compile_js_with_bundle(source, runtime_path); + } + return compile(source); + return 0; +} + el_val_t detect_target(el_val_t argv) { el_val_t n = native_list_len(argv); el_val_t i = 0; @@ -6355,6 +6723,201 @@ el_val_t strip_flags(el_val_t argv) { return 0; } +el_val_t detect_emit_header(el_val_t argv) { + el_val_t n = native_list_len(argv); + el_val_t i = 0; + while (i < n) { + el_val_t a = native_list_get(argv, i); + if (str_eq(a, EL_STR("--emit-header"))) { + return 1; + } + i = (i + 1); + } + return 0; + return 0; +} + +el_val_t detect_bundle(el_val_t argv) { + el_val_t n = native_list_len(argv); + el_val_t i = 0; + while (i < n) { + el_val_t a = native_list_get(argv, i); + if (str_eq(a, EL_STR("--bundle"))) { + return 1; + } + i = (i + 1); + } + return 0; + return 0; +} + +el_val_t detect_minify(el_val_t argv) { + el_val_t n = native_list_len(argv); + el_val_t i = 0; + while (i < n) { + el_val_t a = native_list_get(argv, i); + if (str_eq(a, EL_STR("--minify"))) { + return 1; + } + i = (i + 1); + } + return 0; + return 0; +} + +el_val_t detect_obfuscate(el_val_t argv) { + el_val_t n = native_list_len(argv); + el_val_t i = 0; + while (i < n) { + el_val_t a = native_list_get(argv, i); + if (str_eq(a, EL_STR("--obfuscate"))) { + return 1; + } + i = (i + 1); + } + return 0; + return 0; +} + +el_val_t make_temp_path(el_val_t suffix) { + el_val_t pid = getpid_now(); + el_val_t ts = time_now(); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("/tmp/elc-"), native_int_to_str(pid)), EL_STR("-")), native_int_to_str(ts)), EL_STR(".")), suffix); + return 0; +} + +el_val_t js_reserved_names(void) { + return EL_STR("neuronDemoToggle,neuronDemoSend,neuronDemoReset,signInWith,signInWithEmail,signUpWithEmail,sendMagicLink,signOut,resetPassword,sendResetEmail,updatePassword,showSignIn,showSignUp,hideReset,setSort,addFamilyMember,removeFamilyMember,copyForPlatform,entHeadcountChange,NEURON_CFG"); + return 0; +} + +el_val_t find_node_tool(el_val_t tool_name, el_val_t src_dir) { + el_val_t cand1 = el_str_concat(el_str_concat(src_dir, EL_STR("/node_modules/.bin/")), tool_name); + el_val_t check1 = str_trim(exec_capture(el_str_concat(el_str_concat(EL_STR("test -x "), cand1), EL_STR(" && echo yes 2>/dev/null")))); + if (str_eq(check1, EL_STR("yes"))) { + return cand1; + } + el_val_t parent_dir = dirname_of(src_dir); + el_val_t cand2 = el_str_concat(el_str_concat(parent_dir, EL_STR("/node_modules/.bin/")), tool_name); + el_val_t check2 = str_trim(exec_capture(el_str_concat(el_str_concat(EL_STR("test -x "), cand2), EL_STR(" && echo yes 2>/dev/null")))); + if (str_eq(check2, EL_STR("yes"))) { + return cand2; + } + el_val_t npx_path = str_trim(exec_capture(EL_STR("which npx 2>/dev/null"))); + if (!str_eq(npx_path, EL_STR(""))) { + return el_str_concat(EL_STR("npx --yes "), tool_name); + } + return EL_STR(""); + return 0; +} + +el_val_t apply_minify(el_val_t js_path, el_val_t out_path, el_val_t src_dir) { + el_val_t terser = find_node_tool(EL_STR("terser"), src_dir); + if (str_eq(terser, EL_STR(""))) { + println(EL_STR("el-compiler: error: terser not found. Run 'npm install terser' in your project directory.")); + return 0; + } + el_val_t names = js_reserved_names(); + el_val_t compress_opts = EL_STR("passes=2,drop_console=false,drop_debugger=true"); + el_val_t mangle_reserved = el_str_concat(el_str_concat(EL_STR("'reserved=["), names), EL_STR("]'")); + el_val_t cmd = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(terser, EL_STR(" ")), js_path), EL_STR(" --compress ")), compress_opts), EL_STR(" --mangle ")), mangle_reserved), EL_STR(" --output ")), out_path); + el_val_t ret = exec_command(cmd); + if (ret == 0) { + return 1; + } + println(el_str_concat(el_str_concat(EL_STR("el-compiler: error: terser failed (exit "), native_int_to_str(ret)), EL_STR(")"))); + return 0; + return 0; +} + +el_val_t apply_obfuscate(el_val_t js_path, el_val_t out_path, el_val_t src_dir) { + el_val_t obfuscator = find_node_tool(EL_STR("javascript-obfuscator"), src_dir); + if (str_eq(obfuscator, EL_STR(""))) { + println(EL_STR("el-compiler: error: javascript-obfuscator not found. Run 'npm install javascript-obfuscator' in your project directory.")); + return 0; + } + el_val_t names = js_reserved_names(); + el_val_t cmd = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(obfuscator, EL_STR(" ")), js_path), EL_STR(" --output ")), out_path), EL_STR(" --compact true --simplify true --string-array true --string-array-encoding base64 --string-array-threshold 0.75 --identifier-names-generator hexadecimal --rename-globals false --self-defending false --reserved-names ")), names); + el_val_t ret = exec_command(cmd); + if (ret == 0) { + return 1; + } + println(el_str_concat(el_str_concat(EL_STR("el-compiler: error: javascript-obfuscator failed (exit "), native_int_to_str(ret)), EL_STR(")"))); + return 0; + return 0; +} + +el_val_t resolve_runtime_path(el_val_t src_path) { + el_val_t src_dir = dirname_of(src_path); + el_val_t candidate = el_str_concat(src_dir, EL_STR("/el_runtime.js")); + el_val_t existing = fs_read(candidate); + if (!str_eq(existing, EL_STR(""))) { + return candidate; + } + return EL_STR(""); + return 0; +} + +el_val_t type_node_to_el(el_val_t t) { + el_val_t k = el_get_field(t, EL_STR("kind")); + if (str_eq(k, EL_STR("Simple"))) { + return el_get_field(t, EL_STR("name")); + } + if (str_eq(k, EL_STR("List"))) { + el_val_t inner = type_node_to_el(el_get_field(t, EL_STR("inner"))); + return el_str_concat(el_str_concat(EL_STR("["), inner), EL_STR("]")); + } + if (str_eq(k, EL_STR("Map"))) { + el_val_t kt = type_node_to_el(el_get_field(t, EL_STR("key"))); + el_val_t vt = type_node_to_el(el_get_field(t, EL_STR("val"))); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("Map<"), kt), EL_STR(", ")), vt), EL_STR(">")); + } + return EL_STR("Any"); + return 0; +} + +el_val_t emit_header(el_val_t stmts, el_val_t hdr_path) { + el_val_t n = native_list_len(stmts); + el_val_t i = 0; + el_val_t parts = native_list_empty(); + parts = native_list_append(parts, EL_STR("// auto-generated by elc --emit-header \xe2\x80\x94 do not edit\n")); + while (i < n) { + el_val_t stmt = native_list_get(stmts, i); + el_val_t kind = el_get_field(stmt, EL_STR("stmt")); + if (str_eq(kind, EL_STR("FnDef"))) { + el_val_t name = el_get_field(stmt, EL_STR("name")); + if (!str_eq(name, EL_STR("main"))) { + el_val_t params = el_get_field(stmt, EL_STR("params")); + el_val_t ret_type = el_get_field(stmt, EL_STR("ret_type")); + el_val_t np = native_list_len(params); + el_val_t pi = 0; + el_val_t param_parts = native_list_empty(); + 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(""))) { + ptype = EL_STR("Any"); + } + param_parts = native_list_append(param_parts, el_str_concat(el_str_concat(pname, EL_STR(": ")), ptype)); + pi = (pi + 1); + } + el_val_t params_str = str_join(param_parts, EL_STR(", ")); + el_val_t ret_str = ret_type; + if (str_eq(ret_str, EL_STR(""))) { + ret_str = EL_STR("Any"); + } + el_val_t sig = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("extern fn "), name), EL_STR("(")), params_str), EL_STR(") -> ")), ret_str); + parts = native_list_append(parts, el_str_concat(sig, EL_STR("\n"))); + } + } + i = (i + 1); + } + el_val_t content = str_join(parts, EL_STR("")); + el_val_t ok = fs_write(hdr_path, content); + return 0; +} + el_val_t dirname_of(el_val_t path) { el_val_t n = str_len(path); el_val_t i = (n - 1); @@ -6404,21 +6967,77 @@ el_val_t resolve_imports(el_val_t src_path) { el_val_t dir = dirname_of(src_path); el_val_t lines = str_split(source, EL_STR("\n")); el_val_t n = native_list_len(lines); - el_val_t prefix = EL_STR(""); - el_val_t body = EL_STR(""); + el_val_t prefix_chunks = native_list_empty(); + el_val_t body_chunks = native_list_empty(); el_val_t i = 0; while (i < n) { el_val_t line = native_list_get(lines, i); el_val_t trimmed = str_trim(line); el_val_t imp_path = parse_import_line(trimmed, dir); if (!str_eq(imp_path, EL_STR(""))) { - prefix = el_str_concat(prefix, resolve_imports(imp_path)); + el_val_t imp_elh_path = el_str_concat(str_slice(imp_path, 0, (str_len(imp_path) - 3)), EL_STR(".elh")); + el_val_t imp_elh = fs_read(imp_elh_path); + if (!str_eq(imp_elh, EL_STR(""))) { + el_val_t seen_imp_key = el_str_concat(EL_STR("__elc_imp__:"), imp_path); + state_set(seen_imp_key, EL_STR("1")); + prefix_chunks = native_list_append(prefix_chunks, imp_elh); + } else { + el_val_t imp_body = resolve_imports(imp_path); + prefix_chunks = native_list_append(prefix_chunks, imp_body); + } } else { - body = el_str_concat(el_str_concat(body, line), EL_STR("\n")); + body_chunks = native_list_append(body_chunks, el_str_concat(line, EL_STR("\n"))); } i = (i + 1); } - return el_str_concat(prefix, body); + return el_str_concat(str_join(prefix_chunks, EL_STR("")), str_join(body_chunks, EL_STR(""))); + return 0; +} + +el_val_t run_with_postprocess(el_val_t tgt, el_val_t source, el_val_t src_path, el_val_t do_bundle, el_val_t do_obfuscate, el_val_t argc, el_val_t positional) { + el_val_t src_dir = dirname_of(src_path); + el_val_t tmp_gen = make_temp_path(EL_STR("js")); + el_val_t tmp_min = make_temp_path(EL_STR("min.js")); + stdout_to_file(tmp_gen); + if (do_bundle) { + el_val_t runtime_path = resolve_runtime_path(src_path); + compile_dispatch_bundle(tgt, source, runtime_path); + } else { + compile_dispatch(tgt, source); + } + stdout_restore(); + el_val_t ok_min = apply_minify(tmp_gen, tmp_min, src_dir); + if (!ok_min) { + exec_command(el_str_concat(el_str_concat(el_str_concat(EL_STR("rm -f "), tmp_gen), EL_STR(" ")), tmp_min)); + exit(1); + } + state_set(EL_STR("__elc_final_js"), tmp_min); + if (do_obfuscate) { + el_val_t tmp_obf = make_temp_path(EL_STR("obf.js")); + el_val_t ok_obf = apply_obfuscate(tmp_min, tmp_obf, src_dir); + if (!ok_obf) { + exec_command(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("rm -f "), tmp_gen), EL_STR(" ")), tmp_min), EL_STR(" ")), tmp_obf)); + exit(1); + } + state_set(EL_STR("__elc_final_js"), tmp_obf); + } + el_val_t final_path = state_get(EL_STR("__elc_final_js")); + el_val_t final_js = fs_read(final_path); + exec_command(el_str_concat(el_str_concat(el_str_concat(EL_STR("rm -f "), tmp_gen), EL_STR(" ")), tmp_min)); + if (do_obfuscate) { + exec_command(el_str_concat(EL_STR("rm -f "), final_path)); + } + if (argc >= 2) { + el_val_t out_path = native_list_get(positional, 1); + el_val_t ok = fs_write(out_path, final_js); + if (ok) { + return 0; + } else { + println(EL_STR("el-compiler: failed to write output")); + exit(1); + } + } + print(final_js); return 0; } @@ -6426,15 +7045,47 @@ int main(int _argc, char** _argv) { el_runtime_init_args(_argc, _argv); el_val_t argv = args(); el_val_t tgt = detect_target(argv); + el_val_t do_emit_header = detect_emit_header(argv); + el_val_t do_bundle = detect_bundle(argv); + el_val_t do_minify = detect_minify(argv); + el_val_t do_obfuscate = detect_obfuscate(argv); + if (do_obfuscate) { + do_minify = 1; + } el_val_t positional = strip_flags(argv); el_val_t argc = native_list_len(positional); if (argc < 1) { - println(EL_STR("el-compiler: usage: elc [--target=c|js] []")); + println(EL_STR("el-compiler: usage: elc [--target=c|js] [--bundle] [--minify] [--obfuscate] [--emit-header] []")); exit(1); } + if (do_minify) { + if (!str_eq(tgt, EL_STR("js"))) { + println(EL_STR("el-compiler: error: --minify and --obfuscate require --target=js")); + exit(1); + } + } el_val_t src_path = native_list_get(positional, 0); + if (do_emit_header) { + el_val_t raw_source = fs_read(src_path); + el_val_t hdr_tokens = lex(raw_source); + el_val_t hdr_stmts = parse(hdr_tokens); + el_release(hdr_tokens); + el_val_t hdr_path = el_str_concat(str_slice(src_path, 0, (str_len(src_path) - 3)), EL_STR(".elh")); + emit_header(hdr_stmts, hdr_path); + el_release(hdr_stmts); + } el_val_t source = resolve_imports(src_path); - el_val_t out = compile_dispatch(tgt, source); + if (do_minify) { + run_with_postprocess(tgt, source, src_path, do_bundle, do_obfuscate, argc, positional); + exit(0); + } + el_val_t out = EL_STR(""); + if (do_bundle) { + el_val_t runtime_path = resolve_runtime_path(src_path); + out = compile_dispatch_bundle(tgt, source, runtime_path); + } else { + out = compile_dispatch(tgt, source); + } if (argc >= 2) { el_val_t out_path = native_list_get(positional, 1); el_val_t ok = fs_write(out_path, out); diff --git a/el-compiler/runtime/el_runtime.c b/el-compiler/runtime/el_runtime.c index 0ba1432..d599395 100644 --- a/el-compiler/runtime/el_runtime.c +++ b/el-compiler/runtime/el_runtime.c @@ -155,6 +155,36 @@ el_val_t readline(void) { return el_wrap_str(el_strdup(buf)); } +/* ── stdout redirect helpers ─────────────────────────────────────────────── * + * Used by elc post-processing (--minify, --obfuscate): capture codegen * + * output into a temp file, then pass it to the external tool. */ + +static int _stdout_saved_fd = -1; + +/* stdout_to_file(path) — redirect stdout to . Returns 1 on success. */ +el_val_t stdout_to_file(el_val_t pathv) { + const char* path = EL_CSTR(pathv); + if (!path || !*path) return (el_val_t)(int64_t)0; + fflush(stdout); + _stdout_saved_fd = dup(STDOUT_FILENO); + if (_stdout_saved_fd < 0) return (el_val_t)(int64_t)0; + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { close(_stdout_saved_fd); _stdout_saved_fd = -1; return (el_val_t)(int64_t)0; } + dup2(fd, STDOUT_FILENO); + close(fd); + return (el_val_t)(int64_t)1; +} + +/* stdout_restore() — restore stdout from the saved fd. Returns 1 on success. */ +el_val_t stdout_restore(void) { + if (_stdout_saved_fd < 0) return (el_val_t)(int64_t)0; + fflush(stdout); + dup2(_stdout_saved_fd, STDOUT_FILENO); + close(_stdout_saved_fd); + _stdout_saved_fd = -1; + return (el_val_t)(int64_t)1; +} + /* ── String builtins ─────────────────────────────────────────────────────── */ el_val_t el_str_concat(el_val_t av, el_val_t bv) { diff --git a/el-compiler/runtime/el_runtime.h b/el-compiler/runtime/el_runtime.h index 72bbf4b..a9b9b44 100644 --- a/el-compiler/runtime/el_runtime.h +++ b/el-compiler/runtime/el_runtime.h @@ -79,6 +79,8 @@ extern "C" { void println(el_val_t s); void print(el_val_t s); el_val_t readline(void); +el_val_t stdout_to_file(el_val_t path); /* redirect println to a file */ +el_val_t stdout_restore(void); /* restore stdout after capture */ /* ── String builtins ─────────────────────────────────────────────────────── */ diff --git a/el-compiler/src/codegen.el b/el-compiler/src/codegen.el index 51ddc34..b7d5055 100644 --- a/el-compiler/src/codegen.el +++ b/el-compiler/src/codegen.el @@ -2056,6 +2056,11 @@ fn builtin_arity(name: String) -> Int { if str_eq(name, "bool_to_str") { return 1 } // Process if str_eq(name, "exit_program") { return 1 } + // Process info + if str_eq(name, "getpid_now") { return 0 } + // stdout redirect (used by elc post-processing) + if str_eq(name, "stdout_to_file") { return 1 } + if str_eq(name, "stdout_restore") { return 0 } // Subprocess execution if str_eq(name, "exec_command") { return 1 } if str_eq(name, "exec_capture") { return 1 } diff --git a/el-compiler/src/compiler.el b/el-compiler/src/compiler.el index 48eaf1b..e74b38a 100644 --- a/el-compiler/src/compiler.el +++ b/el-compiler/src/compiler.el @@ -123,6 +123,100 @@ fn detect_bundle(argv: [String]) -> Bool { return false } +// Detect --minify flag in argv. +fn detect_minify(argv: [String]) -> Bool { + let n: Int = native_list_len(argv) + let i = 0 + while i < n { + let a: String = native_list_get(argv, i) + if str_eq(a, "--minify") { return true } + let i = i + 1 + } + return false +} + +// Detect --obfuscate flag in argv. +fn detect_obfuscate(argv: [String]) -> Bool { + let n: Int = native_list_len(argv) + let i = 0 + while i < n { + let a: String = native_list_get(argv, i) + if str_eq(a, "--obfuscate") { return true } + let i = i + 1 + } + return false +} + +// Build a unique temp file path: /tmp/elc--. +fn make_temp_path(suffix: String) -> String { + let pid: Int = getpid_now() + let ts: Int = time_now() + "/tmp/elc-" + native_int_to_str(pid) + "-" + native_int_to_str(ts) + "." + suffix +} + +// Reserved globals that terser and javascript-obfuscator must not mangle. +// These are referenced from HTML onclick= attributes and other direct window usage. +fn js_reserved_names() -> String { + "neuronDemoToggle,neuronDemoSend,neuronDemoReset,signInWith,signInWithEmail,signUpWithEmail,sendMagicLink,signOut,resetPassword,sendResetEmail,updatePassword,showSignIn,showSignUp,hideReset,setSort,addFamilyMember,removeFamilyMember,copyForPlatform,entHeadcountChange,NEURON_CFG" +} + +// Find a CLI tool by checking node_modules paths first, then falling back to npx. +// src_dir is the directory of the source file being compiled. +// Returns the command string to invoke the tool, or "" if not found. +fn find_node_tool(tool_name: String, src_dir: String) -> String { + // 1. Check ./node_modules/.bin/ relative to source file + let cand1: String = src_dir + "/node_modules/.bin/" + tool_name + let check1: String = str_trim(exec_capture("test -x " + cand1 + " && echo yes 2>/dev/null")) + if str_eq(check1, "yes") { return cand1 } + // 2. Check ../node_modules/.bin/ (monorepo layout) + let parent_dir: String = dirname_of(src_dir) + let cand2: String = parent_dir + "/node_modules/.bin/" + tool_name + let check2: String = str_trim(exec_capture("test -x " + cand2 + " && echo yes 2>/dev/null")) + if str_eq(check2, "yes") { return cand2 } + // 3. Fall back to npx if it is on PATH. npx will use the globally cached + // package or download on first use. Use --no to avoid auto-install if + // the package is not already cached; if that fails, try with --yes. + let npx_path: String = str_trim(exec_capture("which npx 2>/dev/null")) + if !str_eq(npx_path, "") { return "npx --yes " + tool_name } + return "" +} + +// apply_minify — run terser on js_path, write result to out_path. +// Returns true on success, false on failure. +fn apply_minify(js_path: String, out_path: String, src_dir: String) -> Bool { + let terser: String = find_node_tool("terser", src_dir) + if str_eq(terser, "") { + println("el-compiler: error: terser not found. Run 'npm install terser' in your project directory.") + return false + } + let names: String = js_reserved_names() + // Single-quote the mangle reserved list so the shell does not glob-expand + // the bracket expression. The compress options are safe without quoting. + let compress_opts: String = "passes=2,drop_console=false,drop_debugger=true" + let mangle_reserved: String = "'reserved=[" + names + "]'" + let cmd: String = terser + " " + js_path + " --compress " + compress_opts + " --mangle " + mangle_reserved + " --output " + out_path + let ret: Int = exec_command(cmd) + if ret == 0 { return true } + println("el-compiler: error: terser failed (exit " + native_int_to_str(ret) + ")") + return false +} + +// apply_obfuscate — run javascript-obfuscator on js_path, write result to out_path. +// Returns true on success, false on failure. +fn apply_obfuscate(js_path: String, out_path: String, src_dir: String) -> Bool { + let obfuscator: String = find_node_tool("javascript-obfuscator", src_dir) + if str_eq(obfuscator, "") { + println("el-compiler: error: javascript-obfuscator not found. Run 'npm install javascript-obfuscator' in your project directory.") + return false + } + let names: String = js_reserved_names() + let cmd: String = obfuscator + " " + js_path + " --output " + out_path + " --compact true --simplify true --string-array true --string-array-encoding base64 --string-array-threshold 0.75 --identifier-names-generator hexadecimal --rename-globals false --self-defending false --reserved-names " + names + let ret: Int = exec_command(cmd) + if ret == 0 { return true } + println("el-compiler: error: javascript-obfuscator failed (exit " + native_int_to_str(ret) + ")") + return false +} + // Resolve the runtime path for --bundle mode. // Looks for el_runtime.js next to the source file first; // if not found there, looks next to the elc binary itself. @@ -295,14 +389,83 @@ fn resolve_imports(src_path: String) -> String { return str_join(prefix_chunks, "") + str_join(body_chunks, "") } +// run_with_postprocess — codegen + minify + optional obfuscate pipeline. +// +// Called from main() when --minify or --obfuscate is active. Redirects stdout +// to a temp file during codegen so the output can be passed through the +// external tools (terser, javascript-obfuscator) before final emission. +// +// Pipeline: codegen -> terser -> (javascript-obfuscator) -> stdout or file +fn run_with_postprocess(tgt: String, source: String, src_path: String, do_bundle: Bool, do_obfuscate: Bool, argc: Int, positional: [String]) -> Void { + let src_dir: String = dirname_of(src_path) + let tmp_gen: String = make_temp_path("js") + let tmp_min: String = make_temp_path("min.js") + + // Redirect stdout to tmp_gen so codegen println output is captured. + stdout_to_file(tmp_gen) + if do_bundle { + let runtime_path: String = resolve_runtime_path(src_path) + compile_dispatch_bundle(tgt, source, runtime_path) + } else { + compile_dispatch(tgt, source) + } + stdout_restore() + + // Run terser: tmp_gen -> tmp_min + let ok_min: Bool = apply_minify(tmp_gen, tmp_min, src_dir) + if !ok_min { + exec_command("rm -f " + tmp_gen + " " + tmp_min) + exit(1) + } + + // Determine final result path (either tmp_min or post-obfuscation file). + // Use state to pass the final path out of the optional obfuscation branch. + state_set("__elc_final_js", tmp_min) + + if do_obfuscate { + let tmp_obf: String = make_temp_path("obf.js") + let ok_obf: Bool = apply_obfuscate(tmp_min, tmp_obf, src_dir) + if !ok_obf { + exec_command("rm -f " + tmp_gen + " " + tmp_min + " " + tmp_obf) + exit(1) + } + state_set("__elc_final_js", tmp_obf) + } + + let final_path: String = state_get("__elc_final_js") + let final_js: String = fs_read(final_path) + + // Clean up all temp files. + exec_command("rm -f " + tmp_gen + " " + tmp_min) + if do_obfuscate { + exec_command("rm -f " + final_path) + } + + if argc >= 2 { + let out_path: String = native_list_get(positional, 1) + let ok: Bool = fs_write(out_path, final_js) + if ok { + return + } else { + println("el-compiler: failed to write output") + exit(1) + } + } + // No output file: print final JS to stdout. + print(final_js) +} + // main — CLI entry point. // -// elc # emit C to stdout -// elc --target=js # emit JS (module) to stdout -// elc --target=js --bundle # emit self-contained JS (IIFE) to stdout -// elc --target=c # write C to file -// elc --target=js # write JS to file -// elc --target=js --bundle # write bundled JS to file +// elc # emit C to stdout +// elc --target=js # emit JS (module) to stdout +// elc --target=js --bundle # emit self-contained JS (IIFE) to stdout +// elc --target=js --bundle --minify # emit minified IIFE to stdout +// elc --target=js --bundle --obfuscate # emit minified+obfuscated IIFE to stdout +// elc --target=c # write C to file +// elc --target=js # write JS to file +// elc --target=js --bundle # write bundled JS to file +// elc --target=js --bundle --minify # write minified JS to file fn main() -> Void { let argv: [String] = args() // Use `tgt` not `target`: `target` is a reserved keyword in the lexer @@ -311,12 +474,27 @@ fn main() -> Void { let tgt: String = detect_target(argv) let do_emit_header: Bool = detect_emit_header(argv) let do_bundle: Bool = detect_bundle(argv) + let do_minify: Bool = detect_minify(argv) + let do_obfuscate: Bool = detect_obfuscate(argv) + // --obfuscate implies --minify: obfuscating unminified code is pointless. + if do_obfuscate { + let do_minify = true + } let positional: [String] = strip_flags(argv) let argc: Int = native_list_len(positional) if argc < 1 { - println("el-compiler: usage: elc [--target=c|js] [--bundle] [--emit-header] []") + println("el-compiler: usage: elc [--target=c|js] [--bundle] [--minify] [--obfuscate] [--emit-header] []") exit(1) } + + // --minify and --obfuscate require --target=js + if do_minify { + if !str_eq(tgt, "js") { + println("el-compiler: error: --minify and --obfuscate require --target=js") + exit(1) + } + } + let src_path: String = native_list_get(positional, 0) // When --emit-header is requested, parse the source file directly @@ -332,6 +510,17 @@ fn main() -> Void { } let source: String = resolve_imports(src_path) + + // When post-processing (--minify or --obfuscate) is requested, redirect + // stdout to a temp file so codegen output can be captured and piped through + // the external tools. After codegen, restore stdout before emitting the + // final result. + if do_minify { + run_with_postprocess(tgt, source, src_path, do_bundle, do_obfuscate, argc, positional) + exit(0) + } + + // Standard path (no post-processing). let out: String = "" if do_bundle { let runtime_path: String = resolve_runtime_path(src_path) diff --git a/spec/codegen-js.md b/spec/codegen-js.md index b60e750..4bf921d 100644 --- a/spec/codegen-js.md +++ b/spec/codegen-js.md @@ -240,6 +240,93 @@ The argv parser scans for a `--target=` token; remaining positional args a --- +## 8a. Production output — `--minify` and `--obfuscate` + +Two post-processing flags produce production-ready browser JS in a single compiler invocation, replacing any external post-processing scripts. + +### Usage + +``` +elc --target=js --bundle --minify source.el > output.min.js +elc --target=js --bundle --obfuscate source.el > output.obf.js +elc --target=js --bundle --minify --obfuscate source.el > output.final.js +``` + +Both flags require `--target=js`. Passing either without `--target=js` prints an error and exits with code 1. + +`--obfuscate` implies `--minify` — obfuscating unminified code produces no benefit and only increases output size. + +### Pipeline order + +``` +generate JS -> (if --bundle, wrap in IIFE) -> (if --minify, run terser) -> (if --obfuscate, run javascript-obfuscator) -> output +``` + +### Tool discovery + +The compiler looks for each tool in this order: + +1. `/node_modules/.bin/` — local install next to source file +2. `/../node_modules/.bin/` — one level up (monorepo layout) +3. `npx --yes ` — fall back to npx (uses globally cached package or downloads on first use) + +If no path resolves and npx is not on `PATH`, the compiler prints a clear error and exits non-zero: + +``` +el-compiler: error: terser not found. Run 'npm install terser' in your project directory. +el-compiler: error: javascript-obfuscator not found. Run 'npm install javascript-obfuscator' in your project directory. +``` + +### Minification (terser) + +Command issued internally: + +``` +terser --compress passes=2,drop_console=false,drop_debugger=true \ + --mangle 'reserved=[]' --output +``` + +### Obfuscation (javascript-obfuscator) + +Command issued internally (runs after minification): + +``` +javascript-obfuscator --output + --compact true + --simplify true + --string-array true + --string-array-encoding base64 + --string-array-threshold 0.75 + --identifier-names-generator hexadecimal + --rename-globals false + --self-defending false + --reserved-names +``` + +### Reserved names + +These identifiers are protected from renaming by both tools. They are referenced directly from HTML `onclick=` attributes and other global-scope callsites: + +``` +neuronDemoToggle, neuronDemoSend, neuronDemoReset, +signInWith, signInWithEmail, signUpWithEmail, sendMagicLink, +signOut, resetPassword, sendResetEmail, updatePassword, +showSignIn, showSignUp, hideReset, +setSort, addFamilyMember, removeFamilyMember, copyForPlatform, entHeadcountChange, +NEURON_CFG +``` + +### Temp files + +The compiler uses `/tmp/elc--.js` naming for temp files. All temp files are cleaned up on both success and failure paths. + +### Implementation notes + +- The compiler adds `stdout_to_file(path)` / `stdout_restore()` builtins to the C runtime (`el_runtime.c`) to capture codegen output (which is streamed via `println`) into a temp file before passing it to the external tools. +- `--minify` and `--obfuscate` error messages are printed after stdout is restored, so they always reach the terminal regardless of output redirection. + +--- + ## 9. The path to compiling el-ui/runtime through this backend This is the real-world test. `el-ui/runtime/src/` is currently 5 hand-written `.js` files. The path to authoring them in El: