diff --git a/.gitea/workflows/ci-dev.yaml b/.gitea/workflows/ci-dev.yaml index 51aa463..f788d8b 100644 --- a/.gitea/workflows/ci-dev.yaml +++ b/.gitea/workflows/ci-dev.yaml @@ -22,7 +22,10 @@ jobs: - name: Install build dependencies run: | apt-get update -qq - apt-get install -y gcc libcurl4-openssl-dev + apt-get install -y gcc libcurl4-openssl-dev apt-transport-https ca-certificates + echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" \ + > /etc/apt/sources.list.d/google-cloud-sdk.list + apt-get update -qq && apt-get install -y google-cloud-cli # Seed: use the committed linux-amd64 binary as the bootstrap - name: Bootstrap from committed linux binary (seed) @@ -84,13 +87,22 @@ jobs: bash tests/html_sanitizer/run.sh # Native El test suites (elc --test, compile-link-run) + # el_runtime.c is precompiled to .o once and reused by all 8 modules. + - name: Precompile el_runtime.o + run: | + set -euo pipefail + RUNTIME="$(pwd)/el-compiler/runtime" + gcc -O2 -c -I "$RUNTIME" "$RUNTIME/el_runtime.c" \ + -o /tmp/el_runtime.o + echo "el_runtime.o compiled" + - name: Run tests - native (core) run: | set -euo pipefail ELC="$(pwd)/dist/platform/elc" RUNTIME="$(pwd)/el-compiler/runtime" "$ELC" --test tests/native/test_core.el > /tmp/el_native_core.c - gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c "$RUNTIME/el_runtime.c" \ + gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c /tmp/el_runtime.o \ -lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core /tmp/el_native_core @@ -100,7 +112,7 @@ jobs: ELC="$(pwd)/dist/platform/elc" RUNTIME="$(pwd)/el-compiler/runtime" "$ELC" --test tests/native/test_text.el > /tmp/el_native_text.c - gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c "$RUNTIME/el_runtime.c" \ + gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c /tmp/el_runtime.o \ -lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text /tmp/el_native_text @@ -110,7 +122,7 @@ jobs: ELC="$(pwd)/dist/platform/elc" RUNTIME="$(pwd)/el-compiler/runtime" "$ELC" --test tests/native/test_string.el > /tmp/el_native_string.c - gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c "$RUNTIME/el_runtime.c" \ + gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c /tmp/el_runtime.o \ -lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string /tmp/el_native_string @@ -120,7 +132,7 @@ jobs: ELC="$(pwd)/dist/platform/elc" RUNTIME="$(pwd)/el-compiler/runtime" "$ELC" --test tests/native/test_math.el > /tmp/el_native_math.c - gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c "$RUNTIME/el_runtime.c" \ + gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c /tmp/el_runtime.o \ -lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math /tmp/el_native_math @@ -130,7 +142,7 @@ jobs: ELC="$(pwd)/dist/platform/elc" RUNTIME="$(pwd)/el-compiler/runtime" "$ELC" --test tests/native/test_state.el > /tmp/el_native_state.c - gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c "$RUNTIME/el_runtime.c" \ + gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c /tmp/el_runtime.o \ -lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state /tmp/el_native_state @@ -140,7 +152,7 @@ jobs: ELC="$(pwd)/dist/platform/elc" RUNTIME="$(pwd)/el-compiler/runtime" "$ELC" --test tests/native/test_time.el > /tmp/el_native_time.c - gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c "$RUNTIME/el_runtime.c" \ + gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c /tmp/el_runtime.o \ -lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time /tmp/el_native_time @@ -150,7 +162,7 @@ jobs: ELC="$(pwd)/dist/platform/elc" RUNTIME="$(pwd)/el-compiler/runtime" "$ELC" --test tests/native/test_json.el > /tmp/el_native_json.c - gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c "$RUNTIME/el_runtime.c" \ + gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c /tmp/el_runtime.o \ -lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json /tmp/el_native_json @@ -160,7 +172,7 @@ jobs: ELC="$(pwd)/dist/platform/elc" RUNTIME="$(pwd)/el-compiler/runtime" "$ELC" --test tests/native/test_env.el > /tmp/el_native_env.c - gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c "$RUNTIME/el_runtime.c" \ + gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c /tmp/el_runtime.o \ -lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env /tmp/el_native_env @@ -170,7 +182,7 @@ jobs: ELC="$(pwd)/dist/platform/elc" RUNTIME="$(pwd)/el-compiler/runtime" "$ELC" --test tests/native/test_fs.el > /tmp/el_native_fs.c - gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c "$RUNTIME/el_runtime.c" \ + gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c /tmp/el_runtime.o \ -lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs /tmp/el_native_fs @@ -203,9 +215,6 @@ jobs: GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }} run: | echo "${GCP_SA_KEY}" > /tmp/gcp-key.json - apt-get install -y -qq apt-transport-https ca-certificates curl - echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list - apt-get update -qq && apt-get install -y google-cloud-cli gcloud auth activate-service-account --key-file=/tmp/gcp-key.json gcloud config set project neuron-785695 @@ -252,4 +261,52 @@ jobs: --source=el-compiler/runtime/el_runtime.js echo "Published El SDK version=${VERSION} to foundation-dev" + # Keep key alive for the ci-base rebuild step below + # (deleted in that step after docker push) + + - name: Rebuild ci-base with fresh El SDK (dev) + # Patches ci-base:dev in-place: pulls the existing image (which has all + # system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly + # built El SDK on top. Keeps the full ci-base rebuild fast and incremental. + if: github.event_name == 'push' + env: + GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }} + run: | + set -euo pipefail + CI_BASE="us-central1-docker.pkg.dev/neuron-785695/neuron-ci/ci-base" + SHA="${GITHUB_SHA:0:8}" + + echo "${GCP_SA_KEY}" > /tmp/gcp-key.json + gcloud auth activate-service-account --key-file=/tmp/gcp-key.json + gcloud config set project neuron-785695 + gcloud auth configure-docker us-central1-docker.pkg.dev --quiet + + # Pull existing ci-base:dev (or fall back to :latest on first run) + BASE_TAG="dev" + docker pull "${CI_BASE}:dev" || { docker pull "${CI_BASE}:latest" && BASE_TAG="latest"; } + + # Inline Dockerfile — only replaces the El SDK layer + cat > /tmp/Dockerfile.ci-base-patch << 'EOF' + ARG BASE + FROM ${BASE} + COPY dist/platform/elc /opt/el/dist/platform/elc + COPY dist/bin/elb /opt/el/dist/bin/elb + COPY el-compiler/runtime/el_runtime.c /opt/el/el-compiler/runtime/el_runtime.c + COPY el-compiler/runtime/el_runtime.h /opt/el/el-compiler/runtime/el_runtime.h + COPY el-compiler/runtime/el_runtime.js /opt/el/el-compiler/runtime/el_runtime.js + RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb + EOF + + docker build \ + --build-arg BASE="${CI_BASE}:${BASE_TAG}" \ + --build-arg BUILDKIT_INLINE_CACHE=1 \ + -f /tmp/Dockerfile.ci-base-patch \ + -t "${CI_BASE}:dev" \ + -t "${CI_BASE}:dev-${SHA}" \ + . + + docker push "${CI_BASE}:dev" + docker push "${CI_BASE}:dev-${SHA}" + + echo "ci-base rebuilt: ${CI_BASE}:dev (${SHA})" rm -f /tmp/gcp-key.json diff --git a/.gitea/workflows/ci-stage.yaml b/.gitea/workflows/ci-stage.yaml index 5f4f016..d8b4ee1 100644 --- a/.gitea/workflows/ci-stage.yaml +++ b/.gitea/workflows/ci-stage.yaml @@ -246,4 +246,51 @@ jobs: --source=el-compiler/runtime/el_runtime.h echo "Published El SDK version=${VERSION} to foundation-stage" + # Keep key alive for the ci-base rebuild step below + # (deleted in that step after docker push) + + - name: Rebuild ci-base with fresh El SDK (stage) + # Patches ci-base:stage in-place: pulls the existing image (which has all + # system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly + # built El SDK on top. Keeps the full ci-base rebuild fast and incremental. + if: github.event_name == 'push' + env: + GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }} + run: | + set -euo pipefail + CI_BASE="us-central1-docker.pkg.dev/neuron-785695/neuron-ci/ci-base" + SHA="${GITHUB_SHA:0:8}" + + echo "${GCP_SA_KEY}" > /tmp/gcp-key.json + gcloud auth activate-service-account --key-file=/tmp/gcp-key.json + gcloud config set project neuron-785695 + gcloud auth configure-docker us-central1-docker.pkg.dev --quiet + + # Pull existing ci-base:stage (system deps stay cached in the base layer) + docker pull "${CI_BASE}:stage" || docker pull "${CI_BASE}:latest" + + # Inline Dockerfile — only replaces the El SDK layer + cat > /tmp/Dockerfile.ci-base-patch << 'EOF' + ARG BASE + FROM ${BASE} + COPY dist/platform/elc /opt/el/dist/platform/elc + COPY dist/bin/elb /opt/el/dist/bin/elb + COPY el-compiler/runtime/el_runtime.c /opt/el/el-compiler/runtime/el_runtime.c + COPY el-compiler/runtime/el_runtime.h /opt/el/el-compiler/runtime/el_runtime.h + COPY el-compiler/runtime/el_runtime.js /opt/el/el-compiler/runtime/el_runtime.js + RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb + EOF + + docker build \ + --build-arg BASE="${CI_BASE}:stage" \ + --build-arg BUILDKIT_INLINE_CACHE=1 \ + -f /tmp/Dockerfile.ci-base-patch \ + -t "${CI_BASE}:stage" \ + -t "${CI_BASE}:stage-${SHA}" \ + . + + docker push "${CI_BASE}:stage" + docker push "${CI_BASE}:stage-${SHA}" + + echo "ci-base rebuilt: ${CI_BASE}:stage (${SHA})" rm -f /tmp/gcp-key.json diff --git a/elp/src/language-profile.elh b/elp/src/language-profile.elh index b138c4f..8436652 100644 --- a/elp/src/language-profile.elh +++ b/elp/src/language-profile.elh @@ -1,46 +1,46 @@ -// auto-generated by elc --emit-header - do not edit -extern fn lang_profile(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> Any -extern fn lang_get(profile: Any, key: String) -> String -extern fn lang_profile_en() -> Any -extern fn lang_profile_ja() -> Any -extern fn lang_profile_ar() -> Any -extern fn lang_profile_zh() -> Any -extern fn lang_profile_de() -> Any -extern fn lang_profile_es() -> Any -extern fn lang_profile_fi() -> Any -extern fn lang_profile_sw() -> Any -extern fn lang_profile_hi() -> Any -extern fn lang_profile_ru() -> Any -extern fn lang_profile_fr() -> Any -extern fn lang_profile_la() -> Any -extern fn lang_profile_he() -> Any -extern fn lang_profile_sa() -> Any -extern fn lang_profile_got() -> Any -extern fn lang_profile_non() -> Any -extern fn lang_profile_enm() -> Any -extern fn lang_profile_pi() -> Any -extern fn lang_profile_grc() -> Any -extern fn lang_profile_ang() -> Any -extern fn lang_profile_fro() -> Any -extern fn lang_profile_goh() -> Any -extern fn lang_profile_sga() -> Any -extern fn lang_profile_txb() -> Any -extern fn lang_profile_peo() -> Any -extern fn lang_profile_akk() -> Any -extern fn lang_profile_uga() -> Any -extern fn lang_profile_egy() -> Any -extern fn lang_profile_sux() -> Any -extern fn lang_profile_gez() -> Any -extern fn lang_profile_cop() -> Any -extern fn lang_from_code(code: String) -> Any -extern fn lang_default() -> Any -extern fn lang_is_isolating(profile: Any) -> Bool -extern fn lang_is_agglutinative(profile: Any) -> Bool -extern fn lang_is_fusional(profile: Any) -> Bool -extern fn lang_is_polysynthetic(profile: Any) -> Bool -extern fn lang_is_rtl(profile: Any) -> Bool -extern fn lang_has_null_subject(profile: Any) -> Bool -extern fn lang_has_case(profile: Any) -> Bool -extern fn lang_has_gender(profile: Any) -> Bool -extern fn lang_word_order(profile: Any) -> String -extern fn lang_code(profile: Any) -> String +// auto-generated by elc --emit-header — do not edit +extern fn lang_profile(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] +extern fn lang_get(profile: [String], key: String) -> String +extern fn lang_profile_en() -> [String] +extern fn lang_profile_ja() -> [String] +extern fn lang_profile_ar() -> [String] +extern fn lang_profile_zh() -> [String] +extern fn lang_profile_de() -> [String] +extern fn lang_profile_es() -> [String] +extern fn lang_profile_fi() -> [String] +extern fn lang_profile_sw() -> [String] +extern fn lang_profile_hi() -> [String] +extern fn lang_profile_ru() -> [String] +extern fn lang_profile_fr() -> [String] +extern fn lang_profile_la() -> [String] +extern fn lang_profile_he() -> [String] +extern fn lang_profile_sa() -> [String] +extern fn lang_profile_got() -> [String] +extern fn lang_profile_non() -> [String] +extern fn lang_profile_enm() -> [String] +extern fn lang_profile_pi() -> [String] +extern fn lang_profile_grc() -> [String] +extern fn lang_profile_ang() -> [String] +extern fn lang_profile_fro() -> [String] +extern fn lang_profile_goh() -> [String] +extern fn lang_profile_sga() -> [String] +extern fn lang_profile_txb() -> [String] +extern fn lang_profile_peo() -> [String] +extern fn lang_profile_akk() -> [String] +extern fn lang_profile_uga() -> [String] +extern fn lang_profile_egy() -> [String] +extern fn lang_profile_sux() -> [String] +extern fn lang_profile_gez() -> [String] +extern fn lang_profile_cop() -> [String] +extern fn lang_from_code(code: String) -> [String] +extern fn lang_default() -> [String] +extern fn lang_is_isolating(profile: [String]) -> Bool +extern fn lang_is_agglutinative(profile: [String]) -> Bool +extern fn lang_is_fusional(profile: [String]) -> Bool +extern fn lang_is_polysynthetic(profile: [String]) -> Bool +extern fn lang_is_rtl(profile: [String]) -> Bool +extern fn lang_has_null_subject(profile: [String]) -> Bool +extern fn lang_has_case(profile: [String]) -> Bool +extern fn lang_has_gender(profile: [String]) -> Bool +extern fn lang_word_order(profile: [String]) -> String +extern fn lang_code(profile: [String]) -> String diff --git a/elp/src/morphology-akk.el b/elp/src/morphology-akk.el index 3d5cbc0..e543209 100644 --- a/elp/src/morphology-akk.el +++ b/elp/src/morphology-akk.el @@ -56,6 +56,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn akk_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-akk.elh b/elp/src/morphology-akk.elh index c13a7d8..6c3ac12 100644 --- a/elp/src/morphology-akk.elh +++ b/elp/src/morphology-akk.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn akk_str_ends(s: String, suf: String) -> Bool extern fn akk_str_len(s: String) -> Int extern fn akk_str_drop_last(s: String, n: Int) -> String diff --git a/elp/src/morphology-ang.el b/elp/src/morphology-ang.el index 0d533b0..e0d086b 100644 --- a/elp/src/morphology-ang.el +++ b/elp/src/morphology-ang.el @@ -36,6 +36,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn ang_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-ang.elh b/elp/src/morphology-ang.elh index f1b317a..9975651 100644 --- a/elp/src/morphology-ang.elh +++ b/elp/src/morphology-ang.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn ang_str_ends(s: String, suf: String) -> Bool extern fn ang_str_drop_last(s: String, n: Int) -> String extern fn ang_str_last_char(s: String) -> String diff --git a/elp/src/morphology-ar.el b/elp/src/morphology-ar.el index 6d3c121..c287e46 100644 --- a/elp/src/morphology-ar.el +++ b/elp/src/morphology-ar.el @@ -21,6 +21,7 @@ // ── String helpers ──────────────────────────────────────────────────────────── +import "morphology.el" fn ar_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-ar.elh b/elp/src/morphology-ar.elh index 71bdfd3..34ef396 100644 --- a/elp/src/morphology-ar.elh +++ b/elp/src/morphology-ar.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn ar_str_ends(s: String, suf: String) -> Bool extern fn ar_str_len(s: String) -> Int extern fn ar_str_drop_last(s: String, n: Int) -> String diff --git a/elp/src/morphology-cop.el b/elp/src/morphology-cop.el index b96b117..70f1826 100644 --- a/elp/src/morphology-cop.el +++ b/elp/src/morphology-cop.el @@ -54,6 +54,7 @@ // ── String helpers ────────────────────────────────────────────────────────────── +import "morphology.el" fn cop_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-cop.elh b/elp/src/morphology-cop.elh index 8be3aea..5ab0cb7 100644 --- a/elp/src/morphology-cop.elh +++ b/elp/src/morphology-cop.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn cop_str_ends(s: String, suf: String) -> Bool extern fn cop_str_len(s: String) -> Int extern fn cop_drop(s: String, n: Int) -> String diff --git a/elp/src/morphology-de.el b/elp/src/morphology-de.el index 979aa95..0ea107a 100644 --- a/elp/src/morphology-de.el +++ b/elp/src/morphology-de.el @@ -26,6 +26,7 @@ // Dat: dem der dem den // Gen: des der des der +import "morphology.el" fn de_article_def(gender: String, gram_case: String, number: String) -> String { if str_eq(number, "pl") { if str_eq(gram_case, "nom") { return "die" } diff --git a/elp/src/morphology-de.elh b/elp/src/morphology-de.elh index 23ce0d6..0a38b1c 100644 --- a/elp/src/morphology-de.elh +++ b/elp/src/morphology-de.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn de_article_def(gender: String, gram_case: String, number: String) -> String extern fn de_article_indef(gender: String, gram_case: String, number: String) -> String extern fn de_article(gender: String, gram_case: String, number: String, definite: String) -> String diff --git a/elp/src/morphology-egy.el b/elp/src/morphology-egy.el index c1bfef7..80c20d4 100644 --- a/elp/src/morphology-egy.el +++ b/elp/src/morphology-egy.el @@ -52,6 +52,7 @@ // ── String helpers ────────────────────────────────────────────────────────────── +import "morphology.el" fn egy_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-egy.elh b/elp/src/morphology-egy.elh index 22cf7c2..ba63eaf 100644 --- a/elp/src/morphology-egy.elh +++ b/elp/src/morphology-egy.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn egy_str_ends(s: String, suf: String) -> Bool extern fn egy_str_len(s: String) -> Int extern fn egy_drop(s: String, n: Int) -> String diff --git a/elp/src/morphology-enm.el b/elp/src/morphology-enm.el index e27f9b9..00215a7 100644 --- a/elp/src/morphology-enm.el +++ b/elp/src/morphology-enm.el @@ -31,6 +31,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn enm_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-enm.elh b/elp/src/morphology-enm.elh index c38c522..f6f4195 100644 --- a/elp/src/morphology-enm.elh +++ b/elp/src/morphology-enm.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn enm_str_ends(s: String, suf: String) -> Bool extern fn enm_drop(s: String, n: Int) -> String extern fn enm_first_char(s: String) -> String diff --git a/elp/src/morphology-es.el b/elp/src/morphology-es.el index d2762be..8b2a744 100644 --- a/elp/src/morphology-es.el +++ b/elp/src/morphology-es.el @@ -12,6 +12,7 @@ // ── String helpers (local, matching morphology.el conventions) ──────────────── +import "morphology.el" fn es_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-es.elh b/elp/src/morphology-es.elh index 551d6c0..a2ea5cd 100644 --- a/elp/src/morphology-es.elh +++ b/elp/src/morphology-es.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn es_str_ends(s: String, suf: String) -> Bool extern fn es_str_drop_last(s: String, n: Int) -> String extern fn es_str_last_char(s: String) -> String diff --git a/elp/src/morphology-fi.el b/elp/src/morphology-fi.el index 278550f..fbc58f9 100644 --- a/elp/src/morphology-fi.el +++ b/elp/src/morphology-fi.el @@ -25,6 +25,7 @@ // If only neutral vowels are found, default to "front" (the conservative choice // for borrowed words and those without clear back vowels). +import "morphology.el" fn fi_harmony(word: String) -> String { let n: Int = str_len(word) let i: Int = n - 1 diff --git a/elp/src/morphology-fi.elh b/elp/src/morphology-fi.elh index 6ab4de7..fc991ec 100644 --- a/elp/src/morphology-fi.elh +++ b/elp/src/morphology-fi.elh @@ -1,11 +1,11 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn fi_harmony(word: String) -> String extern fn fi_suffix(base: String, harmony: String) -> String extern fn fi_noun_case(stem: String, gram_case: String, number: String, harmony: String) -> String extern fn fi_str_last_char(s: String) -> String extern fn fi_apply_case(noun: String, gram_case: String, number: String) -> String extern fn fi_verb_stem(dict_form: String) -> String -extern fn fi_irregular_verb(dict_form: String) -> Any +extern fn fi_irregular_verb(dict_form: String) -> [String] extern fn fi_present_ending(stem: String, person: String, number: String, harmony: String) -> String extern fn fi_past_stem(stem: String) -> String extern fn fi_past_ending(stem: String, person: String, number: String, harmony: String) -> String @@ -14,4 +14,4 @@ extern fn fi_negative(verb: String, person: String, number: String) -> String extern fn fi_conjugate(verb: String, tense: String, person: String, number: String) -> String extern fn fi_question_suffix(harmony: String) -> String extern fn fi_make_question(verb_form: String, harmony: String) -> String -extern fn fi_full_paradigm(noun: String) -> Any +extern fn fi_full_paradigm(noun: String) -> [String] diff --git a/elp/src/morphology-fr.el b/elp/src/morphology-fr.el index 576e001..26f2a32 100644 --- a/elp/src/morphology-fr.el +++ b/elp/src/morphology-fr.el @@ -19,6 +19,7 @@ // ── String helpers (local, matching morphology.el conventions) ──────────────── +import "morphology.el" fn fr_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-fr.elh b/elp/src/morphology-fr.elh index 38957e1..a36ab8f 100644 --- a/elp/src/morphology-fr.elh +++ b/elp/src/morphology-fr.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn fr_str_ends(s: String, suf: String) -> Bool extern fn fr_str_drop_last(s: String, n: Int) -> String extern fn fr_str_last_char(s: String) -> String diff --git a/elp/src/morphology-fro.el b/elp/src/morphology-fro.el index f7ed551..d4aa7e8 100644 --- a/elp/src/morphology-fro.el +++ b/elp/src/morphology-fro.el @@ -53,6 +53,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn fro_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-fro.elh b/elp/src/morphology-fro.elh index 823bd0e..477bbae 100644 --- a/elp/src/morphology-fro.elh +++ b/elp/src/morphology-fro.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn fro_str_ends(s: String, suf: String) -> Bool extern fn fro_drop(s: String, n: Int) -> String extern fn fro_slot(person: String, number: String) -> Int diff --git a/elp/src/morphology-gez.el b/elp/src/morphology-gez.el index d1808f4..f4cc03d 100644 --- a/elp/src/morphology-gez.el +++ b/elp/src/morphology-gez.el @@ -64,6 +64,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn gez_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-gez.elh b/elp/src/morphology-gez.elh index c26912a..b23da81 100644 --- a/elp/src/morphology-gez.elh +++ b/elp/src/morphology-gez.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn gez_str_ends(s: String, suf: String) -> Bool extern fn gez_str_len(s: String) -> Int extern fn gez_str_drop_last(s: String, n: Int) -> String diff --git a/elp/src/morphology-goh.el b/elp/src/morphology-goh.el index d83c50e..7572afe 100644 --- a/elp/src/morphology-goh.el +++ b/elp/src/morphology-goh.el @@ -48,6 +48,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn goh_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-goh.elh b/elp/src/morphology-goh.elh index 2226247..3836290 100644 --- a/elp/src/morphology-goh.elh +++ b/elp/src/morphology-goh.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn goh_str_ends(s: String, suf: String) -> Bool extern fn goh_drop(s: String, n: Int) -> String extern fn goh_slot(person: String, number: String) -> Int diff --git a/elp/src/morphology-got.el b/elp/src/morphology-got.el index 5569989..e2d5301 100644 --- a/elp/src/morphology-got.el +++ b/elp/src/morphology-got.el @@ -49,6 +49,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn got_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-got.elh b/elp/src/morphology-got.elh index ef286f9..f5cc1ba 100644 --- a/elp/src/morphology-got.elh +++ b/elp/src/morphology-got.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn got_str_ends(s: String, suf: String) -> Bool extern fn got_str_drop_last(s: String, n: Int) -> String extern fn got_slot(person: String, number: String) -> Int diff --git a/elp/src/morphology-grc.el b/elp/src/morphology-grc.el index 741ced5..4640700 100644 --- a/elp/src/morphology-grc.el +++ b/elp/src/morphology-grc.el @@ -31,6 +31,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn grc_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-grc.elh b/elp/src/morphology-grc.elh index be5f997..1f565f0 100644 --- a/elp/src/morphology-grc.elh +++ b/elp/src/morphology-grc.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn grc_str_ends(s: String, suf: String) -> Bool extern fn grc_str_drop_last(s: String, n: Int) -> String extern fn grc_str_last_char(s: String) -> String diff --git a/elp/src/morphology-he.el b/elp/src/morphology-he.el index 7946d93..0d09f1c 100644 --- a/elp/src/morphology-he.el +++ b/elp/src/morphology-he.el @@ -51,6 +51,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn he_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-he.elh b/elp/src/morphology-he.elh index ed6304f..4d1241b 100644 --- a/elp/src/morphology-he.elh +++ b/elp/src/morphology-he.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn he_str_ends(s: String, suf: String) -> Bool extern fn he_str_len(s: String) -> Int extern fn he_str_drop_last(s: String, n: Int) -> String diff --git a/elp/src/morphology-hi.el b/elp/src/morphology-hi.el index d4cfa02..79eccdd 100644 --- a/elp/src/morphology-hi.el +++ b/elp/src/morphology-hi.el @@ -24,6 +24,7 @@ // ── String helpers ──────────────────────────────────────────────────────────── +import "morphology.el" fn hi_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-hi.elh b/elp/src/morphology-hi.elh index d012ddf..688c51a 100644 --- a/elp/src/morphology-hi.elh +++ b/elp/src/morphology-hi.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn hi_str_ends(s: String, suf: String) -> Bool extern fn hi_str_drop_last(s: String, n: Int) -> String extern fn hi_str_last_char(s: String) -> String diff --git a/elp/src/morphology-ja.el b/elp/src/morphology-ja.el index 2a09412..fdac71e 100644 --- a/elp/src/morphology-ja.el +++ b/elp/src/morphology-ja.el @@ -23,6 +23,7 @@ // Note: this is a heuristic classifier for romanized input. For production use // with native kana/kanji forms, the dictionary form (辞書形) must be consulted. +import "morphology.el" fn ja_verb_group(dict_form: String) -> String { // Irregular verbs (exact match on dictionary form) if str_eq(dict_form, "する") { return "irregular" } diff --git a/elp/src/morphology-ja.elh b/elp/src/morphology-ja.elh index 4ecf826..ee2f337 100644 --- a/elp/src/morphology-ja.elh +++ b/elp/src/morphology-ja.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn ja_verb_group(dict_form: String) -> String extern fn ja_ichidan_stem(dict_form: String) -> String extern fn ja_godan_stem_change(dict_form: String, row: String) -> String diff --git a/elp/src/morphology-la.el b/elp/src/morphology-la.el index a74da54..7feddbd 100644 --- a/elp/src/morphology-la.el +++ b/elp/src/morphology-la.el @@ -25,6 +25,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn la_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-la.elh b/elp/src/morphology-la.elh index 39bf57d..d31a70a 100644 --- a/elp/src/morphology-la.elh +++ b/elp/src/morphology-la.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn la_str_ends(s: String, suf: String) -> Bool extern fn la_str_drop_last(s: String, n: Int) -> String extern fn la_str_last_char(s: String) -> String diff --git a/elp/src/morphology-non.el b/elp/src/morphology-non.el index c7eafb6..3597535 100644 --- a/elp/src/morphology-non.el +++ b/elp/src/morphology-non.el @@ -27,6 +27,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn non_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-non.elh b/elp/src/morphology-non.elh index bdf7ce6..931b540 100644 --- a/elp/src/morphology-non.elh +++ b/elp/src/morphology-non.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn non_str_ends(s: String, suf: String) -> Bool extern fn non_drop(s: String, n: Int) -> String extern fn non_last(s: String) -> String diff --git a/elp/src/morphology-peo.el b/elp/src/morphology-peo.el index 1c12c7c..506ba02 100644 --- a/elp/src/morphology-peo.el +++ b/elp/src/morphology-peo.el @@ -31,6 +31,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn peo_drop(s: String, n: Int) -> String { let len: Int = str_len(s) if n >= len { return "" } diff --git a/elp/src/morphology-peo.elh b/elp/src/morphology-peo.elh index 56c507e..f0f408b 100644 --- a/elp/src/morphology-peo.elh +++ b/elp/src/morphology-peo.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn peo_drop(s: String, n: Int) -> String extern fn peo_ends(s: String, suf: String) -> Bool extern fn peo_slot(person: String, number: String) -> Int diff --git a/elp/src/morphology-pi.el b/elp/src/morphology-pi.el index bf53534..5aee9f0 100644 --- a/elp/src/morphology-pi.el +++ b/elp/src/morphology-pi.el @@ -30,6 +30,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn pi_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-pi.elh b/elp/src/morphology-pi.elh index 1de2c02..1bbf880 100644 --- a/elp/src/morphology-pi.elh +++ b/elp/src/morphology-pi.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn pi_str_ends(s: String, suf: String) -> Bool extern fn pi_drop(s: String, n: Int) -> String extern fn pi_last_char(s: String) -> String diff --git a/elp/src/morphology-ru.el b/elp/src/morphology-ru.el index d0fa913..baf54c5 100644 --- a/elp/src/morphology-ru.el +++ b/elp/src/morphology-ru.el @@ -35,6 +35,7 @@ // The heuristic returns the most probable gender. Caller should override // for known exceptions (путь, рубль are masc despite -ь). +import "morphology.el" fn ru_gender(noun: String) -> String { let n: Int = str_len(noun) if n == 0 { return "m" } diff --git a/elp/src/morphology-ru.elh b/elp/src/morphology-ru.elh index 6ef6791..1f0f081 100644 --- a/elp/src/morphology-ru.elh +++ b/elp/src/morphology-ru.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn ru_gender(noun: String) -> String extern fn ru_stem_type(noun: String, gender: String) -> String extern fn ru_noun_case(noun: String, gender: String, gram_case: String, number: String) -> String diff --git a/elp/src/morphology-sa.el b/elp/src/morphology-sa.el index ecd471e..1cc74e7 100644 --- a/elp/src/morphology-sa.el +++ b/elp/src/morphology-sa.el @@ -42,6 +42,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn sa_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-sa.elh b/elp/src/morphology-sa.elh index 368fc69..2583d3d 100644 --- a/elp/src/morphology-sa.elh +++ b/elp/src/morphology-sa.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn sa_str_ends(s: String, suf: String) -> Bool extern fn sa_str_drop_last(s: String, n: Int) -> String extern fn sa_slot(person: String, number: String) -> Int diff --git a/elp/src/morphology-sga.el b/elp/src/morphology-sga.el index 8f92363..a7d8eae 100644 --- a/elp/src/morphology-sga.el +++ b/elp/src/morphology-sga.el @@ -31,6 +31,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn sga_drop(s: String, n: Int) -> String { let len: Int = str_len(s) if n >= len { return "" } diff --git a/elp/src/morphology-sga.elh b/elp/src/morphology-sga.elh index 8bc0529..704ea1b 100644 --- a/elp/src/morphology-sga.elh +++ b/elp/src/morphology-sga.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn sga_drop(s: String, n: Int) -> String extern fn sga_first(s: String) -> String extern fn sga_rest(s: String) -> String diff --git a/elp/src/morphology-sux.el b/elp/src/morphology-sux.el index 92aab5e..8f0e72f 100644 --- a/elp/src/morphology-sux.el +++ b/elp/src/morphology-sux.el @@ -53,6 +53,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn sux_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-sux.elh b/elp/src/morphology-sux.elh index aa40e56..f476446 100644 --- a/elp/src/morphology-sux.elh +++ b/elp/src/morphology-sux.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn sux_str_ends(s: String, suf: String) -> Bool extern fn sux_str_drop_last(s: String, n: Int) -> String extern fn sux_str_last_char(s: String) -> String diff --git a/elp/src/morphology-sw.el b/elp/src/morphology-sw.el index aed8481..5b614a8 100644 --- a/elp/src/morphology-sw.el +++ b/elp/src/morphology-sw.el @@ -24,6 +24,7 @@ // ── String helpers ──────────────────────────────────────────────────────────── +import "morphology.el" fn sw_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-sw.elh b/elp/src/morphology-sw.elh index 6de2ff0..4f5260d 100644 --- a/elp/src/morphology-sw.elh +++ b/elp/src/morphology-sw.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn sw_str_ends(s: String, suf: String) -> Bool extern fn sw_str_drop_last(s: String, n: Int) -> String extern fn sw_str_first_char(s: String) -> String diff --git a/elp/src/morphology-txb.el b/elp/src/morphology-txb.el index c465fcc..a2dae65 100644 --- a/elp/src/morphology-txb.el +++ b/elp/src/morphology-txb.el @@ -30,6 +30,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn txb_drop(s: String, n: Int) -> String { let len: Int = str_len(s) if n >= len { return "" } diff --git a/elp/src/morphology-txb.elh b/elp/src/morphology-txb.elh index 27794bf..6c02972 100644 --- a/elp/src/morphology-txb.elh +++ b/elp/src/morphology-txb.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn txb_drop(s: String, n: Int) -> String extern fn txb_ends(s: String, suf: String) -> Bool extern fn txb_slot(person: String, number: String) -> Int diff --git a/elp/src/morphology-uga.el b/elp/src/morphology-uga.el index 435f86d..2c4b8ff 100644 --- a/elp/src/morphology-uga.el +++ b/elp/src/morphology-uga.el @@ -48,6 +48,7 @@ // ── String helpers ───────────────────────────────────────────────────────────── +import "morphology.el" fn uga_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology-uga.elh b/elp/src/morphology-uga.elh index 37a8aea..ffba827 100644 --- a/elp/src/morphology-uga.elh +++ b/elp/src/morphology-uga.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn uga_str_ends(s: String, suf: String) -> Bool extern fn uga_str_len(s: String) -> Int extern fn uga_str_drop_last(s: String, n: Int) -> String diff --git a/elp/src/morphology.el b/elp/src/morphology.el index 2031194..6d7fe00 100644 --- a/elp/src/morphology.el +++ b/elp/src/morphology.el @@ -33,6 +33,17 @@ // ── String helpers ──────────────────────────────────────────────────────────── +import "language-profile.el" +import "morphology-es.el" +import "morphology-fr.el" +import "morphology-de.el" +import "morphology-ru.el" +import "morphology-fi.el" +import "morphology-ar.el" +import "morphology-hi.el" +import "morphology-sw.el" +import "morphology-la.el" +import "morphology-ja.el" fn str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } diff --git a/elp/src/morphology.elh b/elp/src/morphology.elh index d150d39..cd756a1 100644 --- a/elp/src/morphology.elh +++ b/elp/src/morphology.elh @@ -1,4 +1,4 @@ -// auto-generated by elc --emit-header - do not edit +// auto-generated by elc --emit-header — do not edit extern fn str_ends(s: String, suf: String) -> Bool extern fn str_last_char(s: String) -> String extern fn str_last2(s: String) -> String @@ -8,7 +8,7 @@ extern fn is_vowel(c: String) -> Bool extern fn morph_apply_suffix(base: String, suffix: String) -> String extern fn en_irregular_plural(word: String) -> String extern fn en_irregular_singular(word: String) -> String -extern fn en_irregular_verb(base: String) -> Any +extern fn en_irregular_verb(base: String) -> [String] extern fn en_verb_3sg(base: String) -> String extern fn en_should_double_final(base: String) -> Bool extern fn en_verb_past(base: String) -> String @@ -16,10 +16,10 @@ extern fn en_verb_gerund(base: String) -> String extern fn en_pluralize_regular(singular: String) -> String extern fn en_verb_form(base: String, tense: String, person: String, number: String) -> String extern fn agree_determiner(det: String, noun: String) -> String -extern fn morph_pluralize(noun: String, profile: Any) -> String +extern fn morph_pluralize(noun: String, profile: [String]) -> String extern fn morph_map_canonical(verb: String, code: String) -> String -extern fn morph_conjugate(verb: String, tense: String, person: String, number: String, profile: Any) -> String -extern fn morph_inflect(word: String, features: String, profile: Any) -> String +extern fn morph_conjugate(verb: String, tense: String, person: String, number: String, profile: [String]) -> String +extern fn morph_inflect(word: String, features: String, profile: [String]) -> String extern fn pluralize(singular: String) -> String extern fn singularize(plural: String) -> String extern fn verb_form(base: String, tense: String, person: String, number: String) -> String diff --git a/elp/src/vocabulary.elh b/elp/src/vocabulary.elh index 3633adc..f91be96 100644 --- a/elp/src/vocabulary.elh +++ b/elp/src/vocabulary.elh @@ -1,20 +1,20 @@ -// auto-generated by elc --emit-header - do not edit -extern fn lex_word(entry: Any) -> String -extern fn lex_pos(entry: Any) -> String -extern fn lex_form(entry: Any, idx: Int) -> String -extern fn lex_class(entry: Any) -> String -extern fn make_entry(word: String, pos: String, f0: String, f1: String, f2: String, f3: String, f4: String, cls: String) -> Any -extern fn make_entry2(word: String, pos: String, f0: String, f1: String, cls: String) -> Any -extern fn make_entry3(word: String, pos: String, f0: String, f1: String, f2: String, cls: String) -> Any -extern fn make_entry1(word: String, pos: String, f0: String, cls: String) -> Any -extern fn build_vocab() -> Any -extern fn get_vocab() -> Any -extern fn vocab_lookup(word: String, lang_code: String) -> Any -extern fn vocab_lookup_en(word: String) -> Any +// auto-generated by elc --emit-header — do not edit +extern fn lex_word(entry: [String]) -> String +extern fn lex_pos(entry: [String]) -> String +extern fn lex_form(entry: [String], idx: Int) -> String +extern fn lex_class(entry: [String]) -> String +extern fn make_entry(word: String, pos: String, f0: String, f1: String, f2: String, f3: String, f4: String, cls: String) -> [String] +extern fn make_entry2(word: String, pos: String, f0: String, f1: String, cls: String) -> [String] +extern fn make_entry3(word: String, pos: String, f0: String, f1: String, f2: String, cls: String) -> [String] +extern fn make_entry1(word: String, pos: String, f0: String, cls: String) -> [String] +extern fn build_vocab() -> [[String]] +extern fn get_vocab() -> [[String]] +extern fn vocab_lookup(word: String, lang_code: String) -> [String] +extern fn vocab_lookup_en(word: String) -> [String] extern fn vocab_synonym(word: String, lang_register: String, lang_code: String) -> String -extern fn vocab_by_pos(pos: String) -> Any -extern fn vocab_by_class(cls: String) -> Any -extern fn entry_found(entry: Any) -> Bool -extern fn entry_word(entry: Any) -> String -extern fn entry_pos(entry: Any) -> String -extern fn entry_form(entry: Any, n: Int) -> String +extern fn vocab_by_pos(pos: String) -> [[String]] +extern fn vocab_by_class(cls: String) -> [[String]] +extern fn entry_found(entry: [String]) -> Bool +extern fn entry_word(entry: [String]) -> String +extern fn entry_pos(entry: [String]) -> String +extern fn entry_form(entry: [String], n: Int) -> String diff --git a/lang/dist/platform/elb b/lang/dist/platform/elb index 2df99e2..187092f 100755 Binary files a/lang/dist/platform/elb and b/lang/dist/platform/elb differ diff --git a/lang/dist/platform/elc b/lang/dist/platform/elc index f1cba65..5e582a3 100755 Binary files a/lang/dist/platform/elc and b/lang/dist/platform/elc differ diff --git a/lang/dist/platform/elc-new b/lang/dist/platform/elc-new deleted file mode 100755 index f1cba65..0000000 Binary files a/lang/dist/platform/elc-new and /dev/null differ diff --git a/lang/el-compiler/runtime/el_runtime.c b/lang/el-compiler/runtime/el_runtime.c index 7d47e17..6b97221 100644 --- a/lang/el-compiler/runtime/el_runtime.c +++ b/lang/el-compiler/runtime/el_runtime.c @@ -42,6 +42,7 @@ #include #include #include +#include /* getrusage — memory guard */ #ifdef HAVE_CURL #include #endif @@ -2241,6 +2242,43 @@ el_val_t url_decode(el_val_t sv) { return el_wrap_str(out); } +/* ── html_raw ──────────────────────────────────────────────────────────────── + * Identity passthrough for raw HTML template interpolation. + * El's {raw(expr)} compiles to html_raw(expr) — the value is output as-is + * without any escaping. The caller is responsible for safety. + */ +el_val_t html_raw(el_val_t s) { + return s; +} + +/* ── html_escape ───────────────────────────────────────────────────────────── + * Escape < > " ' & for safe HTML text interpolation. + * El's {expr} in HTML templates compiles to html_escape(expr). + */ +el_val_t html_escape(el_val_t sv) { + const char* src = EL_CSTR(sv); + if (!src) return EL_STR(""); + size_t len = strlen(src); + /* Worst case: every byte → 6 chars (") */ + char* out = (char*)malloc(len * 6 + 1); + if (!out) return sv; + el_arena_track(out); + char* p = out; + for (size_t i = 0; i < len; i++) { + unsigned char c = (unsigned char)src[i]; + switch (c) { + case '&': memcpy(p, "&", 5); p += 5; break; + case '<': memcpy(p, "<", 4); p += 4; break; + case '>': memcpy(p, ">", 4); p += 4; break; + case '"': memcpy(p, """, 6); p += 6; break; + case '\'': memcpy(p, "'", 5); p += 5; break; + default: *p++ = (char)c; break; + } + } + *p = '\0'; + return el_wrap_str(out); +} + /* ── HTML allowlist sanitizer ──────────────────────────────────────────────── * el_html_sanitize(input, allowlist_json) * @@ -5700,6 +5738,50 @@ el_val_t getpid_now(void) { return (el_val_t)getpid(); } +/* el_mem_check — self-terminating memory guard for long-running compiler runs. + * + * Call this periodically (e.g. after each function compiled) to detect runaway + * memory growth before the OS OOM-killer fires. Reads the limit from the env + * var ELC_MAX_MEM_MB (default 512 MB). If resident set size exceeds the limit, + * prints a diagnostic to stderr and exits with code 1 so the caller (elb or a + * CI script) can handle the failure gracefully instead of having the whole + * machine go down. + * + * Platform notes: + * macOS — ru_maxrss is in bytes. + * Linux — ru_maxrss is in kilobytes. + * We normalise to MB before comparing. + * + * Returns 0 always (the only non-return path is the exit() branch). + */ +el_val_t el_mem_check(void) { + /* Read limit from env; default 512 MB. */ + long limit_mb = 512; + const char *env_val = getenv("ELC_MAX_MEM_MB"); + if (env_val && *env_val) { + long v = atol(env_val); + if (v > 0) limit_mb = v; + } + + struct rusage ru; + if (getrusage(RUSAGE_SELF, &ru) != 0) return 0; /* can't read — skip check */ + + long rss_mb; +#if defined(__APPLE__) || defined(__MACH__) + /* macOS: ru_maxrss is bytes */ + rss_mb = (long)(ru.ru_maxrss / (1024L * 1024L)); +#else + /* Linux: ru_maxrss is kilobytes */ + rss_mb = (long)(ru.ru_maxrss / 1024L); +#endif + + if (rss_mb >= limit_mb) { + fprintf(stderr, "elc: memory limit exceeded (%ldMB), aborting\n", limit_mb); + exit(1); + } + return 0; +} + /* ── args() — command-line argument access ────────────────────────────────── * Compiled El programs call args() to get a list of CLI arguments. * Call el_runtime_init_args(argc, argv) at the start of C main() to populate. diff --git a/lang/el-compiler/runtime/el_runtime.h b/lang/el-compiler/runtime/el_runtime.h index c0529ef..2f9583f 100644 --- a/lang/el-compiler/runtime/el_runtime.h +++ b/lang/el-compiler/runtime/el_runtime.h @@ -227,6 +227,8 @@ el_val_t url_decode(el_val_t s); /* '+' → space, %XX → byte */ * {"p":[],"a":["href","title"],"strong":[],...} * where each value is the array of attribute names allowed for that tag. */ el_val_t el_html_sanitize(el_val_t input_html, el_val_t allowlist_json); +el_val_t html_raw(el_val_t s); +el_val_t html_escape(el_val_t s); /* ── Filesystem ──────────────────────────────────────────────────────────── */ @@ -531,6 +533,12 @@ el_val_t parse_int(el_val_t s, el_val_t default_val); el_val_t exit_program(el_val_t code); el_val_t getpid_now(void); +/* Self-terminating memory guard. Reads ELC_MAX_MEM_MB (default 512) and + * exits with code 1 if resident memory exceeds the limit. Call periodically + * during long compilation loops (e.g. after each function is compiled). + * Returns 0 when memory is within bounds. */ +el_val_t el_mem_check(void); + /* ── CGI identity ───────────────────────────────────────────────────────────── * Called at the start of main() in CGI programs (those with a `cgi {}` block). * Records the program's DHARMA identity before any other code executes. */ diff --git a/lang/el-compiler/src/codegen.el b/lang/el-compiler/src/codegen.el index d8a78f6..917fb31 100644 --- a/lang/el-compiler/src/codegen.el +++ b/lang/el-compiler/src/codegen.el @@ -324,6 +324,10 @@ fn cg_html_parts(children: [Map], acc_var: String) -> String { let each_c: String = cg_html_each(child, acc_var) let parts = native_list_append(parts, each_c) } + if str_eq(html_kind, "HtmlIf") { + let if_c: String = cg_html_if(child, acc_var) + let parts = native_list_append(parts, if_c) + } let i = i + 1 } str_join(parts, "") @@ -413,6 +417,17 @@ fn cg_html_each(node: Map, acc_var: String) -> String { "{ el_val_t " + list_var + " = (" + list_c + "); el_val_t " + len_var + " = el_list_len(" + list_var + "); for (el_val_t " + idx_var + " = 0; " + idx_var + " < " + len_var + "; " + idx_var + "++) { el_val_t " + item_name + " = el_list_get(" + list_var + ", " + idx_var + "); " + inner_c + "} } " } +// Generate code for {#if cond} ... {/if} (with optional {#else}). +fn cg_html_if(node: Map, acc_var: String) -> String { + let cond_expr = node["cond"] + let then_children: [Map] = node["then"] + let else_children: [Map] = node["else"] + let cond_c: String = cg_expr(cond_expr) + let then_c: String = cg_html_parts(then_children, acc_var) + let else_c: String = cg_html_parts(else_children, acc_var) + "if (" + cond_c + ") { " + then_c + " } else { " + else_c + " } " +} + // Top-level HTML template codegen — returns a C statement-expression string. fn cg_html_template(expr: Map) -> String { let root = expr["root"] @@ -3715,6 +3730,7 @@ fn codegen_streaming(tokens: [Any], sigs: [Map], source: String) -> cg_fn(stmt) el_release(stmt) el_arena_pop(fn_arena_mark) + el_mem_check() } } else { if is_top_level_decl(stmt) { diff --git a/lang/el-compiler/src/compiler.el b/lang/el-compiler/src/compiler.el index a4f5fdc..b9647bb 100644 --- a/lang/el-compiler/src/compiler.el +++ b/lang/el-compiler/src/compiler.el @@ -287,6 +287,9 @@ fn type_node_to_el(t: Map) -> String { // emit_header — write a .elh file from parsed statements. // Scans for FnDef nodes and emits 'extern fn' declarations. +// NOTE: This function requires the full AST. Prefer emit_header_from_sigs +// for the --emit-header path — it works from a token-level scan without +// building expression ASTs, avoiding OOM on large files. fn emit_header(stmts: [Map], hdr_path: String) -> Void { let n: Int = native_list_len(stmts) let i = 0 @@ -325,6 +328,32 @@ fn emit_header(stmts: [Map], hdr_path: String) -> Void { let ok: Bool = fs_write(hdr_path, content) } +// emit_header_from_sigs — write a .elh file from pre-scanned El signatures. +// Uses the output of scan_fn_sigs_el() — no full AST required. +// Peak memory is O(tokens) rather than O(whole-program AST), which prevents +// OOM on large files with HTML template bodies or deep BinOp chains. +fn emit_header_from_sigs(sigs: [Map], hdr_path: String) -> Void { + let n: Int = native_list_len(sigs) + let i: Int = 0 + let parts: [String] = native_list_empty() + let parts = native_list_append(parts, "// auto-generated by elc --emit-header — do not edit\n") + while i < n { + let sig = native_list_get(sigs, i) + let kind: String = sig["kind"] + if str_eq(kind, "fn") { + let name: String = sig["name"] + let params_el: String = sig["params_el"] + let ret_el: String = sig["ret_el"] + if str_eq(ret_el, "") { let ret_el = "Any" } + let line: String = "extern fn " + name + "(" + params_el + ") -> " + ret_el + let parts = native_list_append(parts, line + "\n") + } + let i = i + 1 + } + let content: String = str_join(parts, "") + let ok: Bool = fs_write(hdr_path, content) +} + // ── Import resolution ──────────────────────────────────────────────────────── // // elc supports two forms of import: @@ -536,16 +565,20 @@ fn main() -> Void { let src_path: String = native_list_get(positional, 0) - // When --emit-header is requested, parse the source file directly - // (without inlining imports) and write out a .elh file alongside the .c. + // When --emit-header is requested, lex the source file and do a + // token-level signature scan (no full AST) to write a .elh file. + // This avoids OOM on large files with HTML template bodies or deep + // BinOp chains (e.g. checkout.el) — parse() builds O(whole-program AST) + // while scan_fn_sigs_el keeps peak memory at O(tokens). if do_emit_header { + el_mem_check() let raw_source: String = fs_read(src_path) let hdr_tokens: [Any] = lex(raw_source) - let hdr_stmts: [Map] = parse(hdr_tokens) + let hdr_sigs: [Map] = scan_fn_sigs_el(hdr_tokens) el_release(hdr_tokens) let hdr_path: String = str_slice(src_path, 0, str_len(src_path) - 3) + ".elh" - emit_header(hdr_stmts, hdr_path) - el_release(hdr_stmts) + emit_header_from_sigs(hdr_sigs, hdr_path) + el_release(hdr_sigs) } let source: String = resolve_imports(src_path) diff --git a/lang/el-compiler/src/parser.el b/lang/el-compiler/src/parser.el index 025ac90..e4de3b9 100644 --- a/lang/el-compiler/src/parser.el +++ b/lang/el-compiler/src/parser.el @@ -293,6 +293,48 @@ fn is_void_element(name: String) -> Bool { false } +// Collect all tokens as raw text until is encountered. +// Used for