#include <stdint.h>
#include <stdlib.h>
#include "el_runtime.h"

el_val_t lex_is_digit(el_val_t ch);
el_val_t lex_is_alpha(el_val_t ch);
el_val_t is_alnum_or_underscore(el_val_t ch);
el_val_t lex_is_whitespace(el_val_t ch);
el_val_t make_tok(el_val_t kind, el_val_t value);
el_val_t keyword_kind(el_val_t word);
el_val_t scan_digits(el_val_t chars, el_val_t start, el_val_t total);
el_val_t scan_ident(el_val_t chars, el_val_t start, el_val_t total);
el_val_t substr_at(el_val_t chars, el_val_t start, el_val_t total, el_val_t needle);
el_val_t str_has(el_val_t s, el_val_t needle);
el_val_t looks_like_code(el_val_t s);
el_val_t strip_code_comments(el_val_t s);
el_val_t scan_string(el_val_t chars, el_val_t start, el_val_t total);
el_val_t lex(el_val_t source);
el_val_t tok_at(el_val_t tokens, el_val_t pos);
el_val_t tok_kind(el_val_t tokens, el_val_t pos);
el_val_t tok_value(el_val_t tokens, el_val_t pos);
el_val_t expect(el_val_t tokens, el_val_t pos, el_val_t kind);
el_val_t make_result(el_val_t node, el_val_t pos);
el_val_t skip_type(el_val_t tokens, el_val_t pos);
el_val_t parse_params(el_val_t tokens, el_val_t pos);
el_val_t parse_primary(el_val_t tokens, el_val_t pos);
el_val_t parse_if(el_val_t tokens, el_val_t pos);
el_val_t parse_match(el_val_t tokens, el_val_t pos);
el_val_t parse_pattern(el_val_t tokens, el_val_t pos);
el_val_t parse_for_expr(el_val_t tokens, el_val_t pos);
el_val_t parse_block(el_val_t tokens, el_val_t pos);
el_val_t is_duration_unit(el_val_t name);
el_val_t parse_postfix(el_val_t tokens, el_val_t pos);
el_val_t op_precedence(el_val_t kind);
el_val_t is_binop(el_val_t kind);
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);
el_val_t emit_line(el_val_t line);
el_val_t emit_blank(void);
el_val_t binop_to_c(el_val_t op);
el_val_t duration_unit_nanos(el_val_t unit);
el_val_t cg_expr(el_val_t expr);
el_val_t next_match_id(void);
el_val_t cg_match(el_val_t expr);
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 cg_if_expr(el_val_t expr);
el_val_t list_contains(el_val_t lst, el_val_t s);
el_val_t cg_stmt(el_val_t stmt, el_val_t indent, el_val_t declared);
el_val_t strip_outer_parens(el_val_t s);
el_val_t cg_if_stmt(el_val_t expr, el_val_t indent, el_val_t declared);
el_val_t cg_for_body(el_val_t item, el_val_t list_expr, el_val_t body, el_val_t indent, el_val_t declared);
el_val_t cg_for_stmt(el_val_t expr, el_val_t indent, el_val_t declared);
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 is_instant_name(el_val_t name);
el_val_t is_duration_name(el_val_t name);
el_val_t is_int_call(el_val_t call_expr);
el_val_t is_instant_call(el_val_t call_expr);
el_val_t is_duration_call(el_val_t call_expr);
el_val_t is_calendar_name(el_val_t name);
el_val_t is_caltime_name(el_val_t name);
el_val_t is_rhythm_name(el_val_t name);
el_val_t is_localdate_name(el_val_t name);
el_val_t is_localtime_name(el_val_t name);
el_val_t is_localdt_name(el_val_t name);
el_val_t is_zone_name(el_val_t name);
el_val_t is_calendar_call(el_val_t call_expr);
el_val_t is_caltime_call(el_val_t call_expr);
el_val_t is_rhythm_call(el_val_t call_expr);
el_val_t is_localdate_call(el_val_t call_expr);
el_val_t is_localtime_call(el_val_t call_expr);
el_val_t is_localdt_call(el_val_t call_expr);
el_val_t is_zone_call(el_val_t call_expr);
el_val_t is_calendar_expr(el_val_t expr);
el_val_t is_caltime_expr(el_val_t expr);
el_val_t is_rhythm_expr(el_val_t expr);
el_val_t is_localdate_expr(el_val_t expr);
el_val_t is_localtime_expr(el_val_t expr);
el_val_t is_localdt_expr(el_val_t expr);
el_val_t is_zone_expr(el_val_t expr);
el_val_t is_instant_expr(el_val_t expr);
el_val_t is_duration_expr(el_val_t expr);
el_val_t time_record_violation(el_val_t kind, el_val_t detail);
el_val_t is_int_expr(el_val_t expr);
el_val_t cap_record_violation(el_val_t kind, el_val_t fn_name);
el_val_t is_self_formation_call(el_val_t fn_name);
el_val_t is_dharma_call(el_val_t fn_name);
el_val_t is_llm_call(el_val_t fn_name);
el_val_t cap_check_call(el_val_t fn_name);
el_val_t emit_cap_violations(void);
el_val_t emit_time_violations(void);
el_val_t builtin_arity(el_val_t name);
el_val_t arity_record_violation(el_val_t fn_name, el_val_t expected, el_val_t actual);
el_val_t arity_check_call(el_val_t fn_name, el_val_t actual);
el_val_t emit_arity_violations(void);
el_val_t add_int_name(el_val_t name);
el_val_t add_instant_name(el_val_t name);
el_val_t add_duration_name(el_val_t name);
el_val_t add_calendar_name(el_val_t name);
el_val_t add_caltime_name(el_val_t name);
el_val_t add_rhythm_name(el_val_t name);
el_val_t add_localdate_name(el_val_t name);
el_val_t add_localtime_name(el_val_t name);
el_val_t add_localdt_name(el_val_t name);
el_val_t add_zone_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);
el_val_t cgi_arg(el_val_t value, el_val_t has_value);
el_val_t vbd_is_restricted_name(el_val_t name);
el_val_t vbd_expr_has_restricted_call(el_val_t expr);
el_val_t vbd_has_restricted_call(el_val_t stmts);
el_val_t codegen(el_val_t stmts, el_val_t source);
el_val_t js_escape(el_val_t s);
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_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);
el_val_t js_is_int_call(el_val_t call_expr);
el_val_t js_cg_expr(el_val_t expr);
el_val_t js_next_match_id(void);
el_val_t js_cg_match(el_val_t expr);
el_val_t js_list_contains(el_val_t lst, el_val_t s);
el_val_t js_cg_stmt(el_val_t stmt, el_val_t indent, el_val_t declared);
el_val_t js_strip_outer_parens(el_val_t s);
el_val_t js_cg_if_stmt(el_val_t expr, el_val_t indent, el_val_t declared);
el_val_t js_cg_for_body(el_val_t item, el_val_t list_expr, el_val_t body, el_val_t indent, el_val_t declared);
el_val_t js_cg_for_stmt(el_val_t expr, el_val_t indent, el_val_t declared);
el_val_t js_cg_stmts(el_val_t stmts, el_val_t indent, el_val_t declared);
el_val_t js_params_str(el_val_t params);
el_val_t js_transform_implicit_return(el_val_t body);
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 compile(el_val_t source);
el_val_t compile_js(el_val_t source);
el_val_t compile_dispatch(el_val_t tgt, el_val_t source);
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 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 lex_is_digit(el_val_t ch) {
  if (str_eq(ch, EL_STR("0"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("1"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("2"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("3"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("4"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("5"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("6"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("7"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("8"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("9"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t lex_is_alpha(el_val_t ch) {
  if (str_eq(ch, EL_STR("a"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("b"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("c"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("d"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("e"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("f"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("g"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("h"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("i"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("j"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("k"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("l"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("m"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("n"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("o"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("p"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("q"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("r"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("s"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("t"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("u"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("v"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("w"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("x"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("y"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("z"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("A"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("B"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("C"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("D"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("E"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("F"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("G"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("H"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("I"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("J"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("K"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("L"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("M"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("N"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("O"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("P"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("Q"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("R"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("S"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("T"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("U"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("V"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("W"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("X"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("Y"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("Z"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_alnum_or_underscore(el_val_t ch) {
  if (lex_is_digit(ch)) {
    return 1;
  }
  if (lex_is_alpha(ch)) {
    return 1;
  }
  if (str_eq(ch, EL_STR("_"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t lex_is_whitespace(el_val_t ch) {
  if (str_eq(ch, EL_STR(" "))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("\t"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("\n"))) {
    return 1;
  }
  if (str_eq(ch, EL_STR("\r"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t make_tok(el_val_t kind, el_val_t value) {
  return el_map_new(2, "kind", kind, "value", value);
  return 0;
}

el_val_t keyword_kind(el_val_t word) {
  if (str_eq(word, EL_STR("let"))) {
    return EL_STR("Let");
  }
  if (str_eq(word, EL_STR("fn"))) {
    return EL_STR("Fn");
  }
  if (str_eq(word, EL_STR("type"))) {
    return EL_STR("Type");
  }
  if (str_eq(word, EL_STR("enum"))) {
    return EL_STR("Enum");
  }
  if (str_eq(word, EL_STR("match"))) {
    return EL_STR("Match");
  }
  if (str_eq(word, EL_STR("return"))) {
    return EL_STR("Return");
  }
  if (str_eq(word, EL_STR("if"))) {
    return EL_STR("If");
  }
  if (str_eq(word, EL_STR("else"))) {
    return EL_STR("Else");
  }
  if (str_eq(word, EL_STR("for"))) {
    return EL_STR("For");
  }
  if (str_eq(word, EL_STR("in"))) {
    return EL_STR("In");
  }
  if (str_eq(word, EL_STR("while"))) {
    return EL_STR("While");
  }
  if (str_eq(word, EL_STR("import"))) {
    return EL_STR("Import");
  }
  if (str_eq(word, EL_STR("from"))) {
    return EL_STR("From");
  }
  if (str_eq(word, EL_STR("as"))) {
    return EL_STR("As");
  }
  if (str_eq(word, EL_STR("with"))) {
    return EL_STR("With");
  }
  if (str_eq(word, EL_STR("sealed"))) {
    return EL_STR("Sealed");
  }
  if (str_eq(word, EL_STR("activate"))) {
    return EL_STR("Activate");
  }
  if (str_eq(word, EL_STR("where"))) {
    return EL_STR("Where");
  }
  if (str_eq(word, EL_STR("test"))) {
    return EL_STR("Test");
  }
  if (str_eq(word, EL_STR("seed"))) {
    return EL_STR("Seed");
  }
  if (str_eq(word, EL_STR("assert"))) {
    return EL_STR("Assert");
  }
  if (str_eq(word, EL_STR("protocol"))) {
    return EL_STR("Protocol");
  }
  if (str_eq(word, EL_STR("impl"))) {
    return EL_STR("Impl");
  }
  if (str_eq(word, EL_STR("retry"))) {
    return EL_STR("Retry");
  }
  if (str_eq(word, EL_STR("times"))) {
    return EL_STR("Times");
  }
  if (str_eq(word, EL_STR("fallback"))) {
    return EL_STR("Fallback");
  }
  if (str_eq(word, EL_STR("reason"))) {
    return EL_STR("Reason");
  }
  if (str_eq(word, EL_STR("parallel"))) {
    return EL_STR("Parallel");
  }
  if (str_eq(word, EL_STR("trace"))) {
    return EL_STR("Trace");
  }
  if (str_eq(word, EL_STR("requires"))) {
    return EL_STR("Requires");
  }
  if (str_eq(word, EL_STR("deploy"))) {
    return EL_STR("Deploy");
  }
  if (str_eq(word, EL_STR("to"))) {
    return EL_STR("To");
  }
  if (str_eq(word, EL_STR("via"))) {
    return EL_STR("Via");
  }
  if (str_eq(word, EL_STR("target"))) {
    return EL_STR("Target");
  }
  if (str_eq(word, EL_STR("true"))) {
    return EL_STR("Bool");
  }
  if (str_eq(word, EL_STR("false"))) {
    return EL_STR("Bool");
  }
  if (str_eq(word, EL_STR("cgi"))) {
    return EL_STR("Cgi");
  }
  if (str_eq(word, EL_STR("service"))) {
    return EL_STR("Service");
  }
  if (str_eq(word, EL_STR("manager"))) {
    return EL_STR("Manager");
  }
  if (str_eq(word, EL_STR("engine"))) {
    return EL_STR("Engine");
  }
  if (str_eq(word, EL_STR("accessor"))) {
    return EL_STR("Accessor");
  }
  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 parts = native_list_empty();
  el_val_t running = 1;
  while (running) {
    if (i >= total) {
      running = 0;
    } else {
      el_val_t ch = native_list_get(chars, i);
      if (lex_is_digit(ch)) {
        parts = native_list_append(parts, ch);
        i = (i + 1);
      } else {
        running = 0;
      }
    }
  }
  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 parts = native_list_empty();
  el_val_t running = 1;
  while (running) {
    if (i >= total) {
      running = 0;
    } else {
      el_val_t ch = native_list_get(chars, i);
      if (is_alnum_or_underscore(ch)) {
        parts = native_list_append(parts, ch);
        i = (i + 1);
      } else {
        running = 0;
      }
    }
  }
  return el_map_new(2, "text", str_join(parts, EL_STR("")), "pos", i);
  return 0;
}

el_val_t substr_at(el_val_t chars, el_val_t start, el_val_t total, el_val_t needle) {
  el_val_t nchars = native_string_chars(needle);
  el_val_t nlen = native_list_len(nchars);
  if ((start + nlen) > total) {
    return 0;
  }
  el_val_t i = 0;
  el_val_t matched = 1;
  while (i < nlen) {
    el_val_t a = native_list_get(chars, (start + i));
    el_val_t b = native_list_get(nchars, i);
    if (str_eq(a, b)) {
      i = (i + 1);
    } else {
      matched = 0;
      EL_NULL;
      i = nlen;
    }
  }
  return matched;
  return 0;
}

el_val_t str_has(el_val_t s, el_val_t needle) {
  el_val_t chars = native_string_chars(s);
  el_val_t total = native_list_len(chars);
  el_val_t i = 0;
  el_val_t found = 0;
  while (i < total) {
    if (substr_at(chars, i, total, needle)) {
      found = 1;
      i = total;
    } else {
      i = (i + 1);
    }
  }
  return found;
  return 0;
}

el_val_t looks_like_code(el_val_t s) {
  if (str_has(s, EL_STR("<script"))) {
    return 1;
  }
  if (str_has(s, EL_STR("<style"))) {
    return 1;
  }
  if (str_has(s, EL_STR("function"))) {
    if (str_has(s, EL_STR(";"))) {
      return 1;
    }
  }
  return 0;
  return 0;
}

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_parts = native_list_empty();
  el_val_t i = 0;
  el_val_t in_squote = 0;
  el_val_t in_dquote = 0;
  el_val_t in_btick = 0;
  el_val_t prev = EL_STR("");
  while (i < total) {
    el_val_t ch = native_list_get(chars, i);
    el_val_t in_js_string = 0;
    if (in_squote) {
      in_js_string = 1;
    }
    if (in_dquote) {
      in_js_string = 1;
    }
    if (in_btick) {
      in_js_string = 1;
    }
    if (in_js_string) {
      if (str_eq(ch, EL_STR("\\"))) {
        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_parts = native_list_append(out_parts, nc);
          prev = nc;
          i = (next_i + 1);
        } else {
          prev = ch;
          i = next_i;
        }
      } else {
        if (in_squote) {
          if (str_eq(ch, EL_STR("'"))) {
            in_squote = 0;
          }
        } else {
          if (in_dquote) {
            if (str_eq(ch, EL_STR("\""))) {
              in_dquote = 0;
            }
          } else {
            if (in_btick) {
              if (str_eq(ch, EL_STR("`"))) {
                in_btick = 0;
              }
            }
          }
        }
        out_parts = native_list_append(out_parts, ch);
        prev = ch;
        i = (i + 1);
      }
    } else {
      el_val_t next_i = (i + 1);
      el_val_t next_ch = EL_STR("");
      if (next_i < total) {
        next_ch = native_list_get(chars, next_i);
      }
      if (str_eq(ch, EL_STR("/"))) {
        if (str_eq(next_ch, EL_STR("/"))) {
          if (str_eq(prev, EL_STR(":"))) {
            out_parts = native_list_append(out_parts, ch);
            prev = ch;
            i = (i + 1);
          } else {
            i = (i + 2);
            el_val_t scanning = 1;
            while (scanning) {
              if (i >= total) {
                scanning = 0;
              } else {
                el_val_t lc = native_list_get(chars, i);
                if (str_eq(lc, EL_STR("\n"))) {
                  scanning = 0;
                } else {
                  i = (i + 1);
                }
              }
            }
            prev = EL_STR("");
          }
        } else {
          if (str_eq(next_ch, EL_STR("*"))) {
            i = (i + 2);
            el_val_t scanning2 = 1;
            while (scanning2) {
              if (i >= total) {
                scanning2 = 0;
              } else {
                el_val_t bc = native_list_get(chars, i);
                if (str_eq(bc, EL_STR("*"))) {
                  el_val_t after = (i + 1);
                  if (after < total) {
                    el_val_t nc2 = native_list_get(chars, after);
                    if (str_eq(nc2, EL_STR("/"))) {
                      i = (after + 1);
                      scanning2 = 0;
                    } else {
                      i = (i + 1);
                    }
                  } else {
                    i = (i + 1);
                  }
                } else {
                  i = (i + 1);
                }
              }
            }
            prev = EL_STR("");
          } else {
            out_parts = native_list_append(out_parts, ch);
            prev = ch;
            i = (i + 1);
          }
        }
      } else {
        if (str_eq(ch, EL_STR("'"))) {
          in_squote = 1;
          out_parts = native_list_append(out_parts, ch);
          prev = ch;
          i = (i + 1);
        } else {
          if (str_eq(ch, EL_STR("\""))) {
            in_dquote = 1;
            out_parts = native_list_append(out_parts, ch);
            prev = ch;
            i = (i + 1);
          } else {
            if (str_eq(ch, EL_STR("`"))) {
              in_btick = 1;
              out_parts = native_list_append(out_parts, ch);
              prev = ch;
              i = (i + 1);
            } else {
              out_parts = native_list_append(out_parts, ch);
              prev = ch;
              i = (i + 1);
            }
          }
        }
      }
    }
  }
  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 parts = native_list_empty();
  el_val_t running = 1;
  while (running) {
    if (i >= total) {
      running = 0;
    } else {
      el_val_t ch = native_list_get(chars, i);
      if (str_eq(ch, EL_STR("\\"))) {
        el_val_t next_i = (i + 1);
        if (next_i < total) {
          el_val_t next_ch = native_list_get(chars, next_i);
          if (str_eq(next_ch, EL_STR("\""))) {
            parts = native_list_append(parts, EL_STR("\""));
            i = (next_i + 1);
          } else {
            if (str_eq(next_ch, EL_STR("n"))) {
              parts = native_list_append(parts, EL_STR("\n"));
              i = (next_i + 1);
            } else {
              if (str_eq(next_ch, EL_STR("t"))) {
                parts = native_list_append(parts, EL_STR("\t"));
                i = (next_i + 1);
              } else {
                if (str_eq(next_ch, EL_STR("r"))) {
                  parts = native_list_append(parts, EL_STR("\r"));
                  i = (next_i + 1);
                } else {
                  if (str_eq(next_ch, EL_STR("\\"))) {
                    parts = native_list_append(parts, EL_STR("\\"));
                    i = (next_i + 1);
                  } else {
                    parts = native_list_append(parts, next_ch);
                    i = (next_i + 1);
                  }
                }
              }
            }
          }
        } else {
          i = (i + 1);
        }
      } else {
        if (str_eq(ch, EL_STR("\""))) {
          i = (i + 1);
          running = 0;
        } else {
          parts = native_list_append(parts, ch);
          i = (i + 1);
        }
      }
    }
  }
  return el_map_new(2, "text", str_join(parts, EL_STR("")), "pos", i);
  return 0;
}

el_val_t lex(el_val_t source) {
  el_val_t chars = native_string_chars(source);
  el_val_t total = native_list_len(chars);
  el_val_t tokens = native_list_empty();
  el_val_t i = 0;
  while (i < total) {
    el_val_t ch = native_list_get(chars, i);
    if (lex_is_whitespace(ch)) {
      i = (i + 1);
    } else {
      if (str_eq(ch, EL_STR("/"))) {
        el_val_t next_i = (i + 1);
        if (next_i < total) {
          el_val_t next_ch = native_list_get(chars, next_i);
          if (str_eq(next_ch, EL_STR("/"))) {
            i = (i + 2);
            el_val_t running2 = 1;
            while (running2) {
              if (i >= total) {
                running2 = 0;
              } else {
                el_val_t lch = native_list_get(chars, i);
                if (str_eq(lch, EL_STR("\n"))) {
                  running2 = 0;
                } else {
                  i = (i + 1);
                }
              }
            }
          } else {
            tokens = native_list_append(tokens, make_tok(EL_STR("Slash"), EL_STR("/")));
            i = (i + 1);
          }
        } else {
          tokens = native_list_append(tokens, make_tok(EL_STR("Slash"), EL_STR("/")));
          i = (i + 1);
        }
      } else {
        if (str_eq(ch, EL_STR("\""))) {
          el_val_t result = scan_string(chars, (i + 1), total);
          el_val_t str_text = el_get_field(result, EL_STR("text"));
          el_val_t new_pos = el_get_field(result, EL_STR("pos"));
          el_val_t clean_text = str_text;
          if (looks_like_code(str_text)) {
            clean_text = strip_code_comments(str_text);
          }
          tokens = native_list_append(tokens, make_tok(EL_STR("Str"), clean_text));
          i = new_pos;
        } else {
          if (lex_is_digit(ch)) {
            el_val_t result = scan_digits(chars, i, total);
            el_val_t num_text = el_get_field(result, EL_STR("text"));
            el_val_t new_pos = el_get_field(result, EL_STR("pos"));
            if (new_pos < total) {
              el_val_t dot_ch = native_list_get(chars, new_pos);
              if (str_eq(dot_ch, EL_STR("."))) {
                el_val_t after_dot = (new_pos + 1);
                if (after_dot < total) {
                  el_val_t after_dot_ch = native_list_get(chars, after_dot);
                  if (lex_is_digit(after_dot_ch)) {
                    el_val_t frac_result = scan_digits(chars, after_dot, total);
                    el_val_t frac_text = el_get_field(frac_result, EL_STR("text"));
                    el_val_t frac_pos = el_get_field(frac_result, EL_STR("pos"));
                    tokens = native_list_append(tokens, make_tok(EL_STR("Float"), el_str_concat(el_str_concat(num_text, EL_STR(".")), frac_text)));
                    i = frac_pos;
                  } else {
                    tokens = native_list_append(tokens, make_tok(EL_STR("Int"), num_text));
                    i = new_pos;
                  }
                } else {
                  tokens = native_list_append(tokens, make_tok(EL_STR("Int"), num_text));
                  i = new_pos;
                }
              } else {
                tokens = native_list_append(tokens, make_tok(EL_STR("Int"), num_text));
                i = new_pos;
              }
            } else {
              tokens = native_list_append(tokens, make_tok(EL_STR("Int"), num_text));
              i = new_pos;
            }
          } else {
            if (lex_is_alpha(ch) || str_eq(ch, EL_STR("_"))) {
              el_val_t result = scan_ident(chars, i, total);
              el_val_t word = el_get_field(result, EL_STR("text"));
              el_val_t new_pos = el_get_field(result, EL_STR("pos"));
              el_val_t kw = keyword_kind(word);
              if (str_eq(kw, EL_STR(""))) {
                tokens = native_list_append(tokens, make_tok(EL_STR("Ident"), word));
              } else {
                tokens = native_list_append(tokens, make_tok(kw, word));
              }
              i = new_pos;
            } else {
              el_val_t peek_i = (i + 1);
              el_val_t peek_ch = EL_STR("");
              if (peek_i < total) {
                peek_ch = native_list_get(chars, peek_i);
              }
              if (str_eq(ch, EL_STR("="))) {
                if (str_eq(peek_ch, EL_STR("="))) {
                  tokens = native_list_append(tokens, make_tok(EL_STR("EqEq"), EL_STR("==")));
                  i = (i + 2);
                } else {
                  if (str_eq(peek_ch, EL_STR(">"))) {
                    tokens = native_list_append(tokens, make_tok(EL_STR("FatArrow"), EL_STR("=>")));
                    i = (i + 2);
                  } else {
                    tokens = native_list_append(tokens, make_tok(EL_STR("Eq"), EL_STR("=")));
                    i = (i + 1);
                  }
                }
              } else {
                if (str_eq(ch, EL_STR("!"))) {
                  if (str_eq(peek_ch, EL_STR("="))) {
                    tokens = native_list_append(tokens, make_tok(EL_STR("NotEq"), EL_STR("!=")));
                    i = (i + 2);
                  } else {
                    tokens = native_list_append(tokens, make_tok(EL_STR("Not"), EL_STR("!")));
                    i = (i + 1);
                  }
                } else {
                  if (str_eq(ch, EL_STR("<"))) {
                    if (str_eq(peek_ch, EL_STR("="))) {
                      tokens = native_list_append(tokens, make_tok(EL_STR("LtEq"), EL_STR("<=")));
                      i = (i + 2);
                    } else {
                      tokens = native_list_append(tokens, make_tok(EL_STR("Lt"), EL_STR("<")));
                      i = (i + 1);
                    }
                  } else {
                    if (str_eq(ch, EL_STR(">"))) {
                      if (str_eq(peek_ch, EL_STR("="))) {
                        tokens = native_list_append(tokens, make_tok(EL_STR("GtEq"), EL_STR(">=")));
                        i = (i + 2);
                      } else {
                        tokens = native_list_append(tokens, make_tok(EL_STR("Gt"), EL_STR(">")));
                        i = (i + 1);
                      }
                    } else {
                      if (str_eq(ch, EL_STR("&"))) {
                        if (str_eq(peek_ch, EL_STR("&"))) {
                          tokens = native_list_append(tokens, make_tok(EL_STR("And"), EL_STR("&&")));
                          i = (i + 2);
                        } else {
                          i = (i + 1);
                        }
                      } else {
                        if (str_eq(ch, EL_STR("|"))) {
                          if (str_eq(peek_ch, EL_STR("|"))) {
                            tokens = native_list_append(tokens, make_tok(EL_STR("Or"), EL_STR("||")));
                            i = (i + 2);
                          } else {
                            if (str_eq(peek_ch, EL_STR(">"))) {
                              tokens = native_list_append(tokens, make_tok(EL_STR("PipeOp"), EL_STR("|>")));
                              i = (i + 2);
                            } else {
                              tokens = native_list_append(tokens, make_tok(EL_STR("Pipe"), EL_STR("|")));
                              i = (i + 1);
                            }
                          }
                        } else {
                          if (str_eq(ch, EL_STR("-"))) {
                            if (str_eq(peek_ch, EL_STR(">"))) {
                              tokens = native_list_append(tokens, make_tok(EL_STR("Arrow"), EL_STR("->")));
                              i = (i + 2);
                            } else {
                              tokens = native_list_append(tokens, make_tok(EL_STR("Minus"), EL_STR("-")));
                              i = (i + 1);
                            }
                          } else {
                            if (str_eq(ch, EL_STR(":"))) {
                              if (str_eq(peek_ch, EL_STR(":"))) {
                                tokens = native_list_append(tokens, make_tok(EL_STR("ColonColon"), EL_STR("::")));
                                i = (i + 2);
                              } else {
                                tokens = native_list_append(tokens, make_tok(EL_STR("Colon"), EL_STR(":")));
                                i = (i + 1);
                              }
                            } else {
                              if (str_eq(ch, EL_STR("+"))) {
                                tokens = native_list_append(tokens, make_tok(EL_STR("Plus"), EL_STR("+")));
                                i = (i + 1);
                              } else {
                                if (str_eq(ch, EL_STR("*"))) {
                                  tokens = native_list_append(tokens, make_tok(EL_STR("Star"), EL_STR("*")));
                                  i = (i + 1);
                                } else {
                                  if (str_eq(ch, EL_STR("%"))) {
                                    tokens = native_list_append(tokens, make_tok(EL_STR("Percent"), EL_STR("%")));
                                    i = (i + 1);
                                  } else {
                                    if (str_eq(ch, EL_STR("("))) {
                                      tokens = native_list_append(tokens, make_tok(EL_STR("LParen"), EL_STR("(")));
                                      i = (i + 1);
                                    } else {
                                      if (str_eq(ch, EL_STR(")"))) {
                                        tokens = native_list_append(tokens, make_tok(EL_STR("RParen"), EL_STR(")")));
                                        i = (i + 1);
                                      } else {
                                        if (str_eq(ch, EL_STR("{"))) {
                                          tokens = native_list_append(tokens, make_tok(EL_STR("LBrace"), EL_STR("{")));
                                          i = (i + 1);
                                        } else {
                                          if (str_eq(ch, EL_STR("}"))) {
                                            tokens = native_list_append(tokens, make_tok(EL_STR("RBrace"), EL_STR("}")));
                                            i = (i + 1);
                                          } else {
                                            if (str_eq(ch, EL_STR("["))) {
                                              tokens = native_list_append(tokens, make_tok(EL_STR("LBracket"), EL_STR("[")));
                                              i = (i + 1);
                                            } else {
                                              if (str_eq(ch, EL_STR("]"))) {
                                                tokens = native_list_append(tokens, make_tok(EL_STR("RBracket"), EL_STR("]")));
                                                i = (i + 1);
                                              } else {
                                                if (str_eq(ch, EL_STR(","))) {
                                                  tokens = native_list_append(tokens, make_tok(EL_STR("Comma"), EL_STR(",")));
                                                  i = (i + 1);
                                                } else {
                                                  if (str_eq(ch, EL_STR("."))) {
                                                    tokens = native_list_append(tokens, make_tok(EL_STR("Dot"), EL_STR(".")));
                                                    i = (i + 1);
                                                  } else {
                                                    if (str_eq(ch, EL_STR(";"))) {
                                                      tokens = native_list_append(tokens, make_tok(EL_STR("Semicolon"), EL_STR(";")));
                                                      i = (i + 1);
                                                    } else {
                                                      if (str_eq(ch, EL_STR("@"))) {
                                                        tokens = native_list_append(tokens, make_tok(EL_STR("At"), EL_STR("@")));
                                                        i = (i + 1);
                                                      } else {
                                                        if (str_eq(ch, EL_STR("?"))) {
                                                          tokens = native_list_append(tokens, make_tok(EL_STR("QuestionMark"), EL_STR("?")));
                                                          i = (i + 1);
                                                        } else {
                                                          i = (i + 1);
                                                        }
                                                      }
                                                    }
                                                  }
                                                }
                                              }
                                            }
                                          }
                                        }
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  tokens = native_list_append(tokens, make_tok(EL_STR("Eof"), EL_STR("")));
  return tokens;
  return 0;
}

el_val_t tok_at(el_val_t tokens, el_val_t pos) {
  return native_list_get(tokens, pos);
  return 0;
}

el_val_t tok_kind(el_val_t tokens, el_val_t pos) {
  el_val_t t = native_list_get(tokens, pos);
  return el_get_field(t, EL_STR("kind"));
  return 0;
}

el_val_t tok_value(el_val_t tokens, el_val_t pos) {
  el_val_t t = native_list_get(tokens, pos);
  return el_get_field(t, EL_STR("value"));
  return 0;
}

el_val_t expect(el_val_t tokens, el_val_t pos, el_val_t kind) {
  el_val_t k = tok_kind(tokens, pos);
  if (str_eq(k, kind)) {
    return (pos + 1);
  }
  return (pos + 1);
  return 0;
}

el_val_t make_result(el_val_t node, el_val_t pos) {
  return el_map_new(2, "node", node, "pos", pos);
  return 0;
}

el_val_t skip_type(el_val_t tokens, el_val_t pos) {
  el_val_t k = tok_kind(tokens, pos);
  if (str_eq(k, EL_STR("LBracket"))) {
    el_val_t p = (pos + 1);
    p = skip_type(tokens, p);
    p = expect(tokens, p, EL_STR("RBracket"));
    return p;
  }
  if (str_eq(k, EL_STR("Ident"))) {
    el_val_t p = (pos + 1);
    el_val_t k2 = tok_kind(tokens, p);
    if (str_eq(k2, EL_STR("Lt"))) {
      p = (p + 1);
      el_val_t depth = 1;
      el_val_t running = 1;
      while (running) {
        el_val_t kk = tok_kind(tokens, p);
        if (str_eq(kk, EL_STR("Eof"))) {
          running = 0;
        } else {
          if (str_eq(kk, EL_STR("Lt"))) {
            depth = (depth + 1);
            p = (p + 1);
          } else {
            if (str_eq(kk, EL_STR("Gt"))) {
              depth = (depth - 1);
              p = (p + 1);
              if (depth <= 0) {
                running = 0;
              }
            } else {
              p = (p + 1);
            }
          }
        }
      }
      el_val_t k3 = tok_kind(tokens, p);
      if (str_eq(k3, EL_STR("QuestionMark"))) {
        p = (p + 1);
      }
      return p;
    }
    if (str_eq(k2, EL_STR("QuestionMark"))) {
      return (p + 1);
    }
    return p;
  }
  return (pos + 1);
  return 0;
}

el_val_t parse_params(el_val_t tokens, el_val_t pos) {
  el_val_t p = expect(tokens, pos, EL_STR("LParen"));
  el_val_t params = native_list_empty();
  el_val_t running = 1;
  while (running) {
    el_val_t k = tok_kind(tokens, p);
    if (str_eq(k, EL_STR("RParen"))) {
      running = 0;
    } else {
      if (str_eq(k, EL_STR("Eof"))) {
        running = 0;
      } else {
        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(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"))) {
          p = (p + 1);
        }
      }
    }
  }
  p = expect(tokens, p, EL_STR("RParen"));
  return el_map_new(2, "params", params, "pos", p);
  return 0;
}

el_val_t parse_primary(el_val_t tokens, el_val_t pos) {
  el_val_t k = tok_kind(tokens, pos);
  el_val_t v = tok_value(tokens, pos);
  if (str_eq(k, EL_STR("Int"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Int"), "value", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Float"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Float"), "value", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Str"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Str"), "value", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Bool"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Bool"), "value", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Ident"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("LParen"))) {
    el_val_t r = parse_expr(tokens, (pos + 1));
    el_val_t node = el_get_field(r, EL_STR("node"));
    el_val_t p = el_get_field(r, EL_STR("pos"));
    p = expect(tokens, p, EL_STR("RParen"));
    return make_result(node, p);
  }
  if (str_eq(k, EL_STR("LBracket"))) {
    el_val_t p = (pos + 1);
    el_val_t elems = native_list_empty();
    el_val_t running = 1;
    while (running) {
      el_val_t k2 = tok_kind(tokens, p);
      if (str_eq(k2, EL_STR("RBracket"))) {
        running = 0;
      } else {
        if (str_eq(k2, EL_STR("Eof"))) {
          running = 0;
        } else {
          el_val_t r = parse_expr(tokens, p);
          el_val_t elem = el_get_field(r, EL_STR("node"));
          p = el_get_field(r, EL_STR("pos"));
          elems = native_list_append(elems, elem);
          el_val_t k3 = tok_kind(tokens, p);
          if (str_eq(k3, EL_STR("Comma"))) {
            p = (p + 1);
          }
        }
      }
    }
    p = expect(tokens, p, EL_STR("RBracket"));
    return make_result(el_map_new(2, "expr", EL_STR("Array"), "elems", elems), p);
  }
  if (str_eq(k, EL_STR("LBrace"))) {
    el_val_t no_block = state_get(EL_STR("__no_block_expr"));
    if (str_eq(no_block, EL_STR("1"))) {
      return make_result(el_map_new(1, "expr", EL_STR("Nil")), pos);
    }
    el_val_t p = (pos + 1);
    el_val_t pairs = native_list_empty();
    el_val_t running = 1;
    while (running) {
      el_val_t k2 = tok_kind(tokens, p);
      if (str_eq(k2, EL_STR("RBrace"))) {
        running = 0;
      } else {
        if (str_eq(k2, EL_STR("Eof"))) {
          running = 0;
        } else {
          el_val_t key = tok_value(tokens, p);
          el_val_t new_p = (p + 1);
          new_p = expect(tokens, new_p, EL_STR("Colon"));
          el_val_t r = parse_expr(tokens, new_p);
          el_val_t val_node = el_get_field(r, EL_STR("node"));
          new_p = el_get_field(r, EL_STR("pos"));
          el_val_t pair = el_map_new(2, "key", key, "value", val_node);
          pairs = native_list_append(pairs, pair);
          el_val_t k3 = tok_kind(tokens, new_p);
          if (str_eq(k3, EL_STR("Comma"))) {
            new_p = (new_p + 1);
          }
          if (new_p <= p) {
            p = (p + 1);
          } else {
            p = new_p;
          }
        }
      }
    }
    p = expect(tokens, p, EL_STR("RBrace"));
    return make_result(el_map_new(2, "expr", EL_STR("Map"), "pairs", pairs), p);
  }
  if (str_eq(k, EL_STR("If"))) {
    el_val_t r = parse_if(tokens, pos);
    return r;
  }
  if (str_eq(k, EL_STR("Match"))) {
    el_val_t r = parse_match(tokens, pos);
    return r;
  }
  if (str_eq(k, EL_STR("For"))) {
    el_val_t r = parse_for_expr(tokens, pos);
    return r;
  }
  if (str_eq(k, EL_STR("Not"))) {
    el_val_t r = parse_primary(tokens, (pos + 1));
    el_val_t inner = el_get_field(r, EL_STR("node"));
    el_val_t p = el_get_field(r, EL_STR("pos"));
    return make_result(el_map_new(2, "expr", EL_STR("Not"), "inner", inner), p);
  }
  if (str_eq(k, EL_STR("Minus"))) {
    el_val_t r = parse_primary(tokens, (pos + 1));
    el_val_t inner = el_get_field(r, EL_STR("node"));
    el_val_t p = el_get_field(r, EL_STR("pos"));
    return make_result(el_map_new(2, "expr", EL_STR("Neg"), "inner", inner), p);
  }
  if (str_eq(k, EL_STR("Target"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("To"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Via"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Deploy"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Reason"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Times"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Fallback"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Retry"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Parallel"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Trace"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Requires"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Where"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("As"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("With"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Manager"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Engine"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Accessor"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Vessel"))) {
    return make_result(el_map_new(2, "expr", EL_STR("Ident"), "name", v), (pos + 1));
  }
  return make_result(el_map_new(1, "expr", EL_STR("Nil")), (pos + 1));
  return 0;
}

el_val_t parse_if(el_val_t tokens, el_val_t pos) {
  el_val_t p = expect(tokens, pos, EL_STR("If"));
  el_val_t prev_no_block = state_get(EL_STR("__no_block_expr"));
  state_set(EL_STR("__no_block_expr"), EL_STR("1"));
  el_val_t r = parse_expr(tokens, p);
  state_set(EL_STR("__no_block_expr"), prev_no_block);
  el_val_t cond = el_get_field(r, EL_STR("node"));
  p = el_get_field(r, EL_STR("pos"));
  el_val_t r2 = parse_block(tokens, p);
  el_val_t then_stmts = el_get_field(r2, EL_STR("stmts"));
  p = el_get_field(r2, EL_STR("pos"));
  el_val_t has_else = 0;
  el_val_t else_stmts = native_list_empty();
  el_val_t k2 = tok_kind(tokens, p);
  if (str_eq(k2, EL_STR("Else"))) {
    p = (p + 1);
    el_val_t k3 = tok_kind(tokens, p);
    if (str_eq(k3, EL_STR("If"))) {
      el_val_t r3 = parse_if(tokens, p);
      el_val_t nested = el_get_field(r3, EL_STR("node"));
      p = el_get_field(r3, EL_STR("pos"));
      else_stmts = native_list_append(else_stmts, el_map_new(2, "stmt", EL_STR("Expr"), "value", nested));
      has_else = 1;
    } else {
      el_val_t r3 = parse_block(tokens, p);
      else_stmts = el_get_field(r3, EL_STR("stmts"));
      p = el_get_field(r3, EL_STR("pos"));
      has_else = 1;
    }
  }
  return make_result(el_map_new(5, "expr", EL_STR("If"), "cond", cond, "then", then_stmts, "else", else_stmts, "has_else", has_else), p);
  return 0;
}

el_val_t parse_match(el_val_t tokens, el_val_t pos) {
  el_val_t p = expect(tokens, pos, EL_STR("Match"));
  el_val_t prev_no_block = state_get(EL_STR("__no_block_expr"));
  state_set(EL_STR("__no_block_expr"), EL_STR("1"));
  el_val_t r = parse_expr(tokens, p);
  state_set(EL_STR("__no_block_expr"), prev_no_block);
  el_val_t subject = el_get_field(r, EL_STR("node"));
  p = el_get_field(r, EL_STR("pos"));
  p = expect(tokens, p, EL_STR("LBrace"));
  el_val_t arms = native_list_empty();
  el_val_t running = 1;
  while (running) {
    el_val_t k = tok_kind(tokens, p);
    if (str_eq(k, EL_STR("RBrace"))) {
      running = 0;
    } else {
      if (str_eq(k, EL_STR("Eof"))) {
        running = 0;
      } else {
        el_val_t r2 = parse_pattern(tokens, p);
        el_val_t pattern = el_get_field(r2, EL_STR("node"));
        p = el_get_field(r2, EL_STR("pos"));
        p = expect(tokens, p, EL_STR("FatArrow"));
        el_val_t r3 = parse_expr(tokens, p);
        el_val_t body = el_get_field(r3, EL_STR("node"));
        p = el_get_field(r3, EL_STR("pos"));
        el_val_t arm = el_map_new(2, "pattern", pattern, "body", body);
        arms = native_list_append(arms, arm);
        el_val_t k2 = tok_kind(tokens, p);
        if (str_eq(k2, EL_STR("Comma"))) {
          p = (p + 1);
        }
      }
    }
  }
  p = expect(tokens, p, EL_STR("RBrace"));
  return make_result(el_map_new(3, "expr", EL_STR("Match"), "subject", subject, "arms", arms), p);
  return 0;
}

el_val_t parse_pattern(el_val_t tokens, el_val_t pos) {
  el_val_t k = tok_kind(tokens, pos);
  if (str_eq(k, EL_STR("Ident"))) {
    el_val_t v = tok_value(tokens, pos);
    if (str_eq(v, EL_STR("_"))) {
      return make_result(el_map_new(1, "pattern", EL_STR("Wildcard")), (pos + 1));
    }
    return make_result(el_map_new(2, "pattern", EL_STR("Binding"), "name", v), (pos + 1));
  }
  if (str_eq(k, EL_STR("Int"))) {
    return make_result(el_map_new(2, "pattern", EL_STR("LitInt"), "value", tok_value(tokens, pos)), (pos + 1));
  }
  if (str_eq(k, EL_STR("Str"))) {
    return make_result(el_map_new(2, "pattern", EL_STR("LitStr"), "value", tok_value(tokens, pos)), (pos + 1));
  }
  if (str_eq(k, EL_STR("Bool"))) {
    return make_result(el_map_new(2, "pattern", EL_STR("LitBool"), "value", tok_value(tokens, pos)), (pos + 1));
  }
  return make_result(el_map_new(1, "pattern", EL_STR("Wildcard")), (pos + 1));
  return 0;
}

el_val_t parse_for_expr(el_val_t tokens, el_val_t pos) {
  el_val_t p = expect(tokens, pos, EL_STR("For"));
  el_val_t item_name = tok_value(tokens, p);
  p = (p + 1);
  p = expect(tokens, p, EL_STR("In"));
  el_val_t prev_no_block = state_get(EL_STR("__no_block_expr"));
  state_set(EL_STR("__no_block_expr"), EL_STR("1"));
  el_val_t r = parse_expr(tokens, p);
  state_set(EL_STR("__no_block_expr"), prev_no_block);
  el_val_t list_expr = el_get_field(r, EL_STR("node"));
  p = el_get_field(r, EL_STR("pos"));
  el_val_t r2 = parse_block(tokens, p);
  el_val_t body = el_get_field(r2, EL_STR("stmts"));
  p = el_get_field(r2, EL_STR("pos"));
  return make_result(el_map_new(4, "expr", EL_STR("For"), "item", item_name, "list", list_expr, "body", body), p);
  return 0;
}

el_val_t parse_block(el_val_t tokens, el_val_t pos) {
  el_val_t p = expect(tokens, pos, EL_STR("LBrace"));
  el_val_t stmts = native_list_empty();
  el_val_t running = 1;
  while (running) {
    el_val_t k = tok_kind(tokens, p);
    if (str_eq(k, EL_STR("RBrace"))) {
      running = 0;
    } else {
      if (str_eq(k, EL_STR("Eof"))) {
        running = 0;
      } else {
        el_val_t r = parse_stmt(tokens, p);
        el_val_t stmt = el_get_field(r, EL_STR("node"));
        el_val_t new_p = el_get_field(r, EL_STR("pos"));
        stmts = native_list_append(stmts, stmt);
        if (new_p <= p) {
          p = (p + 1);
        } else {
          p = new_p;
        }
      }
    }
  }
  p = expect(tokens, p, EL_STR("RBrace"));
  return el_map_new(2, "stmts", stmts, "pos", p);
  return 0;
}

el_val_t is_duration_unit(el_val_t name) {
  if (str_eq(name, EL_STR("nanos"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("nano"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("millis"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("milli"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("millisecond"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("milliseconds"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("second"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("seconds"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("minute"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("minutes"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("hour"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("hours"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("day"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("days"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t parse_postfix(el_val_t tokens, el_val_t pos) {
  el_val_t r = parse_primary(tokens, pos);
  el_val_t node = el_get_field(r, EL_STR("node"));
  el_val_t p = el_get_field(r, EL_STR("pos"));
  el_val_t primary_kind = el_get_field(node, EL_STR("expr"));
  if (str_eq(primary_kind, EL_STR("Int"))) {
    el_val_t dot_kind = tok_kind(tokens, p);
    if (str_eq(dot_kind, EL_STR("Dot"))) {
      el_val_t unit_kind = tok_kind(tokens, (p + 1));
      if (str_eq(unit_kind, EL_STR("Ident"))) {
        el_val_t unit_name = tok_value(tokens, (p + 1));
        if (is_duration_unit(unit_name)) {
          el_val_t count_str = el_get_field(node, EL_STR("value"));
          node = el_map_new(3, "expr", EL_STR("DurationLit"), "count", count_str, "unit", unit_name);
          p = (p + 2);
        }
      }
    }
  }
  el_val_t running = 1;
  while (running) {
    el_val_t k = tok_kind(tokens, p);
    if (str_eq(k, EL_STR("LParen"))) {
      p = (p + 1);
      el_val_t args = native_list_empty();
      el_val_t run2 = 1;
      while (run2) {
        el_val_t k2 = tok_kind(tokens, p);
        if (str_eq(k2, EL_STR("RParen"))) {
          run2 = 0;
        } else {
          if (str_eq(k2, EL_STR("Eof"))) {
            run2 = 0;
          } else {
            el_val_t r2 = parse_expr(tokens, p);
            el_val_t arg = el_get_field(r2, EL_STR("node"));
            p = el_get_field(r2, EL_STR("pos"));
            args = native_list_append(args, arg);
            el_val_t k3 = tok_kind(tokens, p);
            if (str_eq(k3, EL_STR("Comma"))) {
              p = (p + 1);
            }
          }
        }
      }
      p = expect(tokens, p, EL_STR("RParen"));
      node = el_map_new(3, "expr", EL_STR("Call"), "func", node, "args", args);
    } else {
      if (str_eq(k, EL_STR("Dot"))) {
        el_val_t field = tok_value(tokens, (p + 1));
        p = (p + 2);
        node = el_map_new(3, "expr", EL_STR("Field"), "object", node, "field", field);
      } else {
        if (str_eq(k, EL_STR("LBracket"))) {
          el_val_t r2 = parse_expr(tokens, (p + 1));
          el_val_t idx = el_get_field(r2, EL_STR("node"));
          p = el_get_field(r2, EL_STR("pos"));
          p = expect(tokens, p, EL_STR("RBracket"));
          node = el_map_new(3, "expr", EL_STR("Index"), "object", node, "index", idx);
        } else {
          if (str_eq(k, EL_STR("QuestionMark"))) {
            p = (p + 1);
            node = el_map_new(2, "expr", EL_STR("Try"), "inner", node);
          } else {
            running = 0;
          }
        }
      }
    }
  }
  return make_result(node, p);
  return 0;
}

el_val_t op_precedence(el_val_t kind) {
  if (str_eq(kind, EL_STR("Or"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("And"))) {
    return 2;
  }
  if (str_eq(kind, EL_STR("EqEq"))) {
    return 3;
  }
  if (str_eq(kind, EL_STR("NotEq"))) {
    return 3;
  }
  if (str_eq(kind, EL_STR("Lt"))) {
    return 4;
  }
  if (str_eq(kind, EL_STR("Gt"))) {
    return 4;
  }
  if (str_eq(kind, EL_STR("LtEq"))) {
    return 4;
  }
  if (str_eq(kind, EL_STR("GtEq"))) {
    return 4;
  }
  if (str_eq(kind, EL_STR("Plus"))) {
    return 5;
  }
  if (str_eq(kind, EL_STR("Minus"))) {
    return 5;
  }
  if (str_eq(kind, EL_STR("Star"))) {
    return 6;
  }
  if (str_eq(kind, EL_STR("Slash"))) {
    return 6;
  }
  return 0;
  return 0;
}

el_val_t is_binop(el_val_t kind) {
  if (str_eq(kind, EL_STR("Or"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("And"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("EqEq"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("NotEq"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("Lt"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("Gt"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("LtEq"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("GtEq"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("Plus"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("Minus"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("Star"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("Slash"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t parse_binop(el_val_t tokens, el_val_t pos, el_val_t min_prec) {
  el_val_t r = parse_postfix(tokens, pos);
  el_val_t left = el_get_field(r, EL_STR("node"));
  el_val_t p = el_get_field(r, EL_STR("pos"));
  el_val_t running = 1;
  while (running) {
    el_val_t k = tok_kind(tokens, p);
    el_val_t prec = op_precedence(k);
    if (is_binop(k)) {
      if (prec >= min_prec) {
        el_val_t op = k;
        el_val_t r2 = parse_binop(tokens, (p + 1), (prec + 1));
        el_val_t right = el_get_field(r2, EL_STR("node"));
        p = el_get_field(r2, EL_STR("pos"));
        left = el_map_new(4, "expr", EL_STR("BinOp"), "op", op, "left", left, "right", right);
      } else {
        running = 0;
      }
    } else {
      running = 0;
    }
  }
  return make_result(left, p);
  return 0;
}

el_val_t parse_expr(el_val_t tokens, el_val_t pos) {
  return parse_binop(tokens, pos, 1);
  return 0;
}

el_val_t parse_stmt(el_val_t tokens, el_val_t pos) {
  el_val_t k = tok_kind(tokens, pos);
  if (str_eq(k, EL_STR("Let"))) {
    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(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);
    el_val_t k2 = tok_kind(tokens, p);
    if (str_eq(k2, EL_STR("RBrace"))) {
      return make_result(el_map_new(2, "stmt", EL_STR("Return"), "value", el_map_new(1, "expr", EL_STR("Nil"))), p);
    }
    if (str_eq(k2, EL_STR("Eof"))) {
      return make_result(el_map_new(2, "stmt", EL_STR("Return"), "value", el_map_new(1, "expr", EL_STR("Nil"))), p);
    }
    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(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);
    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 k2 = tok_kind(tokens, p);
    if (str_eq(k2, 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);
    }
    el_val_t r2 = parse_block(tokens, p);
    el_val_t body = el_get_field(r2, EL_STR("stmts"));
    p = el_get_field(r2, EL_STR("pos"));
    return make_result(el_map_new(5, "stmt", EL_STR("FnDef"), "name", name, "params", params, "body", body, "ret_type", ret_type), p);
  }
  if (str_eq(k, EL_STR("Type"))) {
    el_val_t p = (pos + 1);
    el_val_t name = tok_value(tokens, p);
    p = (p + 1);
    p = expect(tokens, p, EL_STR("LBrace"));
    el_val_t fields = native_list_empty();
    el_val_t running = 1;
    while (running) {
      el_val_t k2 = tok_kind(tokens, p);
      if (str_eq(k2, EL_STR("RBrace"))) {
        running = 0;
      } else {
        if (str_eq(k2, EL_STR("Eof"))) {
          running = 0;
        } else {
          el_val_t fname = tok_value(tokens, p);
          p = (p + 1);
          p = expect(tokens, p, EL_STR("Colon"));
          p = skip_type(tokens, p);
          fields = native_list_append(fields, el_map_new(1, "name", fname));
          el_val_t k3 = tok_kind(tokens, p);
          if (str_eq(k3, EL_STR("Comma"))) {
            p = (p + 1);
          }
        }
      }
    }
    p = expect(tokens, p, EL_STR("RBrace"));
    return make_result(el_map_new(3, "stmt", EL_STR("TypeDef"), "name", name, "fields", fields), p);
  }
  if (str_eq(k, EL_STR("Enum"))) {
    el_val_t p = (pos + 1);
    el_val_t name = tok_value(tokens, p);
    p = (p + 1);
    p = expect(tokens, p, EL_STR("LBrace"));
    el_val_t variants = native_list_empty();
    el_val_t running = 1;
    while (running) {
      el_val_t k2 = tok_kind(tokens, p);
      if (str_eq(k2, EL_STR("RBrace"))) {
        running = 0;
      } else {
        if (str_eq(k2, EL_STR("Eof"))) {
          running = 0;
        } else {
          el_val_t vname = tok_value(tokens, p);
          p = (p + 1);
          variants = native_list_append(variants, el_map_new(1, "name", vname));
          el_val_t k3 = tok_kind(tokens, p);
          if (str_eq(k3, EL_STR("Comma"))) {
            p = (p + 1);
          }
        }
      }
    }
    p = expect(tokens, p, EL_STR("RBrace"));
    return make_result(el_map_new(3, "stmt", EL_STR("EnumDef"), "name", name, "variants", variants), p);
  }
  if (str_eq(k, EL_STR("Import"))) {
    el_val_t p = (pos + 1);
    el_val_t path = tok_value(tokens, p);
    p = (p + 1);
    return make_result(el_map_new(2, "stmt", EL_STR("Import"), "path", path), p);
  }
  if (str_eq(k, EL_STR("From"))) {
    el_val_t p = (pos + 1);
    el_val_t module_name = tok_value(tokens, p);
    p = (p + 1);
    el_val_t k2 = tok_kind(tokens, p);
    if (str_eq(k2, EL_STR("Import"))) {
      p = (p + 1);
    }
    el_val_t k3 = tok_kind(tokens, p);
    if (str_eq(k3, EL_STR("LBrace"))) {
      p = (p + 1);
      el_val_t running = 1;
      while (running) {
        el_val_t k4 = tok_kind(tokens, p);
        if (str_eq(k4, EL_STR("RBrace"))) {
          running = 0;
        } else {
          if (str_eq(k4, EL_STR("Eof"))) {
            running = 0;
          } else {
            p = (p + 1);
            el_val_t k5 = tok_kind(tokens, p);
            if (str_eq(k5, EL_STR("Comma"))) {
              p = (p + 1);
            }
          }
        }
      }
      p = expect(tokens, p, EL_STR("RBrace"));
    }
    return make_result(el_map_new(2, "stmt", EL_STR("Import"), "path", module_name), p);
  }
  if (str_eq(k, EL_STR("While"))) {
    el_val_t p = (pos + 1);
    el_val_t prev_no_block = state_get(EL_STR("__no_block_expr"));
    state_set(EL_STR("__no_block_expr"), EL_STR("1"));
    el_val_t r = parse_expr(tokens, p);
    state_set(EL_STR("__no_block_expr"), prev_no_block);
    el_val_t cond = el_get_field(r, EL_STR("node"));
    p = el_get_field(r, EL_STR("pos"));
    el_val_t r2 = parse_block(tokens, p);
    el_val_t body = el_get_field(r2, EL_STR("stmts"));
    p = el_get_field(r2, EL_STR("pos"));
    return make_result(el_map_new(3, "stmt", EL_STR("While"), "cond", cond, "body", body), p);
  }
  if (str_eq(k, EL_STR("For"))) {
    el_val_t p = (pos + 1);
    el_val_t item_name = tok_value(tokens, p);
    p = (p + 1);
    p = expect(tokens, p, EL_STR("In"));
    el_val_t prev_no_block = state_get(EL_STR("__no_block_expr"));
    state_set(EL_STR("__no_block_expr"), EL_STR("1"));
    el_val_t r = parse_expr(tokens, p);
    state_set(EL_STR("__no_block_expr"), prev_no_block);
    el_val_t list_expr = el_get_field(r, EL_STR("node"));
    p = el_get_field(r, EL_STR("pos"));
    el_val_t r2 = parse_block(tokens, p);
    el_val_t body = el_get_field(r2, EL_STR("stmts"));
    p = el_get_field(r2, EL_STR("pos"));
    return make_result(el_map_new(4, "stmt", EL_STR("For"), "item", item_name, "list", list_expr, "body", body), p);
  }
  if (str_eq(k, EL_STR("At"))) {
    el_val_t p = (pos + 1);
    el_val_t dec_name = tok_value(tokens, p);
    p = (p + 1);
    el_val_t r = parse_stmt(tokens, p);
    el_val_t inner = el_get_field(r, EL_STR("node"));
    el_val_t p2 = el_get_field(r, EL_STR("pos"));
    el_val_t inner_kind = el_get_field(inner, EL_STR("stmt"));
    if (str_eq(inner_kind, EL_STR("FnDef"))) {
      el_val_t with_dec = el_map_new(6, "stmt", EL_STR("FnDef"), "name", el_get_field(inner, EL_STR("name")), "params", el_get_field(inner, EL_STR("params")), "body", el_get_field(inner, EL_STR("body")), "ret_type", el_get_field(inner, EL_STR("ret_type")), "decorator", dec_name);
      return make_result(with_dec, p2);
    }
    return r;
  }
  if (str_eq(k, EL_STR("Cgi"))) {
    el_val_t p = (pos + 1);
    el_val_t name = tok_value(tokens, p);
    p = (p + 1);
    p = expect(tokens, p, EL_STR("LBrace"));
    el_val_t dharma_id = EL_STR("");
    el_val_t principal = EL_STR("");
    el_val_t network = EL_STR("");
    el_val_t engram = EL_STR("");
    el_val_t has_dharma_id = 0;
    el_val_t has_principal = 0;
    el_val_t has_network = 0;
    el_val_t has_engram = 0;
    el_val_t running = 1;
    while (running) {
      el_val_t k2 = tok_kind(tokens, p);
      if (str_eq(k2, EL_STR("RBrace"))) {
        running = 0;
      } else {
        if (str_eq(k2, EL_STR("Eof"))) {
          running = 0;
        } else {
          el_val_t fname = tok_value(tokens, p);
          p = (p + 1);
          p = expect(tokens, p, EL_STR("Colon"));
          el_val_t fval = tok_value(tokens, p);
          p = (p + 1);
          if (str_eq(fname, EL_STR("dharma_id"))) {
            dharma_id = fval;
            has_dharma_id = 1;
          }
          if (str_eq(fname, EL_STR("principal"))) {
            principal = fval;
            has_principal = 1;
          }
          if (str_eq(fname, EL_STR("network"))) {
            network = fval;
            has_network = 1;
          }
          if (str_eq(fname, EL_STR("engram"))) {
            engram = fval;
            has_engram = 1;
          }
          el_val_t k3 = tok_kind(tokens, p);
          if (str_eq(k3, EL_STR("Comma"))) {
            p = (p + 1);
          }
        }
      }
    }
    p = expect(tokens, p, EL_STR("RBrace"));
    return make_result(el_map_new(10, "stmt", EL_STR("CgiBlock"), "name", name, "dharma_id", dharma_id, "principal", principal, "network", network, "engram", engram, "has_dharma_id", has_dharma_id, "has_principal", has_principal, "has_network", has_network, "has_engram", has_engram), p);
  }
  if (str_eq(k, EL_STR("Service"))) {
    el_val_t p = (pos + 1);
    el_val_t name = tok_value(tokens, p);
    p = (p + 1);
    p = expect(tokens, p, EL_STR("LBrace"));
    el_val_t sponsor = EL_STR("");
    el_val_t domain = EL_STR("");
    el_val_t running = 1;
    while (running) {
      el_val_t k2 = tok_kind(tokens, p);
      if (str_eq(k2, EL_STR("RBrace"))) {
        running = 0;
      } else {
        if (str_eq(k2, EL_STR("Eof"))) {
          running = 0;
        } else {
          el_val_t fname = tok_value(tokens, p);
          p = (p + 1);
          p = expect(tokens, p, EL_STR("Colon"));
          el_val_t fval = tok_value(tokens, p);
          p = (p + 1);
          if (str_eq(fname, EL_STR("sponsor"))) {
            sponsor = fval;
          }
          if (str_eq(fname, EL_STR("domain"))) {
            domain = fval;
          }
          el_val_t k3 = tok_kind(tokens, p);
          if (str_eq(k3, EL_STR("Comma"))) {
            p = (p + 1);
          }
        }
      }
    }
    p = expect(tokens, p, EL_STR("RBrace"));
    return make_result(el_map_new(4, "stmt", EL_STR("ServiceBlock"), "name", name, "sponsor", sponsor, "domain", domain), p);
  }
  if (str_eq(k, EL_STR("Ident"))) {
    el_val_t k2 = tok_kind(tokens, (pos + 1));
    if (str_eq(k2, EL_STR("Eq"))) {
      el_val_t name = tok_value(tokens, pos);
      el_val_t p = (pos + 2);
      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("Assign"), "name", name, "value", val), p);
    }
  }
  el_val_t r = parse_expr(tokens, pos);
  el_val_t val = el_get_field(r, EL_STR("node"));
  el_val_t p = el_get_field(r, EL_STR("pos"));
  return make_result(el_map_new(2, "stmt", EL_STR("Expr"), "value", val), p);
  return 0;
}

el_val_t parse(el_val_t tokens) {
  el_val_t total = native_list_len(tokens);
  el_val_t stmts = native_list_empty();
  el_val_t pos = 0;
  el_val_t running = 1;
  while (running) {
    if (pos >= total) {
      running = 0;
    } else {
      el_val_t k = tok_kind(tokens, pos);
      if (str_eq(k, EL_STR("Eof"))) {
        running = 0;
      } else {
        el_val_t r = parse_stmt(tokens, pos);
        el_val_t stmt = el_get_field(r, EL_STR("node"));
        el_val_t new_pos = el_get_field(r, EL_STR("pos"));
        stmts = native_list_append(stmts, stmt);
        if (new_pos <= pos) {
          pos = (pos + 1);
        } else {
          pos = new_pos;
        }
      }
    }
  }
  return stmts;
  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 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 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 (bval == 92) {
        parts = native_list_append(parts, EL_STR("\\\\"));
      } else {
        if (bval == 10) {
          parts = native_list_append(parts, EL_STR("\\n"));
        } else {
          if (bval == 13) {
            parts = native_list_append(parts, EL_STR("\\r"));
          } else {
            if (bval == 9) {
              parts = native_list_append(parts, EL_STR("\\t"));
            } else {
              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));
              }
            }
          }
        }
      }
    }
    i = (i + 1);
  }
  return str_join(parts, EL_STR(""));
  return 0;
}

el_val_t c_str_lit(el_val_t s) {
  return el_str_concat(el_str_concat(EL_STR("\""), c_escape(s)), EL_STR("\""));
  return 0;
}

el_val_t el_type_to_c(el_val_t type_str) {
  if (str_eq(type_str, EL_STR("String"))) {
    return EL_STR("const char*");
  }
  if (str_eq(type_str, EL_STR("Int"))) {
    return EL_STR("int64_t");
  }
  if (str_eq(type_str, EL_STR("Bool"))) {
    return EL_STR("int");
  }
  if (str_eq(type_str, EL_STR("Float"))) {
    return EL_STR("double");
  }
  if (str_eq(type_str, EL_STR("Void"))) {
    return EL_STR("void");
  }
  if (str_eq(type_str, EL_STR("void"))) {
    return EL_STR("void");
  }
  return EL_STR("void*");
  return 0;
}

el_val_t emit_line(el_val_t line) {
  println(line);
  return 0;
}

el_val_t emit_blank(void) {
  println(EL_STR(""));
  return 0;
}

el_val_t binop_to_c(el_val_t op) {
  if (str_eq(op, EL_STR("Plus"))) {
    return EL_STR("+");
  }
  if (str_eq(op, EL_STR("Minus"))) {
    return EL_STR("-");
  }
  if (str_eq(op, EL_STR("Star"))) {
    return EL_STR("*");
  }
  if (str_eq(op, EL_STR("Slash"))) {
    return EL_STR("/");
  }
  if (str_eq(op, EL_STR("EqEq"))) {
    return EL_STR("==");
  }
  if (str_eq(op, EL_STR("NotEq"))) {
    return EL_STR("!=");
  }
  if (str_eq(op, EL_STR("Lt"))) {
    return EL_STR("<");
  }
  if (str_eq(op, EL_STR("Gt"))) {
    return EL_STR(">");
  }
  if (str_eq(op, EL_STR("LtEq"))) {
    return EL_STR("<=");
  }
  if (str_eq(op, EL_STR("GtEq"))) {
    return EL_STR(">=");
  }
  if (str_eq(op, EL_STR("And"))) {
    return EL_STR("&&");
  }
  if (str_eq(op, EL_STR("Or"))) {
    return EL_STR("||");
  }
  return op;
  return 0;
}

el_val_t duration_unit_nanos(el_val_t unit) {
  if (str_eq(unit, EL_STR("nano"))) {
    return EL_STR("1LL");
  }
  if (str_eq(unit, EL_STR("nanos"))) {
    return EL_STR("1LL");
  }
  if (str_eq(unit, EL_STR("milli"))) {
    return EL_STR("1000000LL");
  }
  if (str_eq(unit, EL_STR("millis"))) {
    return EL_STR("1000000LL");
  }
  if (str_eq(unit, EL_STR("millisecond"))) {
    return EL_STR("1000000LL");
  }
  if (str_eq(unit, EL_STR("milliseconds"))) {
    return EL_STR("1000000LL");
  }
  if (str_eq(unit, EL_STR("second"))) {
    return EL_STR("1000000000LL");
  }
  if (str_eq(unit, EL_STR("seconds"))) {
    return EL_STR("1000000000LL");
  }
  if (str_eq(unit, EL_STR("minute"))) {
    return EL_STR("60000000000LL");
  }
  if (str_eq(unit, EL_STR("minutes"))) {
    return EL_STR("60000000000LL");
  }
  if (str_eq(unit, EL_STR("hour"))) {
    return EL_STR("3600000000000LL");
  }
  if (str_eq(unit, EL_STR("hours"))) {
    return EL_STR("3600000000000LL");
  }
  if (str_eq(unit, EL_STR("day"))) {
    return EL_STR("86400000000000LL");
  }
  if (str_eq(unit, EL_STR("days"))) {
    return EL_STR("86400000000000LL");
  }
  return EL_STR("1LL");
  return 0;
}

el_val_t cg_expr(el_val_t expr) {
  el_val_t kind = el_get_field(expr, EL_STR("expr"));
  if (str_eq(kind, EL_STR("Int"))) {
    el_val_t v = el_get_field(expr, EL_STR("value"));
    return v;
  }
  if (str_eq(kind, EL_STR("DurationLit"))) {
    el_val_t count = el_get_field(expr, EL_STR("count"));
    el_val_t unit = el_get_field(expr, EL_STR("unit"));
    el_val_t mult = duration_unit_nanos(unit);
    return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_duration_from_nanos((el_val_t)("), count), EL_STR("LL * ")), mult), EL_STR("))"));
  }
  if (str_eq(kind, EL_STR("Float"))) {
    el_val_t v = el_get_field(expr, EL_STR("value"));
    return el_str_concat(el_str_concat(EL_STR("el_from_float("), v), EL_STR(")"));
  }
  if (str_eq(kind, EL_STR("Str"))) {
    el_val_t v = el_get_field(expr, EL_STR("value"));
    return el_str_concat(el_str_concat(EL_STR("EL_STR("), c_str_lit(v)), EL_STR(")"));
  }
  if (str_eq(kind, EL_STR("Bool"))) {
    el_val_t v = el_get_field(expr, EL_STR("value"));
    if (str_eq(v, EL_STR("true"))) {
      return EL_STR("1");
    }
    return EL_STR("0");
  }
  if (str_eq(kind, EL_STR("Nil"))) {
    return EL_STR("EL_NULL");
  }
  if (str_eq(kind, EL_STR("Ident"))) {
    el_val_t name = el_get_field(expr, EL_STR("name"));
    return name;
  }
  if (str_eq(kind, EL_STR("Not"))) {
    el_val_t inner = el_get_field(expr, EL_STR("inner"));
    el_val_t inner_c = cg_expr(inner);
    return el_str_concat(EL_STR("!"), inner_c);
  }
  if (str_eq(kind, EL_STR("Neg"))) {
    el_val_t inner = el_get_field(expr, EL_STR("inner"));
    el_val_t inner_c = cg_expr(inner);
    return el_str_concat(el_str_concat(EL_STR("(-"), inner_c), EL_STR(")"));
  }
  if (str_eq(kind, EL_STR("BinOp"))) {
    el_val_t op = el_get_field(expr, EL_STR("op"));
    el_val_t left = el_get_field(expr, EL_STR("left"));
    el_val_t right = el_get_field(expr, EL_STR("right"));
    el_val_t left_c = cg_expr(left);
    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);
    el_val_t right_is_dur = is_duration_expr(right);
    el_val_t left_is_ld = is_localdate_expr(left);
    el_val_t right_is_ld = is_localdate_expr(right);
    el_val_t left_is_lt = is_localtime_expr(left);
    el_val_t right_is_lt = is_localtime_expr(right);
    el_val_t left_is_ct = is_caltime_expr(left);
    el_val_t right_is_ct = is_caltime_expr(right);
    if (left_is_ld) {
      if (str_eq(op, EL_STR("Plus"))) {
        if (right_is_dur) {
          return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_local_date_add_dur("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
        }
      }
      if (str_eq(op, EL_STR("Lt"))) {
        if (right_is_ld) {
          return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_local_date_lt("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
        }
      }
      if (str_eq(op, EL_STR("EqEq"))) {
        if (right_is_ld) {
          return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_local_date_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
        }
      }
    }
    if (left_is_lt) {
      if (str_eq(op, EL_STR("Plus"))) {
        if (right_is_dur) {
          return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_local_time_add_dur("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
        }
      }
    }
    if (left_is_ct) {
      if (str_eq(op, EL_STR("Plus"))) {
        if (right_is_ct) {
          time_record_violation(EL_STR("caltime_plus_caltime"), EL_STR("CalendarTime + CalendarTime is not allowed (use cal_to_instant + Duration)"));
          return EL_STR("0 /* TIME_TYPE_ERROR: CalendarTime + CalendarTime */");
        }
      }
    }
    el_val_t any_temporal = 0;
    if (left_is_inst) {
      any_temporal = 1;
    }
    if (right_is_inst) {
      any_temporal = 1;
    }
    if (left_is_dur) {
      any_temporal = 1;
    }
    if (right_is_dur) {
      any_temporal = 1;
    }
    if (any_temporal) {
      if (str_eq(op, EL_STR("Plus"))) {
        if (left_is_inst) {
          if (right_is_dur) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_instant_add_dur("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
          if (right_is_inst) {
            time_record_violation(EL_STR("instant_plus_instant"), EL_STR("Instant + Instant is not allowed"));
            return EL_STR("0 /* TIME_TYPE_ERROR: Instant + Instant */");
          }
        }
        if (left_is_dur) {
          if (right_is_inst) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_instant_add_dur("), right_c), EL_STR(", ")), left_c), EL_STR(")"));
          }
          if (right_is_dur) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_duration_add("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
          if (is_int_expr(right)) {
            time_record_violation(EL_STR("duration_plus_int"), EL_STR("Duration + Int is not allowed (use duration_seconds(n) or N.seconds)"));
            return EL_STR("0 /* TIME_TYPE_ERROR: Duration + Int */");
          }
        }
        if (right_is_dur) {
          if (is_int_expr(left)) {
            time_record_violation(EL_STR("duration_plus_int"), EL_STR("Int + Duration is not allowed"));
            return EL_STR("0 /* TIME_TYPE_ERROR: Int + Duration */");
          }
        }
      }
      if (str_eq(op, EL_STR("Minus"))) {
        if (left_is_inst) {
          if (right_is_dur) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_instant_sub_dur("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
          if (right_is_inst) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_instant_diff("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
        if (left_is_dur) {
          if (right_is_dur) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_duration_sub("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
          if (is_int_expr(right)) {
            time_record_violation(EL_STR("duration_minus_int"), EL_STR("Duration - Int is not allowed"));
            return EL_STR("0 /* TIME_TYPE_ERROR: Duration - Int */");
          }
        }
      }
      if (str_eq(op, EL_STR("Star"))) {
        if (left_is_dur) {
          if (is_int_expr(right)) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_duration_scale("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
        if (right_is_dur) {
          if (is_int_expr(left)) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_duration_scale("), right_c), EL_STR(", ")), left_c), EL_STR(")"));
          }
        }
      }
      if (str_eq(op, EL_STR("Slash"))) {
        if (left_is_dur) {
          if (is_int_expr(right)) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_duration_div("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
      }
      if (str_eq(op, EL_STR("Lt"))) {
        if (left_is_inst) {
          if (right_is_inst) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_instant_lt("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
          if (right_is_dur) {
            time_record_violation(EL_STR("instant_cmp_duration"), EL_STR("Instant < Duration is not allowed"));
            return EL_STR("0 /* TIME_TYPE_ERROR: Instant < Duration */");
          }
        }
        if (left_is_dur) {
          if (right_is_dur) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_duration_lt("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
          if (right_is_inst) {
            time_record_violation(EL_STR("duration_cmp_instant"), EL_STR("Duration < Instant is not allowed"));
            return EL_STR("0 /* TIME_TYPE_ERROR: Duration < Instant */");
          }
        }
      }
      if (str_eq(op, EL_STR("LtEq"))) {
        if (left_is_inst) {
          if (right_is_inst) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_instant_le("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
        if (left_is_dur) {
          if (right_is_dur) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_duration_le("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
      }
      if (str_eq(op, EL_STR("Gt"))) {
        if (left_is_inst) {
          if (right_is_inst) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_instant_gt("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
        if (left_is_dur) {
          if (right_is_dur) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_duration_gt("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
      }
      if (str_eq(op, EL_STR("GtEq"))) {
        if (left_is_inst) {
          if (right_is_inst) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_instant_ge("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
        if (left_is_dur) {
          if (right_is_dur) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_duration_ge("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
      }
      if (str_eq(op, EL_STR("EqEq"))) {
        if (left_is_inst) {
          if (right_is_inst) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_instant_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
        if (left_is_dur) {
          if (right_is_dur) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_duration_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
      }
      if (str_eq(op, EL_STR("NotEq"))) {
        if (left_is_inst) {
          if (right_is_inst) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_instant_ne("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
        if (left_is_dur) {
          if (right_is_dur) {
            return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_duration_ne("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
          }
        }
      }
    }
    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 (is_int_expr(left)) {
        if (is_int_expr(right)) {
          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("Int"))) {
        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(right_kind, EL_STR("Int"))) {
        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(")"));
      }
      if (str_eq(right_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(")"));
      }
      if (str_eq(left_kind, EL_STR("BinOp"))) {
        el_val_t left_op = el_get_field(left, EL_STR("op"));
        if (str_eq(left_op, EL_STR("Plus"))) {
          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("BinOp"))) {
        el_val_t right_op = el_get_field(right, EL_STR("op"));
        if (str_eq(right_op, EL_STR("Plus"))) {
          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(left_kind, EL_STR("Ident"))) {
        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("Ident"))) {
        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("Int"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" == ")), right_c), EL_STR(")"));
      }
      if (str_eq(right_kind, EL_STR("Int"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" == ")), right_c), EL_STR(")"));
      }
      if (str_eq(left_kind, EL_STR("Bool"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" == ")), right_c), EL_STR(")"));
      }
      if (str_eq(right_kind, EL_STR("Bool"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" == ")), right_c), EL_STR(")"));
      }
      if (str_eq(left_kind, EL_STR("Ident"))) {
        if (str_eq(right_kind, EL_STR("Ident"))) {
          el_val_t lname = el_get_field(left, EL_STR("name"));
          el_val_t rname = el_get_field(right, EL_STR("name"));
          if (is_int_name(lname)) {
            if (is_int_name(rname)) {
              return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" == ")), right_c), EL_STR(")"));
            }
          }
        }
      }
      if (is_int_expr(left)) {
        if (is_int_expr(right)) {
          return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" == ")), right_c), EL_STR(")"));
        }
      }
      if (str_eq(left_kind, EL_STR("Str"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("str_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
      }
      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(left_kind, EL_STR("Ident"))) {
        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("Ident"))) {
        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(left_kind, EL_STR("Call"))) {
        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("Call"))) {
        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("Int"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" != ")), right_c), EL_STR(")"));
      }
      if (str_eq(right_kind, EL_STR("Int"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" != ")), right_c), EL_STR(")"));
      }
      if (str_eq(left_kind, EL_STR("Bool"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" != ")), right_c), EL_STR(")"));
      }
      if (str_eq(right_kind, EL_STR("Bool"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" != ")), right_c), EL_STR(")"));
      }
      if (str_eq(left_kind, EL_STR("Ident"))) {
        if (str_eq(right_kind, EL_STR("Ident"))) {
          el_val_t lname = el_get_field(left, EL_STR("name"));
          el_val_t rname = el_get_field(right, EL_STR("name"));
          if (is_int_name(lname)) {
            if (is_int_name(rname)) {
              return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" != ")), right_c), EL_STR(")"));
            }
          }
        }
      }
      if (is_int_expr(left)) {
        if (is_int_expr(right)) {
          return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" != ")), right_c), EL_STR(")"));
        }
      }
      if (str_eq(left_kind, EL_STR("Str"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("!str_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
      }
      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(left_kind, EL_STR("Ident"))) {
        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("Ident"))) {
        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(left_kind, EL_STR("Call"))) {
        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("Call"))) {
        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 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(kind, EL_STR("Call"))) {
    el_val_t func = el_get_field(expr, EL_STR("func"));
    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_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);
      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);
      arity_check_call(fn_name, arity);
      if (str_eq(fn_name, EL_STR("sleep"))) {
        if (arity == 1) {
          el_val_t only_arg = native_list_get(args, 0);
          if (is_duration_expr(only_arg)) {
            return el_str_concat(el_str_concat(EL_STR("el_sleep_duration("), args_c), EL_STR(")"));
          }
        }
      }
      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"))) {
      el_val_t obj = el_get_field(func, EL_STR("object"));
      el_val_t field = el_get_field(func, EL_STR("field"));
      el_val_t obj_c = cg_expr(obj);
      if (arity > 0) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(field, EL_STR("(")), obj_c), EL_STR(", ")), args_c), EL_STR(")"));
      }
      return el_str_concat(el_str_concat(el_str_concat(field, EL_STR("(")), obj_c), EL_STR(")"));
    }
    el_val_t fn_c = cg_expr(func);
    return el_str_concat(el_str_concat(el_str_concat(fn_c, EL_STR("(")), args_c), EL_STR(")"));
  }
  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_c = cg_expr(obj);
    return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_get_field("), obj_c), EL_STR(", EL_STR(")), c_str_lit(field)), EL_STR("))"));
  }
  if (str_eq(kind, EL_STR("Index"))) {
    el_val_t obj = el_get_field(expr, EL_STR("object"));
    el_val_t idx = el_get_field(expr, EL_STR("index"));
    el_val_t obj_c = cg_expr(obj);
    el_val_t idx_c = cg_expr(idx);
    el_val_t idx_kind = el_get_field(idx, EL_STR("expr"));
    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(")"));
    }
    return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_list_get("), obj_c), EL_STR(", ")), idx_c), EL_STR(")"));
  }
  if (str_eq(kind, EL_STR("Array"))) {
    el_val_t elems = el_get_field(expr, EL_STR("elems"));
    el_val_t n = native_list_len(elems);
    if (n == 0) {
      return EL_STR("el_list_empty()");
    }
    el_val_t items_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);
      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(", ")), 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"));
    el_val_t n = native_list_len(pairs);
    if (n == 0) {
      return EL_STR("el_map_new(0)");
    }
    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);
      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(", ")), 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"));
    return cg_expr(inner);
  }
  if (str_eq(kind, EL_STR("If"))) {
    return cg_if_expr(expr);
  }
  if (str_eq(kind, EL_STR("Match"))) {
    return cg_match(expr);
  }
  return EL_STR("EL_NULL");
  return 0;
}

el_val_t next_match_id(void) {
  el_val_t csv = state_get(EL_STR("__match_counter"));
  el_val_t n = 0;
  if (!str_eq(csv, EL_STR(""))) {
    n = str_to_int(csv);
  }
  n = (n + 1);
  state_set(EL_STR("__match_counter"), native_int_to_str(n));
  return native_int_to_str(n);
  return 0;
}

el_val_t cg_match(el_val_t expr) {
  el_val_t subject = el_get_field(expr, EL_STR("subject"));
  el_val_t arms = el_get_field(expr, EL_STR("arms"));
  el_val_t subj_c = cg_expr(subject);
  el_val_t id = next_match_id();
  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 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) {
    el_val_t arm = native_list_get(arms, i);
    el_val_t pat = el_get_field(arm, EL_STR("pattern"));
    el_val_t body = el_get_field(arm, EL_STR("body"));
    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"))) {
      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"));
        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"));
          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"));
            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"));
              el_val_t bv = EL_STR("0");
              if (str_eq(v, EL_STR("true"))) {
                bv = EL_STR("1");
              }
              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 {
              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("; } ")));
            }
          }
        }
      }
    }
    i = (i + 1);
  }
  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;
}

el_val_t next_if_id(void) {
  el_val_t csv = state_get(EL_STR("__if_expr_counter"));
  el_val_t n = 0;
  if (!str_eq(csv, EL_STR(""))) {
    n = str_to_int(csv);
  }
  n = (n + 1);
  state_set(EL_STR("__if_expr_counter"), native_int_to_str(n));
  return native_int_to_str(n);
  return 0;
}

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 parts = native_list_empty();
  el_val_t i = 0;
  while (i < n) {
    el_val_t s = native_list_get(stmts, i);
    el_val_t sk = el_get_field(s, EL_STR("stmt"));
    el_val_t is_last = 0;
    if (i == (n - 1)) {
      is_last = 1;
    }
    if (str_eq(sk, EL_STR("Let"))) {
      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);
      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);
        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) {
            parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(result_var, EL_STR(" = (")), val_c), EL_STR("); ")));
          } else {
            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);
            parts = native_list_append(parts, el_str_concat(el_str_concat(el_str_concat(aname, EL_STR(" = ")), aval_c), EL_STR("; ")));
          } else {
          }
        }
      }
    }
    i = (i + 1);
  }
  return str_join(parts, EL_STR(""));
  return 0;
}

el_val_t cg_if_expr(el_val_t expr) {
  el_val_t cond = el_get_field(expr, EL_STR("cond"));
  el_val_t then_stmts = el_get_field(expr, EL_STR("then"));
  el_val_t else_stmts = el_get_field(expr, EL_STR("else"));
  el_val_t has_else = el_get_field(expr, EL_STR("has_else"));
  el_val_t cond_c = cg_expr(cond);
  el_val_t id = next_if_id();
  el_val_t result_var = el_str_concat(EL_STR("_if_result_"), id);
  el_val_t then_c = cg_if_expr_arm(then_stmts, result_var);
  el_val_t else_c = EL_STR("");
  if (has_else) {
    else_c = cg_if_expr_arm(else_stmts, result_var);
  }
  el_val_t 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("({ el_val_t "), result_var), EL_STR(" = 0; if (")), cond_c), EL_STR(") { ")), then_c), EL_STR("} else { ")), else_c), EL_STR("} ")), result_var), EL_STR("; })"));
  return out;
  return 0;
}

el_val_t list_contains(el_val_t lst, el_val_t s) {
  el_val_t n = native_list_len(lst);
  el_val_t i = 0;
  while (i < n) {
    el_val_t item = native_list_get(lst, i);
    if (str_eq(item, s)) {
      return 1;
    }
    i = (i + 1);
  }
  return 0;
  return 0;
}

el_val_t cg_stmt(el_val_t stmt, el_val_t indent, el_val_t declared) {
  el_val_t kind = el_get_field(stmt, EL_STR("stmt"));
  if (str_eq(kind, EL_STR("Let"))) {
    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);
    }
    if (str_eq(ltype, EL_STR("Instant"))) {
      add_instant_name(name);
    }
    if (str_eq(ltype, EL_STR("Duration"))) {
      add_duration_name(name);
    }
    if (str_eq(ltype, EL_STR("Calendar"))) {
      add_calendar_name(name);
    }
    if (str_eq(ltype, EL_STR("CalendarTime"))) {
      add_caltime_name(name);
    }
    if (str_eq(ltype, EL_STR("Rhythm"))) {
      add_rhythm_name(name);
    }
    if (str_eq(ltype, EL_STR("LocalDate"))) {
      add_localdate_name(name);
    }
    if (str_eq(ltype, EL_STR("LocalTime"))) {
      add_localtime_name(name);
    }
    if (str_eq(ltype, EL_STR("LocalDateTime"))) {
      add_localdt_name(name);
    }
    if (str_eq(ltype, EL_STR("Zone"))) {
      add_zone_name(name);
    }
    if (is_instant_expr(val)) {
      add_instant_name(name);
    }
    if (is_duration_expr(val)) {
      add_duration_name(name);
    }
    if (is_calendar_expr(val)) {
      add_calendar_name(name);
    }
    if (is_caltime_expr(val)) {
      add_caltime_name(name);
    }
    if (is_rhythm_expr(val)) {
      add_rhythm_name(name);
    }
    if (is_localdate_expr(val)) {
      add_localdate_name(name);
    }
    if (is_localtime_expr(val)) {
      add_localtime_name(name);
    }
    if (is_localdt_expr(val)) {
      add_localdt_name(name);
    }
    if (is_zone_expr(val)) {
      add_zone_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;
    } else {
      emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("el_val_t ")), name), EL_STR(" = ")), val_c), EL_STR(";")));
      return native_list_append(declared, name);
    }
  }
  if (str_eq(kind, EL_STR("Return"))) {
    el_val_t val = el_get_field(stmt, EL_STR("value"));
    el_val_t val_kind = el_get_field(val, EL_STR("expr"));
    if (str_eq(val_kind, EL_STR("Nil"))) {
      emit_line(el_str_concat(indent, EL_STR("return 0;")));
    } else {
      el_val_t val_c = cg_expr(val);
      emit_line(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("return ")), val_c), EL_STR(";")));
    }
    return declared;
  }
  if (str_eq(kind, EL_STR("Assign"))) {
    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);
    emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(indent, name), EL_STR(" = ")), val_c), EL_STR(";")));
    return declared;
  }
  if (str_eq(kind, EL_STR("Expr"))) {
    el_val_t val = el_get_field(stmt, EL_STR("value"));
    el_val_t val_kind = el_get_field(val, EL_STR("expr"));
    if (str_eq(val_kind, EL_STR("If"))) {
      cg_if_stmt(val, indent, declared);
      return declared;
    }
    if (str_eq(val_kind, EL_STR("For"))) {
      cg_for_stmt(val, indent, declared);
      return declared;
    }
    el_val_t val_c = cg_expr(val);
    emit_line(el_str_concat(el_str_concat(indent, val_c), EL_STR(";")));
    return declared;
  }
  if (str_eq(kind, EL_STR("While"))) {
    el_val_t cond = el_get_field(stmt, EL_STR("cond"));
    el_val_t body = el_get_field(stmt, EL_STR("body"));
    el_val_t cond_c = cg_expr(cond);
    cond_c = strip_outer_parens(cond_c);
    emit_line(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("while (")), cond_c), EL_STR(") {")));
    cg_stmts(body, el_str_concat(indent, EL_STR("  ")), native_list_clone(declared));
    emit_line(el_str_concat(indent, EL_STR("}")));
    return declared;
  }
  if (str_eq(kind, EL_STR("For"))) {
    el_val_t item = el_get_field(stmt, EL_STR("item"));
    el_val_t list_expr = el_get_field(stmt, EL_STR("list"));
    el_val_t body = el_get_field(stmt, EL_STR("body"));
    cg_for_body(item, list_expr, body, indent, declared);
    return declared;
  }
  if (str_eq(kind, EL_STR("FnDef"))) {
    return declared;
  }
  if (str_eq(kind, EL_STR("TypeDef"))) {
    return declared;
  }
  if (str_eq(kind, EL_STR("EnumDef"))) {
    return 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;
  }
  return declared;
  return 0;
}

el_val_t strip_outer_parens(el_val_t s) {
  el_val_t chars = native_string_chars(s);
  el_val_t n = native_list_len(chars);
  if (n < 2) {
    return s;
  }
  el_val_t first = native_list_get(chars, 0);
  el_val_t last = native_list_get(chars, (n - 1));
  if (str_eq(first, EL_STR("("))) {
    if (str_eq(last, EL_STR(")"))) {
      el_val_t depth = 1;
      el_val_t i = 1;
      el_val_t balanced = 1;
      while (i < (n - 1)) {
        el_val_t ch = native_list_get(chars, i);
        if (str_eq(ch, EL_STR("("))) {
          depth = (depth + 1);
        }
        if (str_eq(ch, EL_STR(")"))) {
          depth = (depth - 1);
          if (depth == 0) {
            balanced = 0;
            i = n;
          }
        }
        i = (i + 1);
      }
      if (balanced) {
        return str_slice(s, 1, (n - 1));
      }
    }
  }
  return s;
  return 0;
}

el_val_t cg_if_stmt(el_val_t expr, el_val_t indent, el_val_t declared) {
  el_val_t cond = el_get_field(expr, EL_STR("cond"));
  el_val_t then_stmts = el_get_field(expr, EL_STR("then"));
  el_val_t else_stmts = el_get_field(expr, EL_STR("else"));
  el_val_t has_else = el_get_field(expr, EL_STR("has_else"));
  el_val_t cond_c = cg_expr(cond);
  cond_c = strip_outer_parens(cond_c);
  emit_line(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("if (")), cond_c), EL_STR(") {")));
  cg_stmts(then_stmts, el_str_concat(indent, EL_STR("  ")), native_list_clone(declared));
  if (has_else) {
    emit_line(el_str_concat(indent, EL_STR("} else {")));
    cg_stmts(else_stmts, el_str_concat(indent, EL_STR("  ")), native_list_clone(declared));
  }
  emit_line(el_str_concat(indent, EL_STR("}")));
  return 0;
}

el_val_t cg_for_body(el_val_t item, el_val_t list_expr, el_val_t body, el_val_t indent, el_val_t declared) {
  el_val_t list_c = cg_expr(list_expr);
  el_val_t idx = EL_STR("_el_i");
  el_val_t list_tmp = EL_STR("_el_lst");
  el_val_t len_tmp = EL_STR("_el_len");
  emit_line(el_str_concat(indent, EL_STR("{")));
  emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("  el_val_t ")), list_tmp), EL_STR(" = ")), list_c), EL_STR(";")));
  emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("  el_val_t ")), len_tmp), EL_STR(" = el_list_len(")), list_tmp), EL_STR(");")));
  emit_line(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(indent, EL_STR("  for (el_val_t ")), idx), EL_STR(" = 0; ")), idx), EL_STR(" < ")), len_tmp), EL_STR("; ")), idx), EL_STR("++) {")));
  emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("    el_val_t ")), item), EL_STR(" = el_list_get(")), list_tmp), EL_STR(", ")), idx), EL_STR(");")));
  el_val_t body_decl = native_list_clone(declared);
  body_decl = native_list_append(body_decl, item);
  cg_stmts(body, el_str_concat(indent, EL_STR("    ")), body_decl);
  emit_line(el_str_concat(indent, EL_STR("  }")));
  emit_line(el_str_concat(indent, EL_STR("}")));
  return 0;
}

el_val_t cg_for_stmt(el_val_t expr, el_val_t indent, el_val_t declared) {
  el_val_t item = el_get_field(expr, EL_STR("item"));
  el_val_t list_expr = el_get_field(expr, EL_STR("list"));
  el_val_t body = el_get_field(expr, EL_STR("body"));
  cg_for_body(item, list_expr, body, indent, declared);
  return 0;
}

el_val_t cg_stmts(el_val_t stmts, el_val_t indent, el_val_t declared) {
  el_val_t n = native_list_len(stmts);
  el_val_t i = 0;
  el_val_t decl = declared;
  while (i < n) {
    el_val_t stmt = native_list_get(stmts, i);
    decl = cg_stmt(stmt, indent, decl);
    i = (i + 1);
  }
  return decl;
  return 0;
}

el_val_t param_decl(el_val_t param, el_val_t idx) {
  el_val_t name = el_get_field(param, EL_STR("name"));
  return el_str_concat(EL_STR("el_val_t "), name);
  return 0;
}

el_val_t params_to_c(el_val_t params) {
  el_val_t n = native_list_len(params);
  if (n == 0) {
    return EL_STR("void");
  }
  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);
    parts = native_list_append(parts, decl);
    i = (i + 1);
  }
  return str_join(parts, EL_STR(", "));
  return 0;
}

el_val_t transform_implicit_return(el_val_t body) {
  el_val_t n = native_list_len(body);
  if (n == 0) {
    return body;
  }
  el_val_t last = native_list_get(body, (n - 1));
  el_val_t last_kind = el_get_field(last, EL_STR("stmt"));
  if (str_eq(last_kind, EL_STR("Expr"))) {
    el_val_t val = el_get_field(last, EL_STR("value"));
    el_val_t val_kind = el_get_field(val, EL_STR("expr"));
    if (str_eq(val_kind, EL_STR("If"))) {
      return body;
    }
    if (str_eq(val_kind, EL_STR("For"))) {
      return body;
    }
    el_val_t new_body = native_list_empty();
    el_val_t i = 0;
    while (i < (n - 1)) {
      new_body = native_list_append(new_body, native_list_get(body, i));
      i = (i + 1);
    }
    el_val_t return_stmt = el_map_new(2, "stmt", EL_STR("Return"), "value", val);
    new_body = native_list_append(new_body, return_stmt);
    return new_body;
  }
  return 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 is_instant_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__instant_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 is_duration_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__duration_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 is_int_call(el_val_t call_expr) {
  el_val_t func = el_get_field(call_expr, EL_STR("func"));
  el_val_t fk = el_get_field(func, EL_STR("expr"));
  if (!str_eq(fk, EL_STR("Ident"))) {
    return 0;
  }
  el_val_t name = el_get_field(func, EL_STR("name"));
  if (str_eq(name, EL_STR("str_len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_index_of"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_to_int"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_char_code"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count_chars"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count_bytes"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count_lines"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count_words"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count_letters"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count_digits"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_last_index_of"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_find_chars"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("native_list_len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_list_len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("json_get_int"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("json_array_len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("engram_node_count"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("engram_edge_count"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("time_now"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("time_now_utc"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("time_diff"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("time_add"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("time_from_parts"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_abs"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_max"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_min"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("float_to_int"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("unix_timestamp"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("instant_to_unix_seconds"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("instant_to_unix_millis"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("duration_to_seconds"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("duration_to_millis"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("duration_to_nanos"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_instant_call(el_val_t call_expr) {
  el_val_t func = el_get_field(call_expr, EL_STR("func"));
  el_val_t fk = el_get_field(func, EL_STR("expr"));
  if (!str_eq(fk, EL_STR("Ident"))) {
    return 0;
  }
  el_val_t name = el_get_field(func, EL_STR("name"));
  if (str_eq(name, EL_STR("now"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_now_instant"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("unix_seconds"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("unix_millis"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("instant_from_iso8601"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_instant_add_dur"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_instant_sub_dur"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_duration_call(el_val_t call_expr) {
  el_val_t func = el_get_field(call_expr, EL_STR("func"));
  el_val_t fk = el_get_field(func, EL_STR("expr"));
  if (!str_eq(fk, EL_STR("Ident"))) {
    return 0;
  }
  el_val_t name = el_get_field(func, EL_STR("name"));
  if (str_eq(name, EL_STR("el_duration_from_nanos"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("duration_seconds"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("duration_millis"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("duration_nanos"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_instant_diff"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_duration_add"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_duration_sub"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_duration_scale"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_duration_div"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("ttl_cache_age"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_calendar_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__calendar_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 is_caltime_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__caltime_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 is_rhythm_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__rhythm_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 is_localdate_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__localdate_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 is_localtime_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__localtime_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 is_localdt_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__localdt_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 is_zone_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__zone_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 is_calendar_call(el_val_t call_expr) {
  el_val_t func = el_get_field(call_expr, EL_STR("func"));
  el_val_t fk = el_get_field(func, EL_STR("expr"));
  if (!str_eq(fk, EL_STR("Ident"))) {
    return 0;
  }
  el_val_t name = el_get_field(func, EL_STR("name"));
  if (str_eq(name, EL_STR("earth_calendar"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("earth_calendar_default"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("mars_calendar"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("cycle_calendar"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("no_cycle_calendar"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("relative_calendar"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_caltime_call(el_val_t call_expr) {
  el_val_t func = el_get_field(call_expr, EL_STR("func"));
  el_val_t fk = el_get_field(func, EL_STR("expr"));
  if (!str_eq(fk, EL_STR("Ident"))) {
    return 0;
  }
  el_val_t name = el_get_field(func, EL_STR("name"));
  if (str_eq(name, EL_STR("now_in"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("in_calendar"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("cal_in"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("zoned"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_rhythm_call(el_val_t call_expr) {
  el_val_t func = el_get_field(call_expr, EL_STR("func"));
  el_val_t fk = el_get_field(func, EL_STR("expr"));
  if (!str_eq(fk, EL_STR("Ident"))) {
    return 0;
  }
  el_val_t name = el_get_field(func, EL_STR("name"));
  if (str_eq(name, EL_STR("rhythm_cycle_start"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("rhythm_cycle_phase"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("rhythm_duration"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("rhythm_session_start"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("rhythm_event"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("rhythm_and"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("rhythm_or"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("rhythm_weekday"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("rhythm_weekly_at"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_localdate_call(el_val_t call_expr) {
  el_val_t func = el_get_field(call_expr, EL_STR("func"));
  el_val_t fk = el_get_field(func, EL_STR("expr"));
  if (!str_eq(fk, EL_STR("Ident"))) {
    return 0;
  }
  el_val_t name = el_get_field(func, EL_STR("name"));
  if (str_eq(name, EL_STR("local_date"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_local_date_add_dur"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_localtime_call(el_val_t call_expr) {
  el_val_t func = el_get_field(call_expr, EL_STR("func"));
  el_val_t fk = el_get_field(func, EL_STR("expr"));
  if (!str_eq(fk, EL_STR("Ident"))) {
    return 0;
  }
  el_val_t name = el_get_field(func, EL_STR("name"));
  if (str_eq(name, EL_STR("local_time"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_local_time_add_dur"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_localdt_call(el_val_t call_expr) {
  el_val_t func = el_get_field(call_expr, EL_STR("func"));
  el_val_t fk = el_get_field(func, EL_STR("expr"));
  if (!str_eq(fk, EL_STR("Ident"))) {
    return 0;
  }
  el_val_t name = el_get_field(func, EL_STR("name"));
  if (str_eq(name, EL_STR("local_datetime"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_zone_call(el_val_t call_expr) {
  el_val_t func = el_get_field(call_expr, EL_STR("func"));
  el_val_t fk = el_get_field(func, EL_STR("expr"));
  if (!str_eq(fk, EL_STR("Ident"))) {
    return 0;
  }
  el_val_t name = el_get_field(func, EL_STR("name"));
  if (str_eq(name, EL_STR("zone"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("zone_utc"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("zone_local"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("zone_offset"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_calendar_expr(el_val_t expr) {
  el_val_t k = el_get_field(expr, EL_STR("expr"));
  if (str_eq(k, EL_STR("Ident"))) {
    return is_calendar_name(el_get_field(expr, EL_STR("name")));
  }
  if (str_eq(k, EL_STR("Call"))) {
    return is_calendar_call(expr);
  }
  return 0;
  return 0;
}

el_val_t is_caltime_expr(el_val_t expr) {
  el_val_t k = el_get_field(expr, EL_STR("expr"));
  if (str_eq(k, EL_STR("Ident"))) {
    return is_caltime_name(el_get_field(expr, EL_STR("name")));
  }
  if (str_eq(k, EL_STR("Call"))) {
    return is_caltime_call(expr);
  }
  return 0;
  return 0;
}

el_val_t is_rhythm_expr(el_val_t expr) {
  el_val_t k = el_get_field(expr, EL_STR("expr"));
  if (str_eq(k, EL_STR("Ident"))) {
    return is_rhythm_name(el_get_field(expr, EL_STR("name")));
  }
  if (str_eq(k, EL_STR("Call"))) {
    return is_rhythm_call(expr);
  }
  return 0;
  return 0;
}

el_val_t is_localdate_expr(el_val_t expr) {
  el_val_t k = el_get_field(expr, EL_STR("expr"));
  if (str_eq(k, EL_STR("Ident"))) {
    return is_localdate_name(el_get_field(expr, EL_STR("name")));
  }
  if (str_eq(k, EL_STR("Call"))) {
    return is_localdate_call(expr);
  }
  return 0;
  return 0;
}

el_val_t is_localtime_expr(el_val_t expr) {
  el_val_t k = el_get_field(expr, EL_STR("expr"));
  if (str_eq(k, EL_STR("Ident"))) {
    return is_localtime_name(el_get_field(expr, EL_STR("name")));
  }
  if (str_eq(k, EL_STR("Call"))) {
    return is_localtime_call(expr);
  }
  return 0;
  return 0;
}

el_val_t is_localdt_expr(el_val_t expr) {
  el_val_t k = el_get_field(expr, EL_STR("expr"));
  if (str_eq(k, EL_STR("Ident"))) {
    return is_localdt_name(el_get_field(expr, EL_STR("name")));
  }
  if (str_eq(k, EL_STR("Call"))) {
    return is_localdt_call(expr);
  }
  return 0;
  return 0;
}

el_val_t is_zone_expr(el_val_t expr) {
  el_val_t k = el_get_field(expr, EL_STR("expr"));
  if (str_eq(k, EL_STR("Ident"))) {
    return is_zone_name(el_get_field(expr, EL_STR("name")));
  }
  if (str_eq(k, EL_STR("Call"))) {
    return is_zone_call(expr);
  }
  return 0;
  return 0;
}

el_val_t is_instant_expr(el_val_t expr) {
  el_val_t k = el_get_field(expr, EL_STR("expr"));
  if (str_eq(k, EL_STR("Ident"))) {
    el_val_t name = el_get_field(expr, EL_STR("name"));
    return is_instant_name(name);
  }
  if (str_eq(k, EL_STR("Call"))) {
    return is_instant_call(expr);
  }
  if (str_eq(k, EL_STR("BinOp"))) {
    el_val_t op = el_get_field(expr, EL_STR("op"));
    if (str_eq(op, EL_STR("Plus"))) {
      if (is_instant_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_duration_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      if (is_duration_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_instant_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      return 0;
    }
    if (str_eq(op, EL_STR("Minus"))) {
      if (is_instant_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_duration_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      return 0;
    }
    return 0;
  }
  return 0;
  return 0;
}

el_val_t is_duration_expr(el_val_t expr) {
  el_val_t k = el_get_field(expr, EL_STR("expr"));
  if (str_eq(k, EL_STR("DurationLit"))) {
    return 1;
  }
  if (str_eq(k, EL_STR("Ident"))) {
    el_val_t name = el_get_field(expr, EL_STR("name"));
    return is_duration_name(name);
  }
  if (str_eq(k, EL_STR("Call"))) {
    return is_duration_call(expr);
  }
  if (str_eq(k, EL_STR("Neg"))) {
    return is_duration_expr(el_get_field(expr, EL_STR("inner")));
  }
  if (str_eq(k, EL_STR("BinOp"))) {
    el_val_t op = el_get_field(expr, EL_STR("op"));
    if (str_eq(op, EL_STR("Plus"))) {
      if (is_duration_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_duration_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      return 0;
    }
    if (str_eq(op, EL_STR("Minus"))) {
      if (is_duration_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_duration_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      if (is_instant_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_instant_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      return 0;
    }
    if (str_eq(op, EL_STR("Star"))) {
      if (is_duration_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_int_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      if (is_int_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_duration_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      return 0;
    }
    if (str_eq(op, EL_STR("Slash"))) {
      if (is_duration_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_int_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      return 0;
    }
    return 0;
  }
  return 0;
  return 0;
}

el_val_t time_record_violation(el_val_t kind, el_val_t detail) {
  el_val_t csv = state_get(EL_STR("__time_violations"));
  if (str_eq(csv, EL_STR(""))) {
    csv = EL_STR(",");
  }
  el_val_t entry = el_str_concat(el_str_concat(kind, EL_STR(":")), detail);
  el_val_t key = el_str_concat(el_str_concat(EL_STR(","), entry), EL_STR(","));
  if (str_contains(csv, key)) {
    return 1;
  }
  state_set(EL_STR("__time_violations"), el_str_concat(el_str_concat(csv, entry), EL_STR(",")));
  return 1;
  return 0;
}

el_val_t is_int_expr(el_val_t expr) {
  el_val_t k = el_get_field(expr, EL_STR("expr"));
  if (str_eq(k, EL_STR("Int"))) {
    return 1;
  }
  if (str_eq(k, EL_STR("Ident"))) {
    el_val_t name = el_get_field(expr, EL_STR("name"));
    return is_int_name(name);
  }
  if (str_eq(k, EL_STR("Call"))) {
    return is_int_call(expr);
  }
  if (str_eq(k, EL_STR("Neg"))) {
    return is_int_expr(el_get_field(expr, EL_STR("inner")));
  }
  if (str_eq(k, EL_STR("Not"))) {
    return 1;
  }
  if (str_eq(k, EL_STR("BinOp"))) {
    el_val_t op = el_get_field(expr, EL_STR("op"));
    if (str_eq(op, EL_STR("EqEq"))) {
      return 1;
    }
    if (str_eq(op, EL_STR("NotEq"))) {
      return 1;
    }
    if (str_eq(op, EL_STR("Lt"))) {
      return 1;
    }
    if (str_eq(op, EL_STR("Gt"))) {
      return 1;
    }
    if (str_eq(op, EL_STR("LtEq"))) {
      return 1;
    }
    if (str_eq(op, EL_STR("GtEq"))) {
      return 1;
    }
    if (str_eq(op, EL_STR("And"))) {
      return 1;
    }
    if (str_eq(op, EL_STR("Or"))) {
      return 1;
    }
    if (str_eq(op, EL_STR("Plus"))) {
      if (is_int_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_int_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      return 0;
    }
    if (str_eq(op, EL_STR("Minus"))) {
      if (is_int_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_int_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      return 0;
    }
    if (str_eq(op, EL_STR("Star"))) {
      if (is_int_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_int_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      return 0;
    }
    if (str_eq(op, EL_STR("Slash"))) {
      if (is_int_expr(el_get_field(expr, EL_STR("left")))) {
        if (is_int_expr(el_get_field(expr, EL_STR("right")))) {
          return 1;
        }
      }
      return 0;
    }
    return 0;
  }
  return 0;
  return 0;
}

el_val_t cap_record_violation(el_val_t kind, el_val_t fn_name) {
  el_val_t csv = state_get(EL_STR("__cap_violations"));
  if (str_eq(csv, EL_STR(""))) {
    csv = EL_STR(",");
  }
  el_val_t entry = el_str_concat(el_str_concat(kind, EL_STR(":")), fn_name);
  el_val_t key = el_str_concat(el_str_concat(EL_STR(","), entry), EL_STR(","));
  if (str_contains(csv, key)) {
    return 1;
  }
  state_set(EL_STR("__cap_violations"), el_str_concat(el_str_concat(csv, entry), EL_STR(",")));
  return 1;
  return 0;
}

el_val_t is_self_formation_call(el_val_t fn_name) {
  if (str_eq(fn_name, EL_STR("llm_call_agentic"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("llm_register_tool"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("dharma_emit"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("dharma_field"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_dharma_call(el_val_t fn_name) {
  if (str_eq(fn_name, EL_STR("dharma_connect"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("dharma_send"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("dharma_activate"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("dharma_emit"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("dharma_field"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("dharma_strengthen"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("dharma_relationship"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("dharma_peers"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_llm_call(el_val_t fn_name) {
  if (str_eq(fn_name, EL_STR("llm_call"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("llm_call_system"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("llm_call_agentic"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("llm_vision"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("llm_register_tool"))) {
    return 1;
  }
  if (str_eq(fn_name, EL_STR("llm_models"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t cap_check_call(el_val_t fn_name) {
  el_val_t kind = state_get(EL_STR("__program_kind"));
  if (str_eq(kind, EL_STR("cgi"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("service"))) {
    if (is_self_formation_call(fn_name)) {
      cap_record_violation(EL_STR("service"), fn_name);
      return 0;
    }
    return 1;
  }
  if (is_dharma_call(fn_name)) {
    cap_record_violation(EL_STR("utility"), fn_name);
    return 0;
  }
  if (is_llm_call(fn_name)) {
    cap_record_violation(EL_STR("utility"), fn_name);
    return 0;
  }
  return 1;
  return 0;
}

el_val_t emit_cap_violations(void) {
  el_val_t csv = state_get(EL_STR("__cap_violations"));
  if (str_eq(csv, EL_STR(""))) {
    return 0;
  }
  if (str_eq(csv, EL_STR(","))) {
    return 0;
  }
  el_val_t n = str_len(csv);
  el_val_t i = 1;
  while (i < n) {
    el_val_t next_comma = str_index_of(str_slice(csv, i, n), EL_STR(","));
    if (next_comma < 0) {
      return 0;
    }
    el_val_t entry = str_slice(csv, i, (i + next_comma));
    el_val_t colon = str_index_of(entry, EL_STR(":"));
    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)\"")));
    }
    i = ((i + next_comma) + 1);
  }
  return 0;
}

el_val_t emit_time_violations(void) {
  el_val_t csv = state_get(EL_STR("__time_violations"));
  if (str_eq(csv, EL_STR(""))) {
    return 0;
  }
  if (str_eq(csv, EL_STR(","))) {
    return 0;
  }
  el_val_t n = str_len(csv);
  el_val_t i = 1;
  while (i < n) {
    el_val_t next_comma = str_index_of(str_slice(csv, i, n), EL_STR(","));
    if (next_comma < 0) {
      return 0;
    }
    el_val_t entry = str_slice(csv, i, (i + next_comma));
    el_val_t colon = str_index_of(entry, EL_STR(":"));
    if (colon > 0) {
      el_val_t detail = str_slice(entry, (colon + 1), str_len(entry));
      emit_line(el_str_concat(el_str_concat(EL_STR("#error \"temporal type error: "), detail), EL_STR("\"")));
    }
    i = ((i + next_comma) + 1);
  }
  return 0;
}

el_val_t builtin_arity(el_val_t name) {
  if (str_eq(name, EL_STR("println"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("print"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("readline"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("el_str_concat"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_eq"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_starts_with"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_ends_with"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_concat"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("int_to_str"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_to_int"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_slice"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("str_contains"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_replace"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("str_to_upper"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_to_lower"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_trim"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_index_of"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_split"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_char_at"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_char_code"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_pad_left"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("str_pad_right"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("str_format"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_lower"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_upper"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_count_chars"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count_bytes"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count_lines"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count_words"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count_letters"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_count_digits"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_index_of_all"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_last_index_of"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_find_chars"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_repeat"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_reverse"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_strip_prefix"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_strip_suffix"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_strip_chars"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_lstrip"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_rstrip"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("is_letter"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("is_digit"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("is_alphanumeric"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("is_whitespace"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("is_punctuation"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("is_uppercase"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("is_lowercase"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_split_lines"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_split_chars"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_split_n"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("str_join"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("el_html_sanitize"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("el_abs"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_max"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("el_min"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("el_list_len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_list_get"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("el_list_append"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("el_list_empty"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("el_list_clone"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("list_push"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("list_push_front"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("list_join"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("list_range"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("el_get_field"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("el_map_get"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("el_map_set"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("http_get"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("http_post"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("http_post_json"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("http_get_with_headers"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("http_post_with_headers"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("http_post_form_auth"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("http_serve"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("http_set_handler"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("fs_read"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("fs_write"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("fs_list"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("json_get"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("json_parse"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("json_stringify"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("json_get_string"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("json_get_int"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("json_get_float"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("json_get_bool"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("json_get_raw"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("json_set"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("json_array_len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("time_now"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("time_now_utc"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("sleep_secs"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("sleep_ms"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("time_format"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("time_to_parts"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("time_from_parts"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("time_add"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("time_diff"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("uuid_new"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("uuid_v4"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("env"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("state_set"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("state_get"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("state_del"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("state_keys"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("float_to_str"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("int_to_float"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("float_to_int"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("format_float"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("decimal_round"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("str_to_float"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("math_sqrt"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("math_log"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("math_ln"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("math_sin"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("math_cos"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("math_pi"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("bool_to_str"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("exit_program"))) {
    return 1;
  }
  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;
  }
  if (str_eq(name, EL_STR("dharma_send"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("dharma_activate"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("dharma_emit"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("dharma_field"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("dharma_strengthen"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("dharma_relationship"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("dharma_peers"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("engram_node"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("engram_node_full"))) {
    return 8;
  }
  if (str_eq(name, EL_STR("engram_get_node"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("engram_strengthen"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("engram_forget"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("engram_node_count"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("engram_search"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("engram_scan_nodes"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("engram_connect"))) {
    return 4;
  }
  if (str_eq(name, EL_STR("engram_edge_between"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("engram_neighbors"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("engram_neighbors_filtered"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("engram_edge_count"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("engram_activate"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("engram_save"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("engram_load"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("engram_get_node_json"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("engram_search_json"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("engram_scan_nodes_json"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("engram_neighbors_json"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("engram_activate_json"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("engram_stats_json"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("llm_call"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("llm_call_system"))) {
    return 3;
  }
  if (str_eq(name, EL_STR("llm_call_agentic"))) {
    return 4;
  }
  if (str_eq(name, EL_STR("llm_vision"))) {
    return 4;
  }
  if (str_eq(name, EL_STR("llm_models"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("llm_register_tool"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("sha256_hex"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("sha256_bytes"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("hmac_sha256_hex"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("hmac_sha256_bytes"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("base64_encode"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("base64_decode"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("base64url_encode"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("base64url_decode"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("native_list_get"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("native_list_len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("native_list_append"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("native_list_empty"))) {
    return 0;
  }
  if (str_eq(name, EL_STR("native_list_clone"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("native_string_chars"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("native_int_to_str"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("append"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("get"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("map_get"))) {
    return 2;
  }
  if (str_eq(name, EL_STR("map_set"))) {
    return 3;
  }
  return (-1);
  return 0;
}

el_val_t arity_record_violation(el_val_t fn_name, el_val_t expected, el_val_t actual) {
  el_val_t csv = state_get(EL_STR("__arity_violations"));
  if (str_eq(csv, EL_STR(""))) {
    csv = EL_STR(",");
  }
  el_val_t entry = el_str_concat(el_str_concat(el_str_concat(el_str_concat(fn_name, EL_STR("|")), native_int_to_str(expected)), EL_STR("|")), native_int_to_str(actual));
  el_val_t key = el_str_concat(el_str_concat(EL_STR(","), entry), EL_STR(","));
  if (str_contains(csv, key)) {
    return 1;
  }
  state_set(EL_STR("__arity_violations"), el_str_concat(el_str_concat(csv, entry), EL_STR(",")));
  return 1;
  return 0;
}

el_val_t arity_check_call(el_val_t fn_name, el_val_t actual) {
  el_val_t expected = builtin_arity(fn_name);
  if (expected < 0) {
    return 1;
  }
  if (expected == actual) {
    return 1;
  }
  arity_record_violation(fn_name, expected, actual);
  return 1;
  return 0;
}

el_val_t emit_arity_violations(void) {
  el_val_t csv = state_get(EL_STR("__arity_violations"));
  if (str_eq(csv, EL_STR(""))) {
    return 0;
  }
  if (str_eq(csv, EL_STR(","))) {
    return 0;
  }
  el_val_t n = str_len(csv);
  el_val_t i = 1;
  while (i < n) {
    el_val_t next_comma = str_index_of(str_slice(csv, i, n), EL_STR(","));
    if (next_comma < 0) {
      return 0;
    }
    el_val_t entry = str_slice(csv, i, (i + next_comma));
    el_val_t p1 = str_index_of(entry, EL_STR("|"));
    if (p1 > 0) {
      el_val_t fn_name = str_slice(entry, 0, p1);
      el_val_t rest = str_slice(entry, (p1 + 1), str_len(entry));
      el_val_t p2 = str_index_of(rest, EL_STR("|"));
      if (p2 > 0) {
        el_val_t exp_s = str_slice(rest, 0, p2);
        el_val_t act_s = str_slice(rest, (p2 + 1), str_len(rest));
        emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("#error \"arity error: '"), fn_name), EL_STR("' takes ")), exp_s), EL_STR(" arguments, but called with ")), act_s), EL_STR("\"")));
      }
    }
    i = ((i + next_comma) + 1);
  }
  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_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 add_instant_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__instant_names"));
  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("__instant_names"), el_str_concat(el_str_concat(csv, name), EL_STR(",")));
  return 1;
  return 0;
}

el_val_t add_duration_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__duration_names"));
  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("__duration_names"), el_str_concat(el_str_concat(csv, name), EL_STR(",")));
  return 1;
  return 0;
}

el_val_t add_calendar_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__calendar_names"));
  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("__calendar_names"), el_str_concat(el_str_concat(csv, name), EL_STR(",")));
  return 1;
  return 0;
}

el_val_t add_caltime_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__caltime_names"));
  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("__caltime_names"), el_str_concat(el_str_concat(csv, name), EL_STR(",")));
  return 1;
  return 0;
}

el_val_t add_rhythm_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__rhythm_names"));
  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("__rhythm_names"), el_str_concat(el_str_concat(csv, name), EL_STR(",")));
  return 1;
  return 0;
}

el_val_t add_localdate_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__localdate_names"));
  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("__localdate_names"), el_str_concat(el_str_concat(csv, name), EL_STR(",")));
  return 1;
  return 0;
}

el_val_t add_localtime_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__localtime_names"));
  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("__localtime_names"), el_str_concat(el_str_concat(csv, name), EL_STR(",")));
  return 1;
  return 0;
}

el_val_t add_localdt_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__localdt_names"));
  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("__localdt_names"), el_str_concat(el_str_concat(csv, name), EL_STR(",")));
  return 1;
  return 0;
}

el_val_t add_zone_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__zone_names"));
  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("__zone_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(","));
  state_set(EL_STR("__instant_names"), EL_STR(","));
  state_set(EL_STR("__duration_names"), EL_STR(","));
  state_set(EL_STR("__calendar_names"), EL_STR(","));
  state_set(EL_STR("__caltime_names"), EL_STR(","));
  state_set(EL_STR("__rhythm_names"), EL_STR(","));
  state_set(EL_STR("__localdate_names"), EL_STR(","));
  state_set(EL_STR("__localtime_names"), EL_STR(","));
  state_set(EL_STR("__localdt_names"), EL_STR(","));
  state_set(EL_STR("__zone_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);
    }
    if (str_eq(ptype, EL_STR("Instant"))) {
      add_instant_name(pname);
    }
    if (str_eq(ptype, EL_STR("Duration"))) {
      add_duration_name(pname);
    }
    if (str_eq(ptype, EL_STR("Calendar"))) {
      add_calendar_name(pname);
    }
    if (str_eq(ptype, EL_STR("CalendarTime"))) {
      add_caltime_name(pname);
    }
    if (str_eq(ptype, EL_STR("Rhythm"))) {
      add_rhythm_name(pname);
    }
    if (str_eq(ptype, EL_STR("LocalDate"))) {
      add_localdate_name(pname);
    }
    if (str_eq(ptype, EL_STR("LocalTime"))) {
      add_localtime_name(pname);
    }
    if (str_eq(ptype, EL_STR("LocalDateTime"))) {
      add_localdt_name(pname);
    }
    if (str_eq(ptype, EL_STR("Zone"))) {
      add_zone_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"))) {
    return 0;
  }
  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 params_c = params_to_c(params);
  el_val_t decorator = el_get_field(stmt, EL_STR("decorator"));
  if (vbd_has_restricted_call(body)) {
    if (!str_eq(decorator, EL_STR("manager"))) {
      emit_line(el_str_concat(el_str_concat(EL_STR("#error \"VBD violation: dharma_emit/dharma_field called from non-@manager fn '"), fn_name), EL_STR("'\"")));
    }
  }
  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);
  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"));
    decl = native_list_append(decl, pname);
    pi = (pi + 1);
  }
  el_val_t body_xformed = body;
  if (!str_eq(ret_type, EL_STR("Void"))) {
    body_xformed = transform_implicit_return(body);
  }
  cg_stmts(body_xformed, EL_STR("  "), decl);
  emit_line(EL_STR("  return 0;"));
  emit_line(EL_STR("}"));
  emit_blank();
  return 0;
}

el_val_t is_fndef(el_val_t stmt) {
  el_val_t kind = el_get_field(stmt, EL_STR("stmt"));
  if (str_eq(kind, EL_STR("FnDef"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t is_top_level_decl(el_val_t stmt) {
  el_val_t kind = el_get_field(stmt, EL_STR("stmt"));
  if (str_eq(kind, EL_STR("TypeDef"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("EnumDef"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("Import"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("CgiBlock"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("ExternFn"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t cgi_arg(el_val_t value, el_val_t has_value) {
  if (has_value) {
    return el_str_concat(el_str_concat(EL_STR("EL_STR("), c_str_lit(value)), EL_STR(")"));
  }
  return EL_STR("EL_NULL");
  return 0;
}

el_val_t vbd_is_restricted_name(el_val_t name) {
  if (str_eq(name, EL_STR("dharma_emit"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("dharma_field"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t vbd_expr_has_restricted_call(el_val_t expr) {
  el_val_t kind = el_get_field(expr, EL_STR("expr"));
  if (str_eq(kind, EL_STR("Call"))) {
    el_val_t func = el_get_field(expr, EL_STR("func"));
    el_val_t fk = el_get_field(func, EL_STR("expr"));
    if (str_eq(fk, EL_STR("Ident"))) {
      el_val_t fname = el_get_field(func, EL_STR("name"));
      if (vbd_is_restricted_name(fname)) {
        return 1;
      }
    }
    if (vbd_expr_has_restricted_call(func)) {
      return 1;
    }
    el_val_t args = el_get_field(expr, EL_STR("args"));
    el_val_t an = native_list_len(args);
    el_val_t ai = 0;
    while (ai < an) {
      el_val_t a = native_list_get(args, ai);
      if (vbd_expr_has_restricted_call(a)) {
        return 1;
      }
      ai = (ai + 1);
    }
    return 0;
  }
  if (str_eq(kind, EL_STR("BinOp"))) {
    el_val_t l = el_get_field(expr, EL_STR("left"));
    el_val_t r = el_get_field(expr, EL_STR("right"));
    if (vbd_expr_has_restricted_call(l)) {
      return 1;
    }
    if (vbd_expr_has_restricted_call(r)) {
      return 1;
    }
    return 0;
  }
  if (str_eq(kind, EL_STR("Not"))) {
    return vbd_expr_has_restricted_call(el_get_field(expr, EL_STR("inner")));
  }
  if (str_eq(kind, EL_STR("Neg"))) {
    return vbd_expr_has_restricted_call(el_get_field(expr, EL_STR("inner")));
  }
  if (str_eq(kind, EL_STR("Field"))) {
    return vbd_expr_has_restricted_call(el_get_field(expr, EL_STR("object")));
  }
  if (str_eq(kind, EL_STR("Index"))) {
    if (vbd_expr_has_restricted_call(el_get_field(expr, EL_STR("object")))) {
      return 1;
    }
    if (vbd_expr_has_restricted_call(el_get_field(expr, EL_STR("index")))) {
      return 1;
    }
    return 0;
  }
  if (str_eq(kind, EL_STR("Try"))) {
    return vbd_expr_has_restricted_call(el_get_field(expr, EL_STR("inner")));
  }
  if (str_eq(kind, EL_STR("Array"))) {
    el_val_t elems = el_get_field(expr, EL_STR("elems"));
    el_val_t n = native_list_len(elems);
    el_val_t i = 0;
    while (i < n) {
      el_val_t e = native_list_get(elems, i);
      if (vbd_expr_has_restricted_call(e)) {
        return 1;
      }
      i = (i + 1);
    }
    return 0;
  }
  if (str_eq(kind, EL_STR("Map"))) {
    el_val_t pairs = el_get_field(expr, EL_STR("pairs"));
    el_val_t n = native_list_len(pairs);
    el_val_t i = 0;
    while (i < n) {
      el_val_t pair = native_list_get(pairs, i);
      el_val_t v = el_get_field(pair, EL_STR("value"));
      if (vbd_expr_has_restricted_call(v)) {
        return 1;
      }
      i = (i + 1);
    }
    return 0;
  }
  if (str_eq(kind, EL_STR("If"))) {
    if (vbd_expr_has_restricted_call(el_get_field(expr, EL_STR("cond")))) {
      return 1;
    }
    if (vbd_has_restricted_call(el_get_field(expr, EL_STR("then")))) {
      return 1;
    }
    if (vbd_has_restricted_call(el_get_field(expr, EL_STR("else")))) {
      return 1;
    }
    return 0;
  }
  if (str_eq(kind, EL_STR("For"))) {
    if (vbd_expr_has_restricted_call(el_get_field(expr, EL_STR("list")))) {
      return 1;
    }
    if (vbd_has_restricted_call(el_get_field(expr, EL_STR("body")))) {
      return 1;
    }
    return 0;
  }
  if (str_eq(kind, EL_STR("Match"))) {
    if (vbd_expr_has_restricted_call(el_get_field(expr, EL_STR("subject")))) {
      return 1;
    }
    el_val_t arms = el_get_field(expr, EL_STR("arms"));
    el_val_t n = native_list_len(arms);
    el_val_t i = 0;
    while (i < n) {
      el_val_t arm = native_list_get(arms, i);
      el_val_t body = el_get_field(arm, EL_STR("body"));
      if (vbd_expr_has_restricted_call(body)) {
        return 1;
      }
      i = (i + 1);
    }
    return 0;
  }
  return 0;
  return 0;
}

el_val_t vbd_has_restricted_call(el_val_t stmts) {
  el_val_t n = native_list_len(stmts);
  el_val_t i = 0;
  while (i < n) {
    el_val_t s = native_list_get(stmts, i);
    el_val_t sk = el_get_field(s, EL_STR("stmt"));
    if (str_eq(sk, EL_STR("Let"))) {
      if (vbd_expr_has_restricted_call(el_get_field(s, EL_STR("value")))) {
        return 1;
      }
    }
    if (str_eq(sk, EL_STR("Return"))) {
      if (vbd_expr_has_restricted_call(el_get_field(s, EL_STR("value")))) {
        return 1;
      }
    }
    if (str_eq(sk, EL_STR("Expr"))) {
      if (vbd_expr_has_restricted_call(el_get_field(s, EL_STR("value")))) {
        return 1;
      }
    }
    if (str_eq(sk, EL_STR("While"))) {
      if (vbd_expr_has_restricted_call(el_get_field(s, EL_STR("cond")))) {
        return 1;
      }
      if (vbd_has_restricted_call(el_get_field(s, EL_STR("body")))) {
        return 1;
      }
    }
    if (str_eq(sk, EL_STR("For"))) {
      if (vbd_expr_has_restricted_call(el_get_field(s, EL_STR("list")))) {
        return 1;
      }
      if (vbd_has_restricted_call(el_get_field(s, EL_STR("body")))) {
        return 1;
      }
    }
    i = (i + 1);
  }
  return 0;
  return 0;
}

el_val_t codegen(el_val_t stmts, el_val_t source) {
  el_val_t n_top = native_list_len(stmts);
  el_val_t cgi_count = 0;
  el_val_t cgi_block = el_map_new(1, "stmt", EL_STR("None"));
  el_val_t svc_count = 0;
  el_val_t svc_block = el_map_new(1, "stmt", EL_STR("None"));
  el_val_t ti = 0;
  while (ti < n_top) {
    el_val_t s = native_list_get(stmts, ti);
    el_val_t sk = el_get_field(s, EL_STR("stmt"));
    if (str_eq(sk, EL_STR("CgiBlock"))) {
      cgi_count = (cgi_count + 1);
      if (cgi_count == 1) {
        cgi_block = s;
      }
    }
    if (str_eq(sk, EL_STR("ServiceBlock"))) {
      svc_count = (svc_count + 1);
      if (svc_count == 1) {
        svc_block = s;
      }
    }
    ti = (ti + 1);
  }
  if (cgi_count > 1) {
    emit_line(EL_STR("#error \"El: multiple cgi blocks in program (only one allowed)\""));
  }
  if (svc_count > 1) {
    emit_line(EL_STR("#error \"El: multiple service blocks in program (only one allowed)\""));
  }
  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)\""));
    }
  }
  el_val_t kind = EL_STR("utility");
  if (cgi_count >= 1) {
    kind = EL_STR("cgi");
  }
  if (svc_count >= 1) {
    kind = EL_STR("service");
  }
  state_set(EL_STR("__program_kind"), kind);
  state_set(EL_STR("__cap_violations"), EL_STR(""));
  state_set(EL_STR("__arity_violations"), EL_STR(""));
  state_set(EL_STR("__time_violations"), EL_STR(""));
  emit_line(EL_STR("#include <stdint.h>"));
  emit_line(EL_STR("#include <stdlib.h>"));
  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;
  while (i < n) {
    el_val_t stmt = native_list_get(stmts, i);
    kind = el_get_field(stmt, EL_STR("stmt"));
    if (str_eq(kind, EL_STR("FnDef"))) {
      el_val_t fn_name = el_get_field(stmt, EL_STR("name"));
      if (!str_eq(fn_name, EL_STR("main"))) {
        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(");")));
      }
    }
    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();
  el_val_t has_toplevel_lets = 0;
  i = 0;
  while (i < n) {
    el_val_t stmt = native_list_get(stmts, i);
    kind = el_get_field(stmt, EL_STR("stmt"));
    if (str_eq(kind, EL_STR("Let"))) {
      el_val_t name = el_get_field(stmt, EL_STR("name"));
      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 val = el_get_field(stmt, EL_STR("value"));
      el_val_t vk = el_get_field(val, EL_STR("expr"));
      if (str_eq(vk, EL_STR("Int"))) {
        add_int_name(name);
      }
      emit_line(el_str_concat(el_str_concat(EL_STR("el_val_t "), name), EL_STR(";")));
      has_toplevel_lets = 1;
    }
    i = (i + 1);
  }
  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);
    if (is_fndef(stmt)) {
      cg_fn(stmt);
    }
    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) {
    el_val_t cname = el_get_field(cgi_block, EL_STR("name"));
    el_val_t cdid = el_get_field(cgi_block, EL_STR("dharma_id"));
    el_val_t cprin = el_get_field(cgi_block, EL_STR("principal"));
    el_val_t cnet = el_get_field(cgi_block, EL_STR("network"));
    el_val_t ceng = el_get_field(cgi_block, EL_STR("engram"));
    el_val_t has_did = el_get_field(cgi_block, EL_STR("has_dharma_id"));
    el_val_t has_prin = el_get_field(cgi_block, EL_STR("has_principal"));
    el_val_t has_net = el_get_field(cgi_block, EL_STR("has_network"));
    el_val_t has_eng = el_get_field(cgi_block, EL_STR("has_engram"));
    el_val_t arg_name = el_str_concat(el_str_concat(EL_STR("EL_STR("), c_str_lit(cname)), EL_STR(")"));
    el_val_t arg_did = cgi_arg(cdid, has_did);
    el_val_t arg_prin = cgi_arg(cprin, has_prin);
    el_val_t arg_net = cgi_arg(cnet, has_net);
    el_val_t arg_eng = cgi_arg(ceng, has_eng);
    emit_line(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_cgi_init("), arg_name), EL_STR(", ")), arg_did), EL_STR(", ")), arg_prin), EL_STR(", ")), arg_net), EL_STR(", ")), arg_eng), EL_STR(");")));
  }
  el_val_t main_decl = native_list_empty();
  i = 0;
  while (i < n) {
    el_val_t stmt = native_list_get(stmts, i);
    kind = el_get_field(stmt, EL_STR("stmt"));
    if (str_eq(kind, EL_STR("Let"))) {
      el_val_t name = el_get_field(stmt, EL_STR("name"));
      main_decl = native_list_append(main_decl, name);
    }
    i = (i + 1);
  }
  el_val_t el_main_body = native_list_empty();
  i = 0;
  while (i < n) {
    el_val_t stmt = native_list_get(stmts, i);
    if (is_fndef(stmt)) {
      el_val_t fn_name = el_get_field(stmt, EL_STR("name"));
      if (str_eq(fn_name, EL_STR("main"))) {
        el_val_t body = el_get_field(stmt, EL_STR("body"));
        el_val_t bn = native_list_len(body);
        el_val_t bi = 0;
        while (bi < bn) {
          el_main_body = native_list_append(el_main_body, native_list_get(body, bi));
          bi = (bi + 1);
        }
      }
    }
    i = (i + 1);
  }
  i = 0;
  while (i < n) {
    el_val_t stmt = native_list_get(stmts, i);
    if (is_fndef(stmt)) {
    } else {
      if (is_top_level_decl(stmt)) {
      } else {
        main_decl = cg_stmt(stmt, EL_STR("  "), main_decl);
      }
    }
    el_release(stmt);
    i = (i + 1);
  }
  el_val_t mn = native_list_len(el_main_body);
  el_val_t mi = 0;
  while (mi < mn) {
    el_val_t mstmt = native_list_get(el_main_body, mi);
    main_decl = cg_stmt(mstmt, EL_STR("  "), main_decl);
    mi = (mi + 1);
  }
  emit_line(EL_STR("  return 0;"));
  emit_line(EL_STR("}"));
  emit_blank();
  emit_cap_violations();
  emit_arity_violations();
  emit_time_violations();
  return EL_STR("");
  return 0;
}

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 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("\""))) {
      parts = native_list_append(parts, EL_STR("\\\""));
    } else {
      if (str_eq(ch, EL_STR("\\"))) {
        parts = native_list_append(parts, EL_STR("\\\\"));
      } else {
        if (str_eq(ch, EL_STR("\n"))) {
          parts = native_list_append(parts, EL_STR("\\n"));
        } else {
          if (str_eq(ch, EL_STR("\r"))) {
            parts = native_list_append(parts, EL_STR("\\r"));
          } else {
            if (str_eq(ch, EL_STR("\t"))) {
              parts = native_list_append(parts, EL_STR("\\t"));
            } else {
              parts = native_list_append(parts, ch);
            }
          }
        }
      }
    }
    i = (i + 1);
  }
  return str_join(parts, EL_STR(""));
  return 0;
}

el_val_t js_str_lit(el_val_t s) {
  return el_str_concat(el_str_concat(EL_STR("\""), js_escape(s)), EL_STR("\""));
  return 0;
}

el_val_t js_emit_line(el_val_t line) {
  println(line);
  return 0;
}

el_val_t js_emit_blank(void) {
  println(EL_STR(""));
  return 0;
}

el_val_t js_binop(el_val_t op) {
  if (str_eq(op, EL_STR("Plus"))) {
    return EL_STR("+");
  }
  if (str_eq(op, EL_STR("Minus"))) {
    return EL_STR("-");
  }
  if (str_eq(op, EL_STR("Star"))) {
    return EL_STR("*");
  }
  if (str_eq(op, EL_STR("Slash"))) {
    return EL_STR("/");
  }
  if (str_eq(op, EL_STR("Percent"))) {
    return EL_STR("%");
  }
  if (str_eq(op, EL_STR("EqEq"))) {
    return EL_STR("===");
  }
  if (str_eq(op, EL_STR("NotEq"))) {
    return EL_STR("!==");
  }
  if (str_eq(op, EL_STR("Lt"))) {
    return EL_STR("<");
  }
  if (str_eq(op, EL_STR("Gt"))) {
    return EL_STR(">");
  }
  if (str_eq(op, EL_STR("LtEq"))) {
    return EL_STR("<=");
  }
  if (str_eq(op, EL_STR("GtEq"))) {
    return EL_STR(">=");
  }
  if (str_eq(op, EL_STR("And"))) {
    return EL_STR("&&");
  }
  if (str_eq(op, EL_STR("Or"))) {
    return EL_STR("||");
  }
  return op;
  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(""))) {
    return 0;
  }
  return str_contains(csv, el_str_concat(el_str_concat(EL_STR(","), name), EL_STR(",")));
  return 0;
}

el_val_t js_add_int_name(el_val_t name) {
  el_val_t csv = state_get(EL_STR("__js_int_names"));
  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_int_names"), el_str_concat(el_str_concat(csv, name), EL_STR(",")));
  return 1;
  return 0;
}

el_val_t js_build_int_names_for_params(el_val_t params) {
  state_set(EL_STR("__js_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"))) {
      js_add_int_name(pname);
    }
    pi = (pi + 1);
  }
  return 1;
  return 0;
}

el_val_t js_is_int_call(el_val_t call_expr) {
  el_val_t func = el_get_field(call_expr, EL_STR("func"));
  el_val_t fk = el_get_field(func, EL_STR("expr"));
  if (!str_eq(fk, EL_STR("Ident"))) {
    return 0;
  }
  el_val_t name = el_get_field(func, EL_STR("name"));
  if (str_eq(name, EL_STR("str_len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_index_of"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_to_int"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("str_char_code"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("native_list_len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_list_len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("len"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("json_get_int"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("time_now"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("time_now_utc"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_abs"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_max"))) {
    return 1;
  }
  if (str_eq(name, EL_STR("el_min"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t js_cg_expr(el_val_t expr) {
  el_val_t kind = el_get_field(expr, EL_STR("expr"));
  if (str_eq(kind, EL_STR("Int"))) {
    el_val_t v = el_get_field(expr, EL_STR("value"));
    return v;
  }
  if (str_eq(kind, EL_STR("DurationLit"))) {
    el_val_t count = el_get_field(expr, EL_STR("count"));
    el_val_t unit = el_get_field(expr, EL_STR("unit"));
    el_val_t mult_ns = EL_STR("1");
    if (str_eq(unit, EL_STR("nano"))) {
      mult_ns = EL_STR("1");
    }
    if (str_eq(unit, EL_STR("nanos"))) {
      mult_ns = EL_STR("1");
    }
    if (str_eq(unit, EL_STR("milli"))) {
      mult_ns = EL_STR("1000000");
    }
    if (str_eq(unit, EL_STR("millis"))) {
      mult_ns = EL_STR("1000000");
    }
    if (str_eq(unit, EL_STR("millisecond"))) {
      mult_ns = EL_STR("1000000");
    }
    if (str_eq(unit, EL_STR("milliseconds"))) {
      mult_ns = EL_STR("1000000");
    }
    if (str_eq(unit, EL_STR("second"))) {
      mult_ns = EL_STR("1000000000");
    }
    if (str_eq(unit, EL_STR("seconds"))) {
      mult_ns = EL_STR("1000000000");
    }
    if (str_eq(unit, EL_STR("minute"))) {
      mult_ns = EL_STR("60000000000");
    }
    if (str_eq(unit, EL_STR("minutes"))) {
      mult_ns = EL_STR("60000000000");
    }
    if (str_eq(unit, EL_STR("hour"))) {
      mult_ns = EL_STR("3600000000000");
    }
    if (str_eq(unit, EL_STR("hours"))) {
      mult_ns = EL_STR("3600000000000");
    }
    if (str_eq(unit, EL_STR("day"))) {
      mult_ns = EL_STR("86400000000000");
    }
    if (str_eq(unit, EL_STR("days"))) {
      mult_ns = EL_STR("86400000000000");
    }
    return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), count), EL_STR(" * ")), mult_ns), EL_STR(")"));
  }
  if (str_eq(kind, EL_STR("Float"))) {
    el_val_t v = el_get_field(expr, EL_STR("value"));
    return v;
  }
  if (str_eq(kind, EL_STR("Str"))) {
    el_val_t v = el_get_field(expr, EL_STR("value"));
    return js_str_lit(v);
  }
  if (str_eq(kind, EL_STR("Bool"))) {
    el_val_t v = el_get_field(expr, EL_STR("value"));
    if (str_eq(v, EL_STR("true"))) {
      return EL_STR("true");
    }
    return EL_STR("false");
  }
  if (str_eq(kind, EL_STR("Nil"))) {
    return EL_STR("null");
  }
  if (str_eq(kind, EL_STR("Ident"))) {
    el_val_t name = el_get_field(expr, EL_STR("name"));
    return name;
  }
  if (str_eq(kind, EL_STR("Not"))) {
    el_val_t inner = el_get_field(expr, EL_STR("inner"));
    el_val_t inner_c = js_cg_expr(inner);
    return el_str_concat(EL_STR("!"), inner_c);
  }
  if (str_eq(kind, EL_STR("Neg"))) {
    el_val_t inner = el_get_field(expr, EL_STR("inner"));
    el_val_t inner_c = js_cg_expr(inner);
    return el_str_concat(el_str_concat(EL_STR("(-"), inner_c), EL_STR(")"));
  }
  if (str_eq(kind, EL_STR("BinOp"))) {
    el_val_t op = el_get_field(expr, EL_STR("op"));
    el_val_t left = el_get_field(expr, EL_STR("left"));
    el_val_t right = el_get_field(expr, EL_STR("right"));
    el_val_t left_c = js_cg_expr(left);
    el_val_t right_c = js_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(left_kind, EL_STR("Int"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" + ")), right_c), EL_STR(")"));
      }
      if (str_eq(right_kind, EL_STR("Int"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" + ")), right_c), EL_STR(")"));
      }
      if (str_eq(left_kind, EL_STR("Ident"))) {
        if (str_eq(right_kind, EL_STR("Ident"))) {
          el_val_t lname = el_get_field(left, EL_STR("name"));
          el_val_t rname = el_get_field(right, EL_STR("name"));
          if (js_is_int_name(lname)) {
            if (js_is_int_name(rname)) {
              return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" + ")), right_c), EL_STR(")"));
            }
          }
        }
      }
      if (str_eq(left_kind, EL_STR("Ident"))) {
        if (str_eq(right_kind, EL_STR("Call"))) {
          el_val_t lname = el_get_field(left, EL_STR("name"));
          if (js_is_int_name(lname)) {
            if (js_is_int_call(right)) {
              return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" + ")), right_c), EL_STR(")"));
            }
          }
        }
      }
      if (str_eq(right_kind, EL_STR("Ident"))) {
        if (str_eq(left_kind, EL_STR("Call"))) {
          el_val_t rname = el_get_field(right, EL_STR("name"));
          if (js_is_int_name(rname)) {
            if (js_is_int_call(left)) {
              return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" + ")), right_c), EL_STR(")"));
            }
          }
        }
      }
      if (str_eq(left_kind, EL_STR("Call"))) {
        if (str_eq(right_kind, EL_STR("Call"))) {
          if (js_is_int_call(left)) {
            if (js_is_int_call(right)) {
              return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" + ")), right_c), EL_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("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(")"));
      }
      if (str_eq(left_kind, EL_STR("Ident"))) {
        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("Ident"))) {
        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("Int"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" === ")), right_c), EL_STR(")"));
      }
      if (str_eq(right_kind, EL_STR("Int"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" === ")), right_c), EL_STR(")"));
      }
      if (str_eq(left_kind, EL_STR("Bool"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" === ")), right_c), EL_STR(")"));
      }
      if (str_eq(right_kind, EL_STR("Bool"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" === ")), right_c), EL_STR(")"));
      }
      if (str_eq(left_kind, EL_STR("Nil"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" === ")), right_c), EL_STR(")"));
      }
      if (str_eq(right_kind, EL_STR("Nil"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" === ")), right_c), EL_STR(")"));
      }
      if (str_eq(left_kind, EL_STR("Ident"))) {
        if (str_eq(right_kind, EL_STR("Ident"))) {
          el_val_t lname = el_get_field(left, EL_STR("name"));
          el_val_t rname = el_get_field(right, EL_STR("name"));
          if (js_is_int_name(lname)) {
            if (js_is_int_name(rname)) {
              return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" === ")), right_c), EL_STR(")"));
            }
          }
        }
      }
      if (str_eq(left_kind, EL_STR("Str"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("str_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
      }
      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(")"));
      }
      return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" === ")), right_c), EL_STR(")"));
    }
    if (str_eq(op, EL_STR("NotEq"))) {
      if (str_eq(left_kind, EL_STR("Int"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" !== ")), right_c), EL_STR(")"));
      }
      if (str_eq(right_kind, EL_STR("Int"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" !== ")), right_c), EL_STR(")"));
      }
      if (str_eq(left_kind, EL_STR("Bool"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" !== ")), right_c), EL_STR(")"));
      }
      if (str_eq(right_kind, EL_STR("Bool"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" !== ")), right_c), EL_STR(")"));
      }
      if (str_eq(left_kind, EL_STR("Nil"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" !== ")), right_c), EL_STR(")"));
      }
      if (str_eq(right_kind, EL_STR("Nil"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" !== ")), right_c), EL_STR(")"));
      }
      if (str_eq(left_kind, EL_STR("Ident"))) {
        if (str_eq(right_kind, EL_STR("Ident"))) {
          el_val_t lname = el_get_field(left, EL_STR("name"));
          el_val_t rname = el_get_field(right, EL_STR("name"));
          if (js_is_int_name(lname)) {
            if (js_is_int_name(rname)) {
              return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" !== ")), right_c), EL_STR(")"));
            }
          }
        }
      }
      if (str_eq(left_kind, EL_STR("Str"))) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("!str_eq("), left_c), EL_STR(", ")), right_c), EL_STR(")"));
      }
      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(")"));
      }
      return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("("), left_c), EL_STR(" !== ")), right_c), EL_STR(")"));
    }
    el_val_t op_c = js_binop(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(kind, EL_STR("Call"))) {
    el_val_t func = el_get_field(expr, EL_STR("func"));
    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_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);
      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(")"));
    }
    if (str_eq(func_kind, EL_STR("Field"))) {
      el_val_t obj = el_get_field(func, EL_STR("object"));
      el_val_t field = el_get_field(func, EL_STR("field"));
      el_val_t obj_c = js_cg_expr(obj);
      if (arity > 0) {
        return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(field, EL_STR("(")), obj_c), EL_STR(", ")), args_c), EL_STR(")"));
      }
      return el_str_concat(el_str_concat(el_str_concat(field, EL_STR("(")), obj_c), EL_STR(")"));
    }
    el_val_t fn_c = js_cg_expr(func);
    return el_str_concat(el_str_concat(el_str_concat(fn_c, EL_STR("(")), args_c), EL_STR(")"));
  }
  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_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(")"));
  }
  if (str_eq(kind, EL_STR("Index"))) {
    el_val_t obj = el_get_field(expr, EL_STR("object"));
    el_val_t idx = el_get_field(expr, EL_STR("index"));
    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"));
    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(")"));
    }
    return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("el_list_get("), obj_c), EL_STR(", ")), idx_c), EL_STR(")"));
  }
  if (str_eq(kind, EL_STR("Array"))) {
    el_val_t elems = el_get_field(expr, EL_STR("elems"));
    el_val_t n = native_list_len(elems);
    if (n == 0) {
      return EL_STR("[]");
    }
    el_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);
      items_parts = native_list_append(items_parts, elem_c);
      i = (i + 1);
    }
    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"));
    el_val_t n = native_list_len(pairs);
    if (n == 0) {
      return 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);
      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("{"), 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"));
    return js_cg_expr(inner);
  }
  if (str_eq(kind, EL_STR("If"))) {
    el_val_t cond = el_get_field(expr, EL_STR("cond"));
    el_val_t cond_c = js_cg_expr(cond);
    return el_str_concat(el_str_concat(EL_STR("("), cond_c), EL_STR(" ? 1 : 0)"));
  }
  if (str_eq(kind, EL_STR("Match"))) {
    return js_cg_match(expr);
  }
  return EL_STR("null");
  return 0;
}

el_val_t js_next_match_id(void) {
  el_val_t csv = state_get(EL_STR("__js_match_counter"));
  el_val_t n = 0;
  if (!str_eq(csv, EL_STR(""))) {
    n = str_to_int(csv);
  }
  n = (n + 1);
  state_set(EL_STR("__js_match_counter"), native_int_to_str(n));
  return native_int_to_str(n);
  return 0;
}

el_val_t js_cg_match(el_val_t expr) {
  el_val_t subject = el_get_field(expr, EL_STR("subject"));
  el_val_t arms = el_get_field(expr, EL_STR("arms"));
  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 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) {
    el_val_t arm = native_list_get(arms, i);
    el_val_t pat = el_get_field(arm, EL_STR("pattern"));
    el_val_t body = el_get_field(arm, EL_STR("body"));
    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"))) {
      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"));
        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"));
          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"));
            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"));
              el_val_t bv = EL_STR("false");
              if (str_eq(v, EL_STR("true"))) {
                bv = EL_STR("true");
              }
              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 {
              parts = native_list_append(parts, el_str_concat(el_str_concat(EL_STR("return ("), body_c), EL_STR("); ")));
            }
          }
        }
      }
    }
    i = (i + 1);
  }
  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;
}

el_val_t js_list_contains(el_val_t lst, el_val_t s) {
  el_val_t n = native_list_len(lst);
  el_val_t i = 0;
  while (i < n) {
    el_val_t item = native_list_get(lst, i);
    if (str_eq(item, s)) {
      return 1;
    }
    i = (i + 1);
  }
  return 0;
  return 0;
}

el_val_t js_cg_stmt(el_val_t stmt, el_val_t indent, el_val_t declared) {
  el_val_t kind = el_get_field(stmt, EL_STR("stmt"));
  if (str_eq(kind, EL_STR("Let"))) {
    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 = js_cg_expr(val);
    el_val_t ltype = el_get_field(stmt, EL_STR("type"));
    if (str_eq(ltype, EL_STR("Int"))) {
      js_add_int_name(name);
    }
    el_val_t vk = el_get_field(val, EL_STR("expr"));
    if (str_eq(vk, EL_STR("Int"))) {
      js_add_int_name(name);
    }
    if (js_list_contains(declared, name)) {
      js_emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(indent, name), EL_STR(" = ")), val_c), EL_STR(";")));
      return declared;
    } else {
      js_emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("let ")), name), EL_STR(" = ")), val_c), EL_STR(";")));
      return native_list_append(declared, name);
    }
  }
  if (str_eq(kind, EL_STR("Return"))) {
    el_val_t val = el_get_field(stmt, EL_STR("value"));
    el_val_t val_kind = el_get_field(val, EL_STR("expr"));
    if (str_eq(val_kind, EL_STR("Nil"))) {
      js_emit_line(el_str_concat(indent, EL_STR("return null;")));
    } else {
      el_val_t val_c = js_cg_expr(val);
      js_emit_line(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("return ")), val_c), EL_STR(";")));
    }
    return declared;
  }
  if (str_eq(kind, EL_STR("Assign"))) {
    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 = js_cg_expr(val);
    js_emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(indent, name), EL_STR(" = ")), val_c), EL_STR(";")));
    return declared;
  }
  if (str_eq(kind, EL_STR("Expr"))) {
    el_val_t val = el_get_field(stmt, EL_STR("value"));
    el_val_t val_kind = el_get_field(val, EL_STR("expr"));
    if (str_eq(val_kind, EL_STR("If"))) {
      js_cg_if_stmt(val, indent, declared);
      return declared;
    }
    if (str_eq(val_kind, EL_STR("For"))) {
      js_cg_for_stmt(val, indent, declared);
      return declared;
    }
    el_val_t val_c = js_cg_expr(val);
    js_emit_line(el_str_concat(el_str_concat(indent, val_c), EL_STR(";")));
    return declared;
  }
  if (str_eq(kind, EL_STR("While"))) {
    el_val_t cond = el_get_field(stmt, EL_STR("cond"));
    el_val_t body = el_get_field(stmt, EL_STR("body"));
    el_val_t cond_c = js_cg_expr(cond);
    cond_c = js_strip_outer_parens(cond_c);
    js_emit_line(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("while (")), cond_c), EL_STR(") {")));
    js_cg_stmts(body, el_str_concat(indent, EL_STR("  ")), native_list_clone(declared));
    js_emit_line(el_str_concat(indent, EL_STR("}")));
    return declared;
  }
  if (str_eq(kind, EL_STR("For"))) {
    el_val_t item = el_get_field(stmt, EL_STR("item"));
    el_val_t list_expr = el_get_field(stmt, EL_STR("list"));
    el_val_t body = el_get_field(stmt, EL_STR("body"));
    js_cg_for_body(item, list_expr, body, indent, declared);
    return declared;
  }
  if (str_eq(kind, EL_STR("FnDef"))) {
    return declared;
  }
  if (str_eq(kind, EL_STR("TypeDef"))) {
    return declared;
  }
  if (str_eq(kind, EL_STR("EnumDef"))) {
    return declared;
  }
  if (str_eq(kind, EL_STR("Import"))) {
    return 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)")));
    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")));
    return declared;
  }
  return declared;
  return 0;
}

el_val_t js_strip_outer_parens(el_val_t s) {
  el_val_t chars = native_string_chars(s);
  el_val_t n = native_list_len(chars);
  if (n < 2) {
    return s;
  }
  el_val_t first = native_list_get(chars, 0);
  el_val_t last = native_list_get(chars, (n - 1));
  if (str_eq(first, EL_STR("("))) {
    if (str_eq(last, EL_STR(")"))) {
      el_val_t depth = 1;
      el_val_t i = 1;
      el_val_t balanced = 1;
      while (i < (n - 1)) {
        el_val_t ch = native_list_get(chars, i);
        if (str_eq(ch, EL_STR("("))) {
          depth = (depth + 1);
        }
        if (str_eq(ch, EL_STR(")"))) {
          depth = (depth - 1);
          if (depth == 0) {
            balanced = 0;
            i = n;
          }
        }
        i = (i + 1);
      }
      if (balanced) {
        return str_slice(s, 1, (n - 1));
      }
    }
  }
  return s;
  return 0;
}

el_val_t js_cg_if_stmt(el_val_t expr, el_val_t indent, el_val_t declared) {
  el_val_t cond = el_get_field(expr, EL_STR("cond"));
  el_val_t then_stmts = el_get_field(expr, EL_STR("then"));
  el_val_t else_stmts = el_get_field(expr, EL_STR("else"));
  el_val_t has_else = el_get_field(expr, EL_STR("has_else"));
  el_val_t cond_c = js_cg_expr(cond);
  cond_c = js_strip_outer_parens(cond_c);
  js_emit_line(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("if (")), cond_c), EL_STR(") {")));
  js_cg_stmts(then_stmts, el_str_concat(indent, EL_STR("  ")), native_list_clone(declared));
  if (has_else) {
    js_emit_line(el_str_concat(indent, EL_STR("} else {")));
    js_cg_stmts(else_stmts, el_str_concat(indent, EL_STR("  ")), native_list_clone(declared));
  }
  js_emit_line(el_str_concat(indent, EL_STR("}")));
  return 0;
}

el_val_t js_cg_for_body(el_val_t item, el_val_t list_expr, el_val_t body, el_val_t indent, el_val_t declared) {
  el_val_t list_c = js_cg_expr(list_expr);
  js_emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(indent, EL_STR("for (const ")), item), EL_STR(" of ")), list_c), EL_STR(") {")));
  el_val_t body_decl = native_list_clone(declared);
  body_decl = native_list_append(body_decl, item);
  js_cg_stmts(body, el_str_concat(indent, EL_STR("  ")), body_decl);
  js_emit_line(el_str_concat(indent, EL_STR("}")));
  return 0;
}

el_val_t js_cg_for_stmt(el_val_t expr, el_val_t indent, el_val_t declared) {
  el_val_t item = el_get_field(expr, EL_STR("item"));
  el_val_t list_expr = el_get_field(expr, EL_STR("list"));
  el_val_t body = el_get_field(expr, EL_STR("body"));
  js_cg_for_body(item, list_expr, body, indent, declared);
  return 0;
}

el_val_t js_cg_stmts(el_val_t stmts, el_val_t indent, el_val_t declared) {
  el_val_t n = native_list_len(stmts);
  el_val_t i = 0;
  el_val_t decl = declared;
  while (i < n) {
    el_val_t stmt = native_list_get(stmts, i);
    decl = js_cg_stmt(stmt, indent, decl);
    i = (i + 1);
  }
  return decl;
  return 0;
}

el_val_t js_params_str(el_val_t params) {
  el_val_t n = native_list_len(params);
  if (n == 0) {
    return 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"));
    parts = native_list_append(parts, name);
    i = (i + 1);
  }
  return str_join(parts, EL_STR(", "));
  return 0;
}

el_val_t js_transform_implicit_return(el_val_t body) {
  el_val_t n = native_list_len(body);
  if (n == 0) {
    return body;
  }
  el_val_t last = native_list_get(body, (n - 1));
  el_val_t last_kind = el_get_field(last, EL_STR("stmt"));
  if (str_eq(last_kind, EL_STR("Expr"))) {
    el_val_t val = el_get_field(last, EL_STR("value"));
    el_val_t val_kind = el_get_field(val, EL_STR("expr"));
    if (str_eq(val_kind, EL_STR("If"))) {
      return body;
    }
    if (str_eq(val_kind, EL_STR("For"))) {
      return body;
    }
    el_val_t new_body = native_list_empty();
    el_val_t i = 0;
    while (i < (n - 1)) {
      new_body = native_list_append(new_body, native_list_get(body, i));
      i = (i + 1);
    }
    el_val_t return_stmt = el_map_new(2, "stmt", EL_STR("Return"), "value", val);
    new_body = native_list_append(new_body, return_stmt);
    return new_body;
  }
  return body;
  return 0;
}

el_val_t js_cg_fn(el_val_t stmt) {
  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 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_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(") {")));
  } 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);
  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"));
    decl = native_list_append(decl, pname);
    pi = (pi + 1);
  }
  el_val_t body_xformed = body;
  if (!str_eq(ret_type, EL_STR("Void"))) {
    body_xformed = js_transform_implicit_return(body);
  }
  js_cg_stmts(body_xformed, EL_STR("  "), decl);
  js_emit_line(EL_STR("}"));
  js_emit_blank();
  return 0;
}

el_val_t js_is_fndef(el_val_t stmt) {
  el_val_t kind = el_get_field(stmt, EL_STR("stmt"));
  if (str_eq(kind, EL_STR("FnDef"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t js_is_top_level_decl(el_val_t stmt) {
  el_val_t kind = el_get_field(stmt, EL_STR("stmt"));
  if (str_eq(kind, EL_STR("TypeDef"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("EnumDef"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("Import"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("CgiBlock"))) {
    return 1;
  }
  if (str_eq(kind, EL_STR("ServiceBlock"))) {
    return 1;
  }
  return 0;
  return 0;
}

el_val_t codegen_js(el_val_t stmts, el_val_t source) {
  state_set(EL_STR("__js_int_names"), EL_STR(""));
  state_set(EL_STR("__js_match_counter"), 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();
  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);
    if (js_is_fndef(stmt)) {
      js_cg_fn(stmt);
    }
    i = (i + 1);
  }
  el_val_t has_main = 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 = el_get_field(stmt, EL_STR("name"));
      if (str_eq(fn_name, EL_STR("main"))) {
        has_main = 1;
      }
    }
    i = (i + 1);
  }
  el_val_t main_decl = native_list_empty();
  i = 0;
  while (i < n) {
    el_val_t stmt = native_list_get(stmts, i);
    if (js_is_fndef(stmt)) {
    } else {
      if (js_is_top_level_decl(stmt)) {
      } else {
        main_decl = js_cg_stmt(stmt, EL_STR(""), main_decl);
      }
    }
    i = (i + 1);
  }
  if (has_main) {
    js_emit_blank();
    js_emit_line(EL_STR("main();"));
  }
  return EL_STR("");
  return 0;
}

el_val_t compile(el_val_t source) {
  el_val_t tokens = lex(source);
  el_val_t stmts = parse(tokens);
  el_release(tokens);
  return codegen(stmts, source);
  return 0;
}

el_val_t compile_js(el_val_t source) {
  el_val_t tokens = lex(source);
  el_val_t stmts = parse(tokens);
  el_release(tokens);
  return codegen_js(stmts, source);
  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);
  }
  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;
  while (i < n) {
    el_val_t a = native_list_get(argv, i);
    if (str_starts_with(a, EL_STR("--target="))) {
      el_val_t v = str_slice(a, 9, str_len(a));
      return v;
    }
    i = (i + 1);
  }
  return EL_STR("c");
  return 0;
}

el_val_t strip_flags(el_val_t argv) {
  el_val_t out = native_list_empty();
  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_starts_with(a, EL_STR("--"))) {
      out = native_list_append(out, a);
    }
    i = (i + 1);
  }
  return out;
  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 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 - 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);
  while (i >= 0) {
    el_val_t c = str_slice(path, i, (i + 1));
    if (str_eq(c, EL_STR("/"))) {
      return str_slice(path, 0, i);
    }
    i = (i - 1);
  }
  return EL_STR(".");
  return 0;
}

el_val_t parse_import_line(el_val_t trimmed, el_val_t dir) {
  if (str_starts_with(trimmed, EL_STR("import \""))) {
    el_val_t after = str_slice(trimmed, 8, str_len(trimmed));
    el_val_t q = str_index_of(after, EL_STR("\""));
    if (q > 0) {
      el_val_t mod = str_slice(after, 0, q);
      return el_str_concat(el_str_concat(dir, EL_STR("/")), mod);
    }
  }
  if (str_starts_with(trimmed, EL_STR("from "))) {
    el_val_t after = str_slice(trimmed, 5, str_len(trimmed));
    el_val_t sp = str_index_of(after, EL_STR(" "));
    if (sp > 0) {
      el_val_t mod_raw = str_slice(after, 0, sp);
      el_val_t mod = str_trim(mod_raw);
      if (!str_eq(mod, EL_STR(""))) {
        return el_str_concat(el_str_concat(el_str_concat(dir, EL_STR("/")), mod), EL_STR(".el"));
      }
    }
  }
  return EL_STR("");
  return 0;
}

el_val_t resolve_imports(el_val_t src_path) {
  el_val_t seen_key = el_str_concat(EL_STR("__elc_imp__:"), src_path);
  el_val_t already = state_get(seen_key);
  if (!str_eq(already, EL_STR(""))) {
    return EL_STR("");
  }
  state_set(seen_key, EL_STR("1"));
  el_val_t source = fs_read(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_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(""))) {
      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_chunks = native_list_append(body_chunks, el_str_concat(line, EL_STR("\n")));
    }
    i = (i + 1);
  }
  return el_str_concat(str_join(prefix_chunks, EL_STR("")), str_join(body_chunks, EL_STR("")));
  return 0;
}

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 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] [--emit-header] <source.el> [<output>]"));
    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 (argc >= 2) {
    el_val_t out_path = native_list_get(positional, 1);
    el_val_t ok = fs_write(out_path, out);
    if (ok) {
      exit(0);
    } else {
      println(EL_STR("el-compiler: failed to write output"));
      exit(1);
    }
  }
  return 0;
}

