Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99b113ea9d | |||
| c087b97093 | |||
| 718a2e0c06 | |||
| b6187501fd | |||
| 18e1ab6db1 | |||
| 2dec76c87a | |||
| 35c189759c | |||
| 5c94b8680d | |||
| cebf3ded62 | |||
| b83ecf52f9 | |||
| 15ea584671 | |||
| dbf2c659d9 | |||
| 2b8062c55f | |||
| a390ee494e | |||
| c2cd5e01e1 | |||
| 8212e12e57 | |||
| 253ee2b887 | |||
| d7540700d4 | |||
| f103e85f88 | |||
| fe84639b17 | |||
| 5fdc9fb15e | |||
| 8967fa404e | |||
| a7e6fbf2d2 | |||
| 1f4b594ae7 | |||
| cff7ce072d | |||
| f5dcca0386 | |||
| 53e0b99d5f | |||
| 5f9cad5908 | |||
| 00629b39c4 | |||
| ca1e4d57b8 | |||
| f971e96dd5 | |||
| 81a1a624f1 | |||
| 7b7f9f353b | |||
| a3732a1e9a |
@@ -22,7 +22,10 @@ jobs:
|
|||||||
- name: Install build dependencies
|
- name: Install build dependencies
|
||||||
run: |
|
run: |
|
||||||
apt-get update -qq
|
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
|
# Seed: use the committed linux-amd64 binary as the bootstrap
|
||||||
- name: Bootstrap from committed linux binary (seed)
|
- name: Bootstrap from committed linux binary (seed)
|
||||||
@@ -84,13 +87,22 @@ jobs:
|
|||||||
bash tests/html_sanitizer/run.sh
|
bash tests/html_sanitizer/run.sh
|
||||||
|
|
||||||
# Native El test suites (elc --test, compile-link-run)
|
# 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)
|
- name: Run tests - native (core)
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||||
"$ELC" --test tests/native/test_core.el > /tmp/el_native_core.c
|
"$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
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
||||||
/tmp/el_native_core
|
/tmp/el_native_core
|
||||||
|
|
||||||
@@ -100,7 +112,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||||
"$ELC" --test tests/native/test_text.el > /tmp/el_native_text.c
|
"$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
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
||||||
/tmp/el_native_text
|
/tmp/el_native_text
|
||||||
|
|
||||||
@@ -110,7 +122,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||||
"$ELC" --test tests/native/test_string.el > /tmp/el_native_string.c
|
"$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
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
||||||
/tmp/el_native_string
|
/tmp/el_native_string
|
||||||
|
|
||||||
@@ -120,7 +132,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||||
"$ELC" --test tests/native/test_math.el > /tmp/el_native_math.c
|
"$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
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
||||||
/tmp/el_native_math
|
/tmp/el_native_math
|
||||||
|
|
||||||
@@ -130,7 +142,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||||
"$ELC" --test tests/native/test_state.el > /tmp/el_native_state.c
|
"$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
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
||||||
/tmp/el_native_state
|
/tmp/el_native_state
|
||||||
|
|
||||||
@@ -140,7 +152,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||||
"$ELC" --test tests/native/test_time.el > /tmp/el_native_time.c
|
"$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
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
||||||
/tmp/el_native_time
|
/tmp/el_native_time
|
||||||
|
|
||||||
@@ -150,7 +162,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||||
"$ELC" --test tests/native/test_json.el > /tmp/el_native_json.c
|
"$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
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
||||||
/tmp/el_native_json
|
/tmp/el_native_json
|
||||||
|
|
||||||
@@ -160,7 +172,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||||
"$ELC" --test tests/native/test_env.el > /tmp/el_native_env.c
|
"$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
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
||||||
/tmp/el_native_env
|
/tmp/el_native_env
|
||||||
|
|
||||||
@@ -170,7 +182,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/el-compiler/runtime"
|
||||||
"$ELC" --test tests/native/test_fs.el > /tmp/el_native_fs.c
|
"$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
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
||||||
/tmp/el_native_fs
|
/tmp/el_native_fs
|
||||||
|
|
||||||
@@ -203,9 +215,6 @@ jobs:
|
|||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
run: |
|
run: |
|
||||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
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 auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||||
gcloud config set project neuron-785695
|
gcloud config set project neuron-785695
|
||||||
|
|
||||||
@@ -252,4 +261,52 @@ jobs:
|
|||||||
--source=el-compiler/runtime/el_runtime.js
|
--source=el-compiler/runtime/el_runtime.js
|
||||||
|
|
||||||
echo "Published El SDK version=${VERSION} to foundation-dev"
|
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
|
rm -f /tmp/gcp-key.json
|
||||||
|
|||||||
@@ -246,4 +246,51 @@ jobs:
|
|||||||
--source=el-compiler/runtime/el_runtime.h
|
--source=el-compiler/runtime/el_runtime.h
|
||||||
|
|
||||||
echo "Published El SDK version=${VERSION} to foundation-stage"
|
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
|
rm -f /tmp/gcp-key.json
|
||||||
|
|||||||
@@ -1,46 +1,46 @@
|
|||||||
// auto-generated by elc --emit-header - do not edit
|
// 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_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: Any, key: String) -> String
|
extern fn lang_get(profile: [String], key: String) -> String
|
||||||
extern fn lang_profile_en() -> Any
|
extern fn lang_profile_en() -> [String]
|
||||||
extern fn lang_profile_ja() -> Any
|
extern fn lang_profile_ja() -> [String]
|
||||||
extern fn lang_profile_ar() -> Any
|
extern fn lang_profile_ar() -> [String]
|
||||||
extern fn lang_profile_zh() -> Any
|
extern fn lang_profile_zh() -> [String]
|
||||||
extern fn lang_profile_de() -> Any
|
extern fn lang_profile_de() -> [String]
|
||||||
extern fn lang_profile_es() -> Any
|
extern fn lang_profile_es() -> [String]
|
||||||
extern fn lang_profile_fi() -> Any
|
extern fn lang_profile_fi() -> [String]
|
||||||
extern fn lang_profile_sw() -> Any
|
extern fn lang_profile_sw() -> [String]
|
||||||
extern fn lang_profile_hi() -> Any
|
extern fn lang_profile_hi() -> [String]
|
||||||
extern fn lang_profile_ru() -> Any
|
extern fn lang_profile_ru() -> [String]
|
||||||
extern fn lang_profile_fr() -> Any
|
extern fn lang_profile_fr() -> [String]
|
||||||
extern fn lang_profile_la() -> Any
|
extern fn lang_profile_la() -> [String]
|
||||||
extern fn lang_profile_he() -> Any
|
extern fn lang_profile_he() -> [String]
|
||||||
extern fn lang_profile_sa() -> Any
|
extern fn lang_profile_sa() -> [String]
|
||||||
extern fn lang_profile_got() -> Any
|
extern fn lang_profile_got() -> [String]
|
||||||
extern fn lang_profile_non() -> Any
|
extern fn lang_profile_non() -> [String]
|
||||||
extern fn lang_profile_enm() -> Any
|
extern fn lang_profile_enm() -> [String]
|
||||||
extern fn lang_profile_pi() -> Any
|
extern fn lang_profile_pi() -> [String]
|
||||||
extern fn lang_profile_grc() -> Any
|
extern fn lang_profile_grc() -> [String]
|
||||||
extern fn lang_profile_ang() -> Any
|
extern fn lang_profile_ang() -> [String]
|
||||||
extern fn lang_profile_fro() -> Any
|
extern fn lang_profile_fro() -> [String]
|
||||||
extern fn lang_profile_goh() -> Any
|
extern fn lang_profile_goh() -> [String]
|
||||||
extern fn lang_profile_sga() -> Any
|
extern fn lang_profile_sga() -> [String]
|
||||||
extern fn lang_profile_txb() -> Any
|
extern fn lang_profile_txb() -> [String]
|
||||||
extern fn lang_profile_peo() -> Any
|
extern fn lang_profile_peo() -> [String]
|
||||||
extern fn lang_profile_akk() -> Any
|
extern fn lang_profile_akk() -> [String]
|
||||||
extern fn lang_profile_uga() -> Any
|
extern fn lang_profile_uga() -> [String]
|
||||||
extern fn lang_profile_egy() -> Any
|
extern fn lang_profile_egy() -> [String]
|
||||||
extern fn lang_profile_sux() -> Any
|
extern fn lang_profile_sux() -> [String]
|
||||||
extern fn lang_profile_gez() -> Any
|
extern fn lang_profile_gez() -> [String]
|
||||||
extern fn lang_profile_cop() -> Any
|
extern fn lang_profile_cop() -> [String]
|
||||||
extern fn lang_from_code(code: String) -> Any
|
extern fn lang_from_code(code: String) -> [String]
|
||||||
extern fn lang_default() -> Any
|
extern fn lang_default() -> [String]
|
||||||
extern fn lang_is_isolating(profile: Any) -> Bool
|
extern fn lang_is_isolating(profile: [String]) -> Bool
|
||||||
extern fn lang_is_agglutinative(profile: Any) -> Bool
|
extern fn lang_is_agglutinative(profile: [String]) -> Bool
|
||||||
extern fn lang_is_fusional(profile: Any) -> Bool
|
extern fn lang_is_fusional(profile: [String]) -> Bool
|
||||||
extern fn lang_is_polysynthetic(profile: Any) -> Bool
|
extern fn lang_is_polysynthetic(profile: [String]) -> Bool
|
||||||
extern fn lang_is_rtl(profile: Any) -> Bool
|
extern fn lang_is_rtl(profile: [String]) -> Bool
|
||||||
extern fn lang_has_null_subject(profile: Any) -> Bool
|
extern fn lang_has_null_subject(profile: [String]) -> Bool
|
||||||
extern fn lang_has_case(profile: Any) -> Bool
|
extern fn lang_has_case(profile: [String]) -> Bool
|
||||||
extern fn lang_has_gender(profile: Any) -> Bool
|
extern fn lang_has_gender(profile: [String]) -> Bool
|
||||||
extern fn lang_word_order(profile: Any) -> String
|
extern fn lang_word_order(profile: [String]) -> String
|
||||||
extern fn lang_code(profile: Any) -> String
|
extern fn lang_code(profile: [String]) -> String
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn akk_str_ends(s: String, suf: String) -> Bool {
|
fn akk_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn akk_str_len(s: String) -> Int
|
extern fn akk_str_len(s: String) -> Int
|
||||||
extern fn akk_str_drop_last(s: String, n: Int) -> String
|
extern fn akk_str_drop_last(s: String, n: Int) -> String
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn ang_str_ends(s: String, suf: String) -> Bool {
|
fn ang_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn ang_str_drop_last(s: String, n: Int) -> String
|
extern fn ang_str_drop_last(s: String, n: Int) -> String
|
||||||
extern fn ang_str_last_char(s: String) -> String
|
extern fn ang_str_last_char(s: String) -> String
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
// ── String helpers ────────────────────────────────────────────────────────────
|
// ── String helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn ar_str_ends(s: String, suf: String) -> Bool {
|
fn ar_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn ar_str_len(s: String) -> Int
|
extern fn ar_str_len(s: String) -> Int
|
||||||
extern fn ar_str_drop_last(s: String, n: Int) -> String
|
extern fn ar_str_drop_last(s: String, n: Int) -> String
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
|
|
||||||
// ── String helpers ──────────────────────────────────────────────────────────────
|
// ── String helpers ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn cop_str_ends(s: String, suf: String) -> Bool {
|
fn cop_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn cop_str_len(s: String) -> Int
|
extern fn cop_str_len(s: String) -> Int
|
||||||
extern fn cop_drop(s: String, n: Int) -> String
|
extern fn cop_drop(s: String, n: Int) -> String
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
// Dat: dem der dem den
|
// Dat: dem der dem den
|
||||||
// Gen: des der des der
|
// Gen: des der des der
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn de_article_def(gender: String, gram_case: String, number: String) -> String {
|
fn de_article_def(gender: String, gram_case: String, number: String) -> String {
|
||||||
if str_eq(number, "pl") {
|
if str_eq(number, "pl") {
|
||||||
if str_eq(gram_case, "nom") { return "die" }
|
if str_eq(gram_case, "nom") { return "die" }
|
||||||
|
|||||||
@@ -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_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_indef(gender: String, gram_case: String, number: String) -> String
|
||||||
extern fn de_article(gender: String, gram_case: String, number: String, definite: String) -> String
|
extern fn de_article(gender: String, gram_case: String, number: String, definite: String) -> String
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
|
|
||||||
// ── String helpers ──────────────────────────────────────────────────────────────
|
// ── String helpers ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn egy_str_ends(s: String, suf: String) -> Bool {
|
fn egy_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn egy_str_len(s: String) -> Int
|
extern fn egy_str_len(s: String) -> Int
|
||||||
extern fn egy_drop(s: String, n: Int) -> String
|
extern fn egy_drop(s: String, n: Int) -> String
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn enm_str_ends(s: String, suf: String) -> Bool {
|
fn enm_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_str_ends(s: String, suf: String) -> Bool
|
||||||
extern fn enm_drop(s: String, n: Int) -> String
|
extern fn enm_drop(s: String, n: Int) -> String
|
||||||
extern fn enm_first_char(s: String) -> String
|
extern fn enm_first_char(s: String) -> String
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
// ── String helpers (local, matching morphology.el conventions) ────────────────
|
// ── String helpers (local, matching morphology.el conventions) ────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn es_str_ends(s: String, suf: String) -> Bool {
|
fn es_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn es_str_drop_last(s: String, n: Int) -> String
|
extern fn es_str_drop_last(s: String, n: Int) -> String
|
||||||
extern fn es_str_last_char(s: String) -> String
|
extern fn es_str_last_char(s: String) -> String
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
// If only neutral vowels are found, default to "front" (the conservative choice
|
// If only neutral vowels are found, default to "front" (the conservative choice
|
||||||
// for borrowed words and those without clear back vowels).
|
// for borrowed words and those without clear back vowels).
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn fi_harmony(word: String) -> String {
|
fn fi_harmony(word: String) -> String {
|
||||||
let n: Int = str_len(word)
|
let n: Int = str_len(word)
|
||||||
let i: Int = n - 1
|
let i: Int = n - 1
|
||||||
|
|||||||
@@ -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_harmony(word: String) -> String
|
||||||
extern fn fi_suffix(base: String, harmony: 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_noun_case(stem: String, gram_case: String, number: String, harmony: String) -> String
|
||||||
extern fn fi_str_last_char(s: 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_apply_case(noun: String, gram_case: String, number: String) -> String
|
||||||
extern fn fi_verb_stem(dict_form: 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_present_ending(stem: String, person: String, number: String, harmony: String) -> String
|
||||||
extern fn fi_past_stem(stem: String) -> String
|
extern fn fi_past_stem(stem: String) -> String
|
||||||
extern fn fi_past_ending(stem: String, person: String, number: String, harmony: 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_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||||
extern fn fi_question_suffix(harmony: String) -> String
|
extern fn fi_question_suffix(harmony: String) -> String
|
||||||
extern fn fi_make_question(verb_form: String, 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]
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
// ── String helpers (local, matching morphology.el conventions) ────────────────
|
// ── String helpers (local, matching morphology.el conventions) ────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn fr_str_ends(s: String, suf: String) -> Bool {
|
fn fr_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn fr_str_drop_last(s: String, n: Int) -> String
|
extern fn fr_str_drop_last(s: String, n: Int) -> String
|
||||||
extern fn fr_str_last_char(s: String) -> String
|
extern fn fr_str_last_char(s: String) -> String
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn fro_str_ends(s: String, suf: String) -> Bool {
|
fn fro_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_str_ends(s: String, suf: String) -> Bool
|
||||||
extern fn fro_drop(s: String, n: Int) -> String
|
extern fn fro_drop(s: String, n: Int) -> String
|
||||||
extern fn fro_slot(person: String, number: String) -> Int
|
extern fn fro_slot(person: String, number: String) -> Int
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn gez_str_ends(s: String, suf: String) -> Bool {
|
fn gez_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn gez_str_len(s: String) -> Int
|
extern fn gez_str_len(s: String) -> Int
|
||||||
extern fn gez_str_drop_last(s: String, n: Int) -> String
|
extern fn gez_str_drop_last(s: String, n: Int) -> String
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn goh_str_ends(s: String, suf: String) -> Bool {
|
fn goh_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_str_ends(s: String, suf: String) -> Bool
|
||||||
extern fn goh_drop(s: String, n: Int) -> String
|
extern fn goh_drop(s: String, n: Int) -> String
|
||||||
extern fn goh_slot(person: String, number: String) -> Int
|
extern fn goh_slot(person: String, number: String) -> Int
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn got_str_ends(s: String, suf: String) -> Bool {
|
fn got_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn got_str_drop_last(s: String, n: Int) -> String
|
extern fn got_str_drop_last(s: String, n: Int) -> String
|
||||||
extern fn got_slot(person: String, number: String) -> Int
|
extern fn got_slot(person: String, number: String) -> Int
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn grc_str_ends(s: String, suf: String) -> Bool {
|
fn grc_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn grc_str_drop_last(s: String, n: Int) -> String
|
extern fn grc_str_drop_last(s: String, n: Int) -> String
|
||||||
extern fn grc_str_last_char(s: String) -> String
|
extern fn grc_str_last_char(s: String) -> String
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn he_str_ends(s: String, suf: String) -> Bool {
|
fn he_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn he_str_len(s: String) -> Int
|
extern fn he_str_len(s: String) -> Int
|
||||||
extern fn he_str_drop_last(s: String, n: Int) -> String
|
extern fn he_str_drop_last(s: String, n: Int) -> String
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
// ── String helpers ────────────────────────────────────────────────────────────
|
// ── String helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn hi_str_ends(s: String, suf: String) -> Bool {
|
fn hi_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn hi_str_drop_last(s: String, n: Int) -> String
|
extern fn hi_str_drop_last(s: String, n: Int) -> String
|
||||||
extern fn hi_str_last_char(s: String) -> String
|
extern fn hi_str_last_char(s: String) -> String
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
// Note: this is a heuristic classifier for romanized input. For production use
|
// Note: this is a heuristic classifier for romanized input. For production use
|
||||||
// with native kana/kanji forms, the dictionary form (辞書形) must be consulted.
|
// with native kana/kanji forms, the dictionary form (辞書形) must be consulted.
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn ja_verb_group(dict_form: String) -> String {
|
fn ja_verb_group(dict_form: String) -> String {
|
||||||
// Irregular verbs (exact match on dictionary form)
|
// Irregular verbs (exact match on dictionary form)
|
||||||
if str_eq(dict_form, "する") { return "irregular" }
|
if str_eq(dict_form, "する") { return "irregular" }
|
||||||
|
|||||||
@@ -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_verb_group(dict_form: String) -> String
|
||||||
extern fn ja_ichidan_stem(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
|
extern fn ja_godan_stem_change(dict_form: String, row: String) -> String
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn la_str_ends(s: String, suf: String) -> Bool {
|
fn la_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn la_str_drop_last(s: String, n: Int) -> String
|
extern fn la_str_drop_last(s: String, n: Int) -> String
|
||||||
extern fn la_str_last_char(s: String) -> String
|
extern fn la_str_last_char(s: String) -> String
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn non_str_ends(s: String, suf: String) -> Bool {
|
fn non_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_str_ends(s: String, suf: String) -> Bool
|
||||||
extern fn non_drop(s: String, n: Int) -> String
|
extern fn non_drop(s: String, n: Int) -> String
|
||||||
extern fn non_last(s: String) -> String
|
extern fn non_last(s: String) -> String
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn peo_drop(s: String, n: Int) -> String {
|
fn peo_drop(s: String, n: Int) -> String {
|
||||||
let len: Int = str_len(s)
|
let len: Int = str_len(s)
|
||||||
if n >= len { return "" }
|
if n >= len { return "" }
|
||||||
|
|||||||
@@ -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_drop(s: String, n: Int) -> String
|
||||||
extern fn peo_ends(s: String, suf: String) -> Bool
|
extern fn peo_ends(s: String, suf: String) -> Bool
|
||||||
extern fn peo_slot(person: String, number: String) -> Int
|
extern fn peo_slot(person: String, number: String) -> Int
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn pi_str_ends(s: String, suf: String) -> Bool {
|
fn pi_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_str_ends(s: String, suf: String) -> Bool
|
||||||
extern fn pi_drop(s: String, n: Int) -> String
|
extern fn pi_drop(s: String, n: Int) -> String
|
||||||
extern fn pi_last_char(s: String) -> String
|
extern fn pi_last_char(s: String) -> String
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
// The heuristic returns the most probable gender. Caller should override
|
// The heuristic returns the most probable gender. Caller should override
|
||||||
// for known exceptions (путь, рубль are masc despite -ь).
|
// for known exceptions (путь, рубль are masc despite -ь).
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn ru_gender(noun: String) -> String {
|
fn ru_gender(noun: String) -> String {
|
||||||
let n: Int = str_len(noun)
|
let n: Int = str_len(noun)
|
||||||
if n == 0 { return "m" }
|
if n == 0 { return "m" }
|
||||||
|
|||||||
@@ -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_gender(noun: String) -> String
|
||||||
extern fn ru_stem_type(noun: String, gender: 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
|
extern fn ru_noun_case(noun: String, gender: String, gram_case: String, number: String) -> String
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn sa_str_ends(s: String, suf: String) -> Bool {
|
fn sa_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn sa_str_drop_last(s: String, n: Int) -> String
|
extern fn sa_str_drop_last(s: String, n: Int) -> String
|
||||||
extern fn sa_slot(person: String, number: String) -> Int
|
extern fn sa_slot(person: String, number: String) -> Int
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn sga_drop(s: String, n: Int) -> String {
|
fn sga_drop(s: String, n: Int) -> String {
|
||||||
let len: Int = str_len(s)
|
let len: Int = str_len(s)
|
||||||
if n >= len { return "" }
|
if n >= len { return "" }
|
||||||
|
|||||||
@@ -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_drop(s: String, n: Int) -> String
|
||||||
extern fn sga_first(s: String) -> String
|
extern fn sga_first(s: String) -> String
|
||||||
extern fn sga_rest(s: String) -> String
|
extern fn sga_rest(s: String) -> String
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn sux_str_ends(s: String, suf: String) -> Bool {
|
fn sux_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn sux_str_drop_last(s: String, n: Int) -> String
|
extern fn sux_str_drop_last(s: String, n: Int) -> String
|
||||||
extern fn sux_str_last_char(s: String) -> String
|
extern fn sux_str_last_char(s: String) -> String
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
// ── String helpers ────────────────────────────────────────────────────────────
|
// ── String helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn sw_str_ends(s: String, suf: String) -> Bool {
|
fn sw_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn sw_str_drop_last(s: String, n: Int) -> String
|
extern fn sw_str_drop_last(s: String, n: Int) -> String
|
||||||
extern fn sw_str_first_char(s: String) -> String
|
extern fn sw_str_first_char(s: String) -> String
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn txb_drop(s: String, n: Int) -> String {
|
fn txb_drop(s: String, n: Int) -> String {
|
||||||
let len: Int = str_len(s)
|
let len: Int = str_len(s)
|
||||||
if n >= len { return "" }
|
if n >= len { return "" }
|
||||||
|
|||||||
@@ -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_drop(s: String, n: Int) -> String
|
||||||
extern fn txb_ends(s: String, suf: String) -> Bool
|
extern fn txb_ends(s: String, suf: String) -> Bool
|
||||||
extern fn txb_slot(person: String, number: String) -> Int
|
extern fn txb_slot(person: String, number: String) -> Int
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
|
|
||||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import "morphology.el"
|
||||||
fn uga_str_ends(s: String, suf: String) -> Bool {
|
fn uga_str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn uga_str_len(s: String) -> Int
|
extern fn uga_str_len(s: String) -> Int
|
||||||
extern fn uga_str_drop_last(s: String, n: Int) -> String
|
extern fn uga_str_drop_last(s: String, n: Int) -> String
|
||||||
|
|||||||
@@ -33,6 +33,17 @@
|
|||||||
|
|
||||||
// ── String helpers ────────────────────────────────────────────────────────────
|
// ── 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 {
|
fn str_ends(s: String, suf: String) -> Bool {
|
||||||
return str_ends_with(s, suf)
|
return str_ends_with(s, suf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_ends(s: String, suf: String) -> Bool
|
||||||
extern fn str_last_char(s: String) -> String
|
extern fn str_last_char(s: String) -> String
|
||||||
extern fn str_last2(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 morph_apply_suffix(base: String, suffix: String) -> String
|
||||||
extern fn en_irregular_plural(word: String) -> String
|
extern fn en_irregular_plural(word: String) -> String
|
||||||
extern fn en_irregular_singular(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_verb_3sg(base: String) -> String
|
||||||
extern fn en_should_double_final(base: String) -> Bool
|
extern fn en_should_double_final(base: String) -> Bool
|
||||||
extern fn en_verb_past(base: String) -> String
|
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_pluralize_regular(singular: String) -> String
|
||||||
extern fn en_verb_form(base: String, tense: String, person: String, number: 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 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_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_conjugate(verb: String, tense: String, person: String, number: String, profile: [String]) -> String
|
||||||
extern fn morph_inflect(word: String, features: String, profile: Any) -> String
|
extern fn morph_inflect(word: String, features: String, profile: [String]) -> String
|
||||||
extern fn pluralize(singular: String) -> String
|
extern fn pluralize(singular: String) -> String
|
||||||
extern fn singularize(plural: String) -> String
|
extern fn singularize(plural: String) -> String
|
||||||
extern fn verb_form(base: String, tense: String, person: String, number: String) -> String
|
extern fn verb_form(base: String, tense: String, person: String, number: String) -> String
|
||||||
|
|||||||
+19
-19
@@ -1,20 +1,20 @@
|
|||||||
// auto-generated by elc --emit-header - do not edit
|
// auto-generated by elc --emit-header — do not edit
|
||||||
extern fn lex_word(entry: Any) -> String
|
extern fn lex_word(entry: [String]) -> String
|
||||||
extern fn lex_pos(entry: Any) -> String
|
extern fn lex_pos(entry: [String]) -> String
|
||||||
extern fn lex_form(entry: Any, idx: Int) -> String
|
extern fn lex_form(entry: [String], idx: Int) -> String
|
||||||
extern fn lex_class(entry: Any) -> 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) -> Any
|
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) -> Any
|
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) -> Any
|
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) -> Any
|
extern fn make_entry1(word: String, pos: String, f0: String, cls: String) -> [String]
|
||||||
extern fn build_vocab() -> Any
|
extern fn build_vocab() -> [[String]]
|
||||||
extern fn get_vocab() -> Any
|
extern fn get_vocab() -> [[String]]
|
||||||
extern fn vocab_lookup(word: String, lang_code: String) -> Any
|
extern fn vocab_lookup(word: String, lang_code: String) -> [String]
|
||||||
extern fn vocab_lookup_en(word: String) -> Any
|
extern fn vocab_lookup_en(word: String) -> [String]
|
||||||
extern fn vocab_synonym(word: String, lang_register: String, lang_code: 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_pos(pos: String) -> [[String]]
|
||||||
extern fn vocab_by_class(cls: String) -> Any
|
extern fn vocab_by_class(cls: String) -> [[String]]
|
||||||
extern fn entry_found(entry: Any) -> Bool
|
extern fn entry_found(entry: [String]) -> Bool
|
||||||
extern fn entry_word(entry: Any) -> String
|
extern fn entry_word(entry: [String]) -> String
|
||||||
extern fn entry_pos(entry: Any) -> String
|
extern fn entry_pos(entry: [String]) -> String
|
||||||
extern fn entry_form(entry: Any, n: Int) -> String
|
extern fn entry_form(entry: [String], n: Int) -> String
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -48,7 +48,7 @@
|
|||||||
/* ── socket close ─────────────────────────────────────────────────────────── */
|
/* ── socket close ─────────────────────────────────────────────────────────── */
|
||||||
/* Winsock closes sockets with closesocket(), not close() (close() is for file fds). The POSIX
|
/* Winsock closes sockets with closesocket(), not close() (close() is for file fds). The POSIX
|
||||||
build defines the same helper as close() so the call sites are identical across platforms. */
|
build defines the same helper as close() so the call sites are identical across platforms. */
|
||||||
static inline int el_closesocket(int s) { return closesocket((SOCKET)s); }
|
static inline int el_closesocket(SOCKET s) { return closesocket(s); }
|
||||||
|
|
||||||
/* ── winsock init (once, at load) ─────────────────────────────────────────── */
|
/* ── winsock init (once, at load) ─────────────────────────────────────────── */
|
||||||
static void el__win_net_init(void) {
|
static void el__win_net_init(void) {
|
||||||
@@ -76,12 +76,35 @@ static inline void* el_win_dlsym(void* handle, const char* name) {
|
|||||||
#define mkdir(path, mode) _mkdir(path) /* POSIX mkdir(path,mode) → _mkdir(path) */
|
#define mkdir(path, mode) _mkdir(path) /* POSIX mkdir(path,mode) → _mkdir(path) */
|
||||||
#define timegm _mkgmtime /* UTC tm → time_t */
|
#define timegm _mkgmtime /* UTC tm → time_t */
|
||||||
|
|
||||||
/* setenv/unsetenv: not in the Windows CRT; map to _putenv_s. */
|
/* setenv/unsetenv: not in the Windows CRT; map to _putenv_s / SetEnvironmentVariable. */
|
||||||
static inline int setenv(const char* name, const char* value, int overwrite) {
|
static inline int setenv(const char* name, const char* value, int overwrite) {
|
||||||
(void)overwrite;
|
(void)overwrite;
|
||||||
return _putenv_s(name, value ? value : "");
|
return _putenv_s(name, value ? value : "");
|
||||||
}
|
}
|
||||||
static inline int unsetenv(const char* name) { return _putenv_s(name, ""); }
|
static inline int unsetenv(const char* name) {
|
||||||
|
/* _putenv_s(name, "") sets VAR="" rather than removing it.
|
||||||
|
* SetEnvironmentVariableA(name, NULL) truly deletes it from the Win32
|
||||||
|
* env block; then we sync the CRT cache with _putenv("NAME="). */
|
||||||
|
SetEnvironmentVariableA(name, NULL);
|
||||||
|
size_t len = strlen(name);
|
||||||
|
char *buf = (char*)malloc(len + 2);
|
||||||
|
if (!buf) return -1;
|
||||||
|
memcpy(buf, name, len);
|
||||||
|
buf[len] = '=';
|
||||||
|
buf[len + 1] = '\0';
|
||||||
|
_putenv(buf);
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* nanosleep — not available in MSVC/UCRT; approximate with Sleep(). */
|
||||||
|
static inline int el_nanosleep(const struct timespec *req, struct timespec *rem) {
|
||||||
|
(void)rem;
|
||||||
|
DWORD ms = (DWORD)((req->tv_sec * 1000ULL) + (req->tv_nsec / 1000000ULL));
|
||||||
|
Sleep(ms ? ms : 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#define nanosleep(req, rem) el_nanosleep((req), (rem))
|
||||||
|
|
||||||
/* localtime_r/gmtime_r: Windows offers localtime_s/gmtime_s with reversed arg order. */
|
/* localtime_r/gmtime_r: Windows offers localtime_s/gmtime_s with reversed arg order. */
|
||||||
static inline struct tm* localtime_r(const time_t* t, struct tm* out) {
|
static inline struct tm* localtime_r(const time_t* t, struct tm* out) {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <sys/resource.h> /* getrusage — memory guard */
|
||||||
/* On POSIX, sockets close with the same close() as files; el_platform_win.h supplies the Windows
|
/* On POSIX, sockets close with the same close() as files; el_platform_win.h supplies the Windows
|
||||||
variant. Defined here so the socket call sites are identical across platforms. */
|
variant. Defined here so the socket call sites are identical across platforms. */
|
||||||
static inline int el_closesocket(int s) { return close(s); }
|
static inline int el_closesocket(int s) { return close(s); }
|
||||||
@@ -1061,7 +1062,6 @@ el_val_t http_post_to_file(el_val_t url, el_val_t body, el_val_t headers_map, el
|
|||||||
|
|
||||||
#define HTTP_MAX_CONNS 64
|
#define HTTP_MAX_CONNS 64
|
||||||
|
|
||||||
typedef el_val_t (*http_handler_fn)(el_val_t method, el_val_t path, el_val_t body);
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char* name;
|
char* name;
|
||||||
@@ -1536,12 +1536,20 @@ static void http_send_response(int fd, const char* body) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
#ifdef _WIN32
|
||||||
|
SOCKET fd;
|
||||||
|
#else
|
||||||
int fd;
|
int fd;
|
||||||
|
#endif
|
||||||
} HttpWorkerArg;
|
} HttpWorkerArg;
|
||||||
|
|
||||||
static void* http_worker(void* arg) {
|
static void* http_worker(void* arg) {
|
||||||
HttpWorkerArg* a = (HttpWorkerArg*)arg;
|
HttpWorkerArg* a = (HttpWorkerArg*)arg;
|
||||||
|
#ifdef _WIN32
|
||||||
|
SOCKET fd = a->fd;
|
||||||
|
#else
|
||||||
int fd = a->fd;
|
int fd = a->fd;
|
||||||
|
#endif
|
||||||
free(a);
|
free(a);
|
||||||
char *method = NULL, *path = NULL, *body = NULL;
|
char *method = NULL, *path = NULL, *body = NULL;
|
||||||
if (http_read_request(fd, &method, &path, &body, NULL) == 0) {
|
if (http_read_request(fd, &method, &path, &body, NULL) == 0) {
|
||||||
@@ -1573,7 +1581,7 @@ static void* http_worker(void* arg) {
|
|||||||
free(response);
|
free(response);
|
||||||
}
|
}
|
||||||
free(method); free(path); free(body);
|
free(method); free(path); free(body);
|
||||||
close(fd);
|
el_closesocket(fd);
|
||||||
/* release a slot */
|
/* release a slot */
|
||||||
pthread_mutex_lock(&_http_conn_mu);
|
pthread_mutex_lock(&_http_conn_mu);
|
||||||
_http_conn_active--;
|
_http_conn_active--;
|
||||||
@@ -1610,7 +1618,11 @@ el_val_t http_serve(el_val_t port, el_val_t handler) {
|
|||||||
while (1) {
|
while (1) {
|
||||||
struct sockaddr_in6 cli;
|
struct sockaddr_in6 cli;
|
||||||
socklen_t clen = sizeof(cli);
|
socklen_t clen = sizeof(cli);
|
||||||
|
#ifdef _WIN32
|
||||||
|
SOCKET cfd = accept(sock, (struct sockaddr*)&cli, &clen);
|
||||||
|
#else
|
||||||
int cfd = accept(sock, (struct sockaddr*)&cli, &clen);
|
int cfd = accept(sock, (struct sockaddr*)&cli, &clen);
|
||||||
|
#endif
|
||||||
if (cfd < 0) {
|
if (cfd < 0) {
|
||||||
if (errno == EINTR) continue;
|
if (errno == EINTR) continue;
|
||||||
perror("accept"); break;
|
perror("accept"); break;
|
||||||
@@ -1656,8 +1668,6 @@ el_val_t http_serve(el_val_t port, el_val_t handler) {
|
|||||||
* separate active-handler slot, separate dlsym fallback. Mixing v1 and v2
|
* separate active-handler slot, separate dlsym fallback. Mixing v1 and v2
|
||||||
* handlers in the same process is fine — they don't share the active slot. */
|
* handlers in the same process is fine — they don't share the active slot. */
|
||||||
|
|
||||||
typedef el_val_t (*http_handler4_fn)(el_val_t method, el_val_t path,
|
|
||||||
el_val_t headers_map, el_val_t body);
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char* name;
|
char* name;
|
||||||
@@ -1793,7 +1803,11 @@ static el_val_t http_build_headers_map(const char* hdr_block) {
|
|||||||
|
|
||||||
static void* http_worker_v2(void* arg) {
|
static void* http_worker_v2(void* arg) {
|
||||||
HttpWorkerArg* a = (HttpWorkerArg*)arg;
|
HttpWorkerArg* a = (HttpWorkerArg*)arg;
|
||||||
|
#ifdef _WIN32
|
||||||
|
SOCKET fd = a->fd;
|
||||||
|
#else
|
||||||
int fd = a->fd;
|
int fd = a->fd;
|
||||||
|
#endif
|
||||||
free(a);
|
free(a);
|
||||||
char *method = NULL, *path = NULL, *body = NULL, *hdr_block = NULL;
|
char *method = NULL, *path = NULL, *body = NULL, *hdr_block = NULL;
|
||||||
if (http_read_request(fd, &method, &path, &body, &hdr_block) == 0) {
|
if (http_read_request(fd, &method, &path, &body, &hdr_block) == 0) {
|
||||||
@@ -1823,7 +1837,7 @@ static void* http_worker_v2(void* arg) {
|
|||||||
free(response);
|
free(response);
|
||||||
}
|
}
|
||||||
free(method); free(path); free(body); free(hdr_block);
|
free(method); free(path); free(body); free(hdr_block);
|
||||||
close(fd);
|
el_closesocket(fd);
|
||||||
pthread_mutex_lock(&_http_conn_mu);
|
pthread_mutex_lock(&_http_conn_mu);
|
||||||
_http_conn_active--;
|
_http_conn_active--;
|
||||||
pthread_cond_signal(&_http_conn_cv);
|
pthread_cond_signal(&_http_conn_cv);
|
||||||
@@ -1860,7 +1874,11 @@ el_val_t http_serve_v2(el_val_t port, el_val_t handler) {
|
|||||||
while (1) {
|
while (1) {
|
||||||
struct sockaddr_in6 cli;
|
struct sockaddr_in6 cli;
|
||||||
socklen_t clen = sizeof(cli);
|
socklen_t clen = sizeof(cli);
|
||||||
|
#ifdef _WIN32
|
||||||
|
SOCKET cfd = accept(sock, (struct sockaddr*)&cli, &clen);
|
||||||
|
#else
|
||||||
int cfd = accept(sock, (struct sockaddr*)&cli, &clen);
|
int cfd = accept(sock, (struct sockaddr*)&cli, &clen);
|
||||||
|
#endif
|
||||||
if (cfd < 0) {
|
if (cfd < 0) {
|
||||||
if (errno == EINTR) continue;
|
if (errno == EINTR) continue;
|
||||||
perror("accept"); break;
|
perror("accept"); break;
|
||||||
@@ -1889,6 +1907,83 @@ el_val_t http_serve_v2(el_val_t port, el_val_t handler) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── http_serve_async — non-blocking HTTP server ─────────────────────────── */
|
||||||
|
/* Runs the accept loop in a background pthread, returns immediately so the
|
||||||
|
* calling EL script can continue (e.g. to run an awareness loop).
|
||||||
|
*
|
||||||
|
* El signature: http_serve_async(port, handler) -> Void */
|
||||||
|
|
||||||
|
typedef struct { int sock; } HttpServeAsyncArg;
|
||||||
|
|
||||||
|
static void* _http_serve_async_loop(void* raw) {
|
||||||
|
HttpServeAsyncArg* a = (HttpServeAsyncArg*)raw;
|
||||||
|
int sock = a->sock;
|
||||||
|
free(a);
|
||||||
|
while (1) {
|
||||||
|
struct sockaddr_in6 cli;
|
||||||
|
socklen_t clen = sizeof(cli);
|
||||||
|
int cfd = accept(sock, (struct sockaddr*)&cli, &clen);
|
||||||
|
if (cfd < 0) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
perror("accept"); break;
|
||||||
|
}
|
||||||
|
pthread_mutex_lock(&_http_conn_mu);
|
||||||
|
while (_http_conn_active >= HTTP_MAX_CONNS) {
|
||||||
|
pthread_cond_wait(&_http_conn_cv, &_http_conn_mu);
|
||||||
|
}
|
||||||
|
_http_conn_active++;
|
||||||
|
pthread_mutex_unlock(&_http_conn_mu);
|
||||||
|
HttpWorkerArg* arg = malloc(sizeof(HttpWorkerArg));
|
||||||
|
if (!arg) { close(cfd); continue; }
|
||||||
|
arg->fd = cfd;
|
||||||
|
pthread_t tid;
|
||||||
|
if (pthread_create(&tid, NULL, http_worker, arg) != 0) {
|
||||||
|
close(cfd); free(arg);
|
||||||
|
pthread_mutex_lock(&_http_conn_mu);
|
||||||
|
_http_conn_active--;
|
||||||
|
pthread_cond_signal(&_http_conn_cv);
|
||||||
|
pthread_mutex_unlock(&_http_conn_mu);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pthread_detach(tid);
|
||||||
|
}
|
||||||
|
close(sock);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_serve_async(el_val_t port, el_val_t handler) {
|
||||||
|
const char* hname = EL_CSTR(handler);
|
||||||
|
if (hname && looks_like_string(handler)) {
|
||||||
|
http_set_handler(handler);
|
||||||
|
}
|
||||||
|
int p = (int)port;
|
||||||
|
if (p <= 0 || p > 65535) { fprintf(stderr, "http_serve_async: invalid port %d\n", p); return; }
|
||||||
|
int sock = socket(AF_INET6, SOCK_STREAM, 0);
|
||||||
|
if (sock < 0) { perror("socket"); return; }
|
||||||
|
int yes = 1; int no = 0;
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||||
|
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &no, sizeof(no));
|
||||||
|
struct sockaddr_in6 addr;
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sin6_family = AF_INET6;
|
||||||
|
addr.sin6_addr = in6addr_any;
|
||||||
|
addr.sin6_port = htons((uint16_t)p);
|
||||||
|
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||||
|
perror("bind"); close(sock); return;
|
||||||
|
}
|
||||||
|
if (listen(sock, 64) < 0) { perror("listen"); close(sock); return; }
|
||||||
|
fprintf(stderr, "[http] async listening on [::]:%d (dual-stack)\n", p);
|
||||||
|
HttpServeAsyncArg* a = malloc(sizeof(HttpServeAsyncArg));
|
||||||
|
if (!a) { close(sock); return; }
|
||||||
|
a->sock = sock;
|
||||||
|
pthread_t tid;
|
||||||
|
if (pthread_create(&tid, NULL, _http_serve_async_loop, a) != 0) {
|
||||||
|
perror("pthread_create"); free(a); close(sock); return;
|
||||||
|
}
|
||||||
|
pthread_detach(tid);
|
||||||
|
/* Returns immediately — caller can now run awareness_run() or any loop. */
|
||||||
|
}
|
||||||
|
|
||||||
/* Build the response envelope a 4-arg handler can return. We hand-write
|
/* Build the response envelope a 4-arg handler can return. We hand-write
|
||||||
* the JSON so the discriminator key always lands first — the runtime's
|
* the JSON so the discriminator key always lands first — the runtime's
|
||||||
* http_parse_envelope() detects it via prefix match. headers_json must be
|
* http_parse_envelope() detects it via prefix match. headers_json must be
|
||||||
@@ -2267,6 +2362,43 @@ el_val_t url_decode(el_val_t sv) {
|
|||||||
return el_wrap_str(out);
|
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 ────────────────────────────────────────────────
|
/* ── HTML allowlist sanitizer ────────────────────────────────────────────────
|
||||||
* el_html_sanitize(input, allowlist_json)
|
* el_html_sanitize(input, allowlist_json)
|
||||||
*
|
*
|
||||||
@@ -3161,23 +3293,49 @@ static void jb_puts(JsonBuf* b, const char* s) {
|
|||||||
|
|
||||||
static void jb_emit_escaped(JsonBuf* b, const char* s) {
|
static void jb_emit_escaped(JsonBuf* b, const char* s) {
|
||||||
jb_putc(b, '"');
|
jb_putc(b, '"');
|
||||||
for (; *s; s++) {
|
const unsigned char* p = (const unsigned char*)s;
|
||||||
unsigned char c = (unsigned char)*s;
|
while (*p) {
|
||||||
|
unsigned char c = *p;
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '"': jb_puts(b, "\\\""); break;
|
case '"': jb_puts(b, "\\\""); p++; break;
|
||||||
case '\\': jb_puts(b, "\\\\"); break;
|
case '\\': jb_puts(b, "\\\\"); p++; break;
|
||||||
case '\b': jb_puts(b, "\\b"); break;
|
case '\b': jb_puts(b, "\\b"); p++; break;
|
||||||
case '\f': jb_puts(b, "\\f"); break;
|
case '\f': jb_puts(b, "\\f"); p++; break;
|
||||||
case '\n': jb_puts(b, "\\n"); break;
|
case '\n': jb_puts(b, "\\n"); p++; break;
|
||||||
case '\r': jb_puts(b, "\\r"); break;
|
case '\r': jb_puts(b, "\\r"); p++; break;
|
||||||
case '\t': jb_puts(b, "\\t"); break;
|
case '\t': jb_puts(b, "\\t"); p++; break;
|
||||||
default:
|
default:
|
||||||
if (c < 0x20) {
|
if (c < 0x20) {
|
||||||
char tmp[8];
|
char tmp[8];
|
||||||
snprintf(tmp, sizeof(tmp), "\\u%04x", c);
|
snprintf(tmp, sizeof(tmp), "\\u%04x", c);
|
||||||
jb_puts(b, tmp);
|
jb_puts(b, tmp);
|
||||||
} else {
|
p++;
|
||||||
|
} else if (c < 0x80) {
|
||||||
jb_putc(b, (char)c);
|
jb_putc(b, (char)c);
|
||||||
|
p++;
|
||||||
|
} else {
|
||||||
|
/* Multi-byte UTF-8: validate sequence, pass through if valid,
|
||||||
|
* escape as \u00xx if the start byte is invalid/orphaned. */
|
||||||
|
int seq_len = 0;
|
||||||
|
if ((c & 0xE0) == 0xC0) seq_len = 2;
|
||||||
|
else if ((c & 0xF0) == 0xE0) seq_len = 3;
|
||||||
|
else if ((c & 0xF8) == 0xF0) seq_len = 4;
|
||||||
|
if (seq_len >= 2) {
|
||||||
|
int valid = 1;
|
||||||
|
for (int i = 1; i < seq_len; i++) {
|
||||||
|
if ((p[i] & 0xC0) != 0x80) { valid = 0; break; }
|
||||||
|
}
|
||||||
|
if (valid) {
|
||||||
|
for (int i = 0; i < seq_len; i++) jb_putc(b, (char)p[i]);
|
||||||
|
p += seq_len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Invalid start byte or truncated sequence — escape it */
|
||||||
|
char tmp[8];
|
||||||
|
snprintf(tmp, sizeof(tmp), "\\u%04x", c);
|
||||||
|
jb_puts(b, tmp);
|
||||||
|
p++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -5705,6 +5863,50 @@ el_val_t getpid_now(void) {
|
|||||||
return (el_val_t)getpid();
|
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 ──────────────────────────────────
|
/* ── args() — command-line argument access ──────────────────────────────────
|
||||||
* Compiled El programs call args() to get a list of CLI arguments.
|
* 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.
|
* Call el_runtime_init_args(argc, argv) at the start of C main() to populate.
|
||||||
@@ -5877,6 +6079,14 @@ void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
|
|||||||
#define ENGRAM_LAYER_DOMAIN 2u
|
#define ENGRAM_LAYER_DOMAIN 2u
|
||||||
#define ENGRAM_LAYER_IMPRINT 3u
|
#define ENGRAM_LAYER_IMPRINT 3u
|
||||||
#define ENGRAM_LAYER_SUIT 4u
|
#define ENGRAM_LAYER_SUIT 4u
|
||||||
|
#define ENGRAM_LAYER_ACCUMULATION 5u
|
||||||
|
/* New user-facing nodes (memories, knowledge, conversations) are created in the
|
||||||
|
* accumulation layer — the top of the consciousness stack, the engram the user
|
||||||
|
* sees; every layer below shapes behavior but is hidden from the user (Layered
|
||||||
|
* Consciousness architecture, app 64/064,262). ENGRAM_LAYER_DEFAULT stays
|
||||||
|
* core-identity ON PURPOSE: it is the fallback home for LEGACY nodes loaded from
|
||||||
|
* snapshots without a layer_id, so existing data (the originator corpus) is
|
||||||
|
* never migrated out of its established layer. New != legacy. */
|
||||||
#define ENGRAM_LAYER_DEFAULT ENGRAM_LAYER_CORE_IDENTITY
|
#define ENGRAM_LAYER_DEFAULT ENGRAM_LAYER_CORE_IDENTITY
|
||||||
|
|
||||||
/* Pass 3 override floor. Layer 0 nodes that received any background
|
/* Pass 3 override floor. Layer 0 nodes that received any background
|
||||||
@@ -6054,6 +6264,20 @@ static void engram_init_layers(EngramStore* g) {
|
|||||||
.transparent = 0,
|
.transparent = 0,
|
||||||
.injectable = 1
|
.injectable = 1
|
||||||
};
|
};
|
||||||
|
/* Layer 5 — accumulation. The TOP of the consciousness stack: the default
|
||||||
|
* home for all new user-facing nodes. This is the engram the user sees;
|
||||||
|
* every layer below shapes behavior but is hidden from the user. Not
|
||||||
|
* injectable — it is the persistent user accumulation, not a swappable
|
||||||
|
* overlay. transparent=0: its content is surfaced to introspection (it is
|
||||||
|
* the user's own knowledge/memory), unlike the lower behavioral layers. */
|
||||||
|
g->layers[g->layer_count++] = (EngramLayer){
|
||||||
|
.layer_id = ENGRAM_LAYER_ACCUMULATION,
|
||||||
|
.name = el_strdup_persist("accumulation"),
|
||||||
|
.activation_priority = 50,
|
||||||
|
.suppressible = 1,
|
||||||
|
.transparent = 0,
|
||||||
|
.injectable = 0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static EngramStore* engram_get(void) {
|
static EngramStore* engram_get(void) {
|
||||||
@@ -6168,7 +6392,9 @@ static void engram_grow_edges(void) {
|
|||||||
static char* engram_new_id(void) {
|
static char* engram_new_id(void) {
|
||||||
el_val_t v = uuid_new();
|
el_val_t v = uuid_new();
|
||||||
const char* s = EL_CSTR(v);
|
const char* s = EL_CSTR(v);
|
||||||
return el_strdup(s ? s : "");
|
/* Persistent: node ids live in the global store; an arena (el_strdup) id is
|
||||||
|
* freed at el_request_end(), corrupting the node after the creating request. */
|
||||||
|
return el_strdup_persist(s ? s : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Convert a node into an ElMap of its fields. */
|
/* Convert a node into an ElMap of its fields. */
|
||||||
@@ -6245,7 +6471,7 @@ el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience) {
|
|||||||
n->last_activated = now;
|
n->last_activated = now;
|
||||||
n->created_at = now;
|
n->created_at = now;
|
||||||
n->updated_at = now;
|
n->updated_at = now;
|
||||||
n->layer_id = ENGRAM_LAYER_DEFAULT;
|
n->layer_id = ENGRAM_LAYER_ACCUMULATION; /* new user-facing node → top layer */
|
||||||
g->node_count++;
|
g->node_count++;
|
||||||
return el_wrap_str(el_strdup(n->id));
|
return el_wrap_str(el_strdup(n->id));
|
||||||
}
|
}
|
||||||
@@ -6263,12 +6489,17 @@ el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
|||||||
const char* lb = EL_CSTR(label);
|
const char* lb = EL_CSTR(label);
|
||||||
const char* ti = EL_CSTR(tier);
|
const char* ti = EL_CSTR(tier);
|
||||||
const char* tg = EL_CSTR(tags);
|
const char* tg = EL_CSTR(tags);
|
||||||
n->content = el_strdup(c ? c : "");
|
/* Persistent (el_strdup_persist, NOT el_strdup): these strings are owned by the
|
||||||
n->node_type = el_strdup(nt && *nt ? nt : "Memory");
|
* persistent global node store. el_strdup tracks into the per-request arena, which
|
||||||
n->label = el_strdup(lb && *lb ? lb : (c ? engram_first_n_chars(c, 60) : ""));
|
* el_request_end() frees when the creating HTTP request completes — leaving the
|
||||||
n->tier = el_strdup(ti && *ti ? ti : "Working");
|
* stored node with dangling pointers (corrupted ids, "saved but never listed").
|
||||||
n->tags = el_strdup(tg ? tg : "");
|
* This is the root cause of the hallucinated/lost-saves class of bugs. */
|
||||||
n->metadata = el_strdup("{}");
|
n->content = el_strdup_persist(c ? c : "");
|
||||||
|
n->node_type = el_strdup_persist(nt && *nt ? nt : "Memory");
|
||||||
|
n->label = el_strdup_persist(lb && *lb ? lb : (c ? engram_first_n_chars(c, 60) : ""));
|
||||||
|
n->tier = el_strdup_persist(ti && *ti ? ti : "Working");
|
||||||
|
n->tags = el_strdup_persist(tg ? tg : "");
|
||||||
|
n->metadata = el_strdup_persist("{}");
|
||||||
n->salience = engram_decode_score(salience);
|
n->salience = engram_decode_score(salience);
|
||||||
n->importance = engram_decode_score(importance);
|
n->importance = engram_decode_score(importance);
|
||||||
n->confidence = engram_decode_score(confidence);
|
n->confidence = engram_decode_score(confidence);
|
||||||
@@ -6281,7 +6512,7 @@ el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
|||||||
n->last_activated = now;
|
n->last_activated = now;
|
||||||
n->created_at = now;
|
n->created_at = now;
|
||||||
n->updated_at = now;
|
n->updated_at = now;
|
||||||
n->layer_id = ENGRAM_LAYER_DEFAULT;
|
n->layer_id = ENGRAM_LAYER_ACCUMULATION; /* new user-facing node → top layer */
|
||||||
g->node_count++;
|
g->node_count++;
|
||||||
return el_wrap_str(el_strdup(n->id));
|
return el_wrap_str(el_strdup(n->id));
|
||||||
}
|
}
|
||||||
@@ -7211,13 +7442,28 @@ el_val_t engram_save(el_val_t path) {
|
|||||||
jb_putc(&b, '}');
|
jb_putc(&b, '}');
|
||||||
}
|
}
|
||||||
jb_puts(&b, "]}");
|
jb_puts(&b, "]}");
|
||||||
FILE* f = fopen(p, "wb");
|
{
|
||||||
if (!f) { free(b.buf); return 0; }
|
struct stat _st;
|
||||||
|
if (stat(p, &_st) == 0 && _st.st_size > 200000 &&
|
||||||
|
(uint64_t)b.len < (uint64_t)_st.st_size / 16) {
|
||||||
|
fprintf(stderr, "[engram_save] REFUSED sparse write: new %zu vs existing %lld (<1/16) protecting %s\n",
|
||||||
|
b.len, (long long)_st.st_size, p);
|
||||||
|
free(b.buf); return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t _plen = strlen(p);
|
||||||
|
char* _tmp = (char*)malloc(_plen + 5);
|
||||||
|
if (!_tmp) { free(b.buf); return 0; }
|
||||||
|
memcpy(_tmp, p, _plen); memcpy(_tmp + _plen, ".tmp", 5);
|
||||||
|
FILE* f = fopen(_tmp, "wb");
|
||||||
|
if (!f) { free(_tmp); free(b.buf); return 0; }
|
||||||
size_t w = fwrite(b.buf, 1, b.len, f);
|
size_t w = fwrite(b.buf, 1, b.len, f);
|
||||||
fclose(f);
|
int wok = (w == b.len);
|
||||||
int ok = (w == b.len);
|
if (wok) { fflush(f); fsync(fileno(f)); }
|
||||||
free(b.buf);
|
fclose(f); free(b.buf);
|
||||||
return ok ? 1 : 0;
|
if (!wok) { unlink(_tmp); free(_tmp); return 0; }
|
||||||
|
if (rename(_tmp, p) != 0) { unlink(_tmp); free(_tmp); return 0; }
|
||||||
|
free(_tmp); return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helper: extract a string field from a JSON object substring. */
|
/* Helper: extract a string field from a JSON object substring. */
|
||||||
@@ -7838,6 +8084,257 @@ el_val_t engram_query_range(el_val_t start_ms_v, el_val_t end_ms_v) {
|
|||||||
return el_wrap_str(b.buf);
|
return el_wrap_str(b.buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* engram_load_merge — like engram_load but WITHOUT resetting the store.
|
||||||
|
* Reads a JSON snapshot from `path` and adds any nodes/edges not already
|
||||||
|
* present in the in-memory graph. Dedup is by node id (for nodes) and by
|
||||||
|
* (from_id, to_id, relation) tuple (for edges).
|
||||||
|
*
|
||||||
|
* Returns (as an EL int) the count of new nodes added. Embeddings are
|
||||||
|
* intentionally skipped on merged nodes to avoid Ollama delays at runtime;
|
||||||
|
* auto_link_semantic will handle them when nodes are next activated.
|
||||||
|
*
|
||||||
|
* Does not merge layers — the in-process layer registry is authoritative. */
|
||||||
|
el_val_t engram_load_merge(el_val_t path) {
|
||||||
|
const char* p = EL_CSTR(path);
|
||||||
|
if (!p || !*p) return 0;
|
||||||
|
FILE* f = fopen(p, "rb");
|
||||||
|
if (!f) return 0;
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
long sz = ftell(f);
|
||||||
|
rewind(f);
|
||||||
|
if (sz <= 0) { fclose(f); return 0; }
|
||||||
|
char* data = malloc((size_t)sz + 1);
|
||||||
|
if (!data) { fclose(f); return 0; }
|
||||||
|
size_t got = fread(data, 1, (size_t)sz, f);
|
||||||
|
fclose(f);
|
||||||
|
data[got] = '\0';
|
||||||
|
|
||||||
|
EngramStore* g = engram_get();
|
||||||
|
int64_t added_nodes = 0;
|
||||||
|
|
||||||
|
/* Walk nodes array — skip any node whose id already exists */
|
||||||
|
const char* nodes_p = json_find_key(data, "nodes");
|
||||||
|
if (nodes_p) {
|
||||||
|
nodes_p = eg_skip_ws(nodes_p);
|
||||||
|
if (*nodes_p == '[') {
|
||||||
|
nodes_p++;
|
||||||
|
nodes_p = eg_skip_ws(nodes_p);
|
||||||
|
while (*nodes_p && *nodes_p != ']') {
|
||||||
|
if (*nodes_p != '{') { nodes_p++; continue; }
|
||||||
|
const char* end = json_skip_value(nodes_p);
|
||||||
|
size_t n = (size_t)(end - nodes_p);
|
||||||
|
char* obj = malloc(n + 1);
|
||||||
|
memcpy(obj, nodes_p, n); obj[n] = '\0';
|
||||||
|
char* nid = eg_get_str_field(obj, "id");
|
||||||
|
int already = (nid && *nid && engram_find_node(nid) != NULL);
|
||||||
|
free(nid);
|
||||||
|
if (!already) {
|
||||||
|
engram_grow_nodes();
|
||||||
|
EngramNode* nn = &g->nodes[g->node_count];
|
||||||
|
memset(nn, 0, sizeof(*nn));
|
||||||
|
nn->id = eg_get_str_field(obj, "id");
|
||||||
|
nn->content = eg_get_str_field(obj, "content");
|
||||||
|
nn->node_type = eg_get_str_field(obj, "node_type");
|
||||||
|
nn->label = eg_get_str_field(obj, "label");
|
||||||
|
nn->tier = eg_get_str_field(obj, "tier");
|
||||||
|
nn->tags = eg_get_str_field(obj, "tags");
|
||||||
|
nn->metadata = eg_get_str_field(obj, "metadata");
|
||||||
|
if (!nn->metadata || !*nn->metadata) { free(nn->metadata); nn->metadata = strdup("{}"); }
|
||||||
|
nn->salience = eg_get_num_field(obj, "salience");
|
||||||
|
nn->importance = eg_get_num_field(obj, "importance");
|
||||||
|
nn->confidence = eg_get_num_field(obj, "confidence");
|
||||||
|
nn->temporal_decay_rate = eg_get_num_field(obj, "temporal_decay_rate");
|
||||||
|
nn->activation_count = eg_get_int_field(obj, "activation_count");
|
||||||
|
nn->last_activated = eg_get_int_field(obj, "last_activated");
|
||||||
|
nn->created_at = eg_get_int_field(obj, "created_at");
|
||||||
|
nn->updated_at = eg_get_int_field(obj, "updated_at");
|
||||||
|
nn->background_activation = eg_get_num_field(obj, "background_activation");
|
||||||
|
nn->working_memory_weight = eg_get_num_field(obj, "working_memory_weight");
|
||||||
|
if (!isfinite(nn->working_memory_weight) || nn->working_memory_weight < 0.0 || nn->working_memory_weight > 1.0)
|
||||||
|
nn->working_memory_weight = 0.0; /* clamp corrupt snapshot values */
|
||||||
|
nn->suppression_count = (int32_t)eg_get_int_field(obj, "suppression_count");
|
||||||
|
if (json_find_key(obj, "layer_id")) {
|
||||||
|
nn->layer_id = (uint32_t)eg_get_int_field(obj, "layer_id");
|
||||||
|
} else {
|
||||||
|
nn->layer_id = ENGRAM_LAYER_DEFAULT;
|
||||||
|
}
|
||||||
|
g->node_count++;
|
||||||
|
added_nodes++;
|
||||||
|
}
|
||||||
|
free(obj);
|
||||||
|
nodes_p = end;
|
||||||
|
nodes_p = eg_skip_ws(nodes_p);
|
||||||
|
if (*nodes_p == ',') { nodes_p++; nodes_p = eg_skip_ws(nodes_p); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Walk edges array — skip if (from_id, to_id, relation) already present */
|
||||||
|
const char* edges_p = json_find_key(data, "edges");
|
||||||
|
if (edges_p) {
|
||||||
|
edges_p = eg_skip_ws(edges_p);
|
||||||
|
if (*edges_p == '[') {
|
||||||
|
edges_p++;
|
||||||
|
edges_p = eg_skip_ws(edges_p);
|
||||||
|
while (*edges_p && *edges_p != ']') {
|
||||||
|
if (*edges_p != '{') { edges_p++; continue; }
|
||||||
|
const char* end = json_skip_value(edges_p);
|
||||||
|
size_t n = (size_t)(end - edges_p);
|
||||||
|
char* obj = malloc(n + 1);
|
||||||
|
memcpy(obj, edges_p, n); obj[n] = '\0';
|
||||||
|
char* efrom = eg_get_str_field(obj, "from_id");
|
||||||
|
char* eto = eg_get_str_field(obj, "to_id");
|
||||||
|
char* erel = eg_get_str_field(obj, "relation");
|
||||||
|
/* Check for duplicate by scanning existing edges */
|
||||||
|
int dup = 0;
|
||||||
|
if (efrom && eto && erel) {
|
||||||
|
for (int64_t ei = 0; ei < g->edge_count; ei++) {
|
||||||
|
EngramEdge* ex = &g->edges[ei];
|
||||||
|
if (ex->from_id && ex->to_id && ex->relation &&
|
||||||
|
strcmp(ex->from_id, efrom) == 0 &&
|
||||||
|
strcmp(ex->to_id, eto) == 0 &&
|
||||||
|
strcmp(ex->relation, erel) == 0) {
|
||||||
|
dup = 1; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!dup) {
|
||||||
|
engram_grow_edges();
|
||||||
|
EngramEdge* ee = &g->edges[g->edge_count];
|
||||||
|
memset(ee, 0, sizeof(*ee));
|
||||||
|
ee->id = eg_get_str_field(obj, "id");
|
||||||
|
ee->from_id = efrom ? efrom : strdup("");
|
||||||
|
ee->to_id = eto ? eto : strdup("");
|
||||||
|
ee->relation = erel ? erel : strdup("");
|
||||||
|
ee->metadata = eg_get_str_field(obj, "metadata");
|
||||||
|
if (!ee->metadata || !*ee->metadata) { free(ee->metadata); ee->metadata = strdup("{}"); }
|
||||||
|
ee->weight = eg_get_num_field(obj, "weight");
|
||||||
|
ee->confidence = eg_get_num_field(obj, "confidence");
|
||||||
|
ee->created_at = eg_get_int_field(obj, "created_at");
|
||||||
|
ee->updated_at = eg_get_int_field(obj, "updated_at");
|
||||||
|
ee->last_fired = eg_get_int_field(obj, "last_fired");
|
||||||
|
ee->inhibitory = (int)eg_get_int_field(obj, "inhibitory");
|
||||||
|
if (json_find_key(obj, "layer_id")) {
|
||||||
|
ee->layer_id = (uint32_t)eg_get_int_field(obj, "layer_id");
|
||||||
|
} else {
|
||||||
|
ee->layer_id = ENGRAM_LAYER_DEFAULT;
|
||||||
|
}
|
||||||
|
g->edge_count++;
|
||||||
|
/* NOTE: efrom/eto/erel ownership transferred to ee above */
|
||||||
|
efrom = NULL; eto = NULL; erel = NULL;
|
||||||
|
} else {
|
||||||
|
free(efrom); free(eto); free(erel);
|
||||||
|
}
|
||||||
|
free(obj);
|
||||||
|
edges_p = end;
|
||||||
|
edges_p = eg_skip_ws(edges_p);
|
||||||
|
if (*edges_p == ',') { edges_p++; edges_p = eg_skip_ws(edges_p); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(data);
|
||||||
|
return (el_val_t)added_nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
el_val_t engram_wm_count(void) {
|
||||||
|
EngramStore* g = engram_get();
|
||||||
|
int64_t count = 0;
|
||||||
|
for (int64_t i = 0; i < g->node_count; i++) {
|
||||||
|
if (g->nodes[i].working_memory_weight > 0.0) count++;
|
||||||
|
}
|
||||||
|
return (el_val_t)count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Average working_memory_weight across all promoted nodes (wm > 0).
|
||||||
|
* Returns the float bit-pattern via el_from_float so EL can use it with
|
||||||
|
* float_to_str / float_gt. Returns 0.0 when no nodes are promoted.
|
||||||
|
* Useful in heartbeat ISEs to distinguish "many weak activations" (sparse
|
||||||
|
* graph, low avg) from "few strong activations" (dense subgraph, high avg).
|
||||||
|
* Added 2026-06-04 self-review for graph health observability. */
|
||||||
|
el_val_t engram_wm_avg_weight(void) {
|
||||||
|
EngramStore* g = engram_get();
|
||||||
|
double sum = 0.0;
|
||||||
|
int64_t count = 0;
|
||||||
|
for (int64_t i = 0; i < g->node_count; i++) {
|
||||||
|
double w = g->nodes[i].working_memory_weight;
|
||||||
|
/* Defensive guard: skip any corrupt/out-of-range values so a single
|
||||||
|
* bad snapshot node doesn't produce a garbage average (e.g. 1.77e+234). */
|
||||||
|
if (w > 0.0 && w <= 1.0 && isfinite(w)) { sum += w; count++; }
|
||||||
|
}
|
||||||
|
double avg = (count > 0) ? (sum / (double)count) : 0.0;
|
||||||
|
return el_from_float(avg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* engram_wm_top_json — return top N working-memory nodes (by wm weight) as a
|
||||||
|
* compact JSON array for ISE heartbeat reporting.
|
||||||
|
*
|
||||||
|
* Each element: {"label":"...","node_type":"...","tier":"...","wm":0.42}
|
||||||
|
*
|
||||||
|
* Purpose: the heartbeat ISE reports wm_active (count) and wm_avg_weight but
|
||||||
|
* gives zero visibility into WM *composition* — which types/tiers are active.
|
||||||
|
* After long uptime every WM slot is in steady-state decay+re-promotion so
|
||||||
|
* wm_promotion ISEs never fire (they only fire on 0→>0.1 transitions).
|
||||||
|
* This function fills the observability gap by snapshotting the current top-N
|
||||||
|
* WM nodes on every heartbeat. Inserted 2026-06-05 self-review. */
|
||||||
|
el_val_t engram_wm_top_json(el_val_t n_v) {
|
||||||
|
int64_t top_n = (int64_t)n_v;
|
||||||
|
if (top_n <= 0) top_n = 10;
|
||||||
|
if (top_n > 50) top_n = 50;
|
||||||
|
EngramStore* g = engram_get();
|
||||||
|
|
||||||
|
/* Collect indices of promoted nodes, excluding monitoring noise.
|
||||||
|
* InternalStateEvent nodes are system-observation artifacts — they reflect
|
||||||
|
* what the daemon is doing, not what it knows. Including them in wm_top
|
||||||
|
* buries real knowledge (Memory, Knowledge, Belief nodes) under a wall of
|
||||||
|
* heartbeat/curiosity ISEs, making the heartbeat ISE useless for diagnosing
|
||||||
|
* WM composition. Filter them out here so wm_top always shows substantive
|
||||||
|
* content. (2026-06-07 self-review) */
|
||||||
|
int64_t* idx = malloc((size_t)(g->node_count + 1) * sizeof(int64_t));
|
||||||
|
if (!idx) return el_wrap_str(el_strdup("[]"));
|
||||||
|
int64_t mc = 0;
|
||||||
|
for (int64_t i = 0; i < g->node_count; i++) {
|
||||||
|
if (g->nodes[i].working_memory_weight > 0.0) {
|
||||||
|
const char* nt = g->nodes[i].node_type;
|
||||||
|
if (nt && strcmp(nt, "InternalStateEvent") == 0) continue;
|
||||||
|
idx[mc++] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Insertion-sort descending by wm weight (mc is typically small). */
|
||||||
|
for (int64_t i = 1; i < mc; i++) {
|
||||||
|
int64_t key = idx[i];
|
||||||
|
double kw = g->nodes[key].working_memory_weight;
|
||||||
|
int64_t j = i;
|
||||||
|
while (j > 0 && g->nodes[idx[j-1]].working_memory_weight < kw) {
|
||||||
|
idx[j] = idx[j-1]; j--;
|
||||||
|
}
|
||||||
|
idx[j] = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t emit = mc < top_n ? mc : top_n;
|
||||||
|
JsonBuf b; jb_init(&b);
|
||||||
|
jb_putc(&b, '[');
|
||||||
|
for (int64_t k = 0; k < emit; k++) {
|
||||||
|
EngramNode* n = &g->nodes[idx[k]];
|
||||||
|
if (k > 0) jb_putc(&b, ',');
|
||||||
|
jb_putc(&b, '{');
|
||||||
|
jb_puts(&b, "\"label\":");
|
||||||
|
jb_emit_escaped(&b, n->label ? n->label : "");
|
||||||
|
jb_puts(&b, ",\"node_type\":");
|
||||||
|
jb_emit_escaped(&b, n->node_type ? n->node_type : "");
|
||||||
|
jb_puts(&b, ",\"tier\":");
|
||||||
|
jb_emit_escaped(&b, n->tier ? n->tier : "");
|
||||||
|
char tmp[48];
|
||||||
|
snprintf(tmp, sizeof(tmp), ",\"wm\":%.3f", n->working_memory_weight);
|
||||||
|
jb_puts(&b, tmp);
|
||||||
|
jb_putc(&b, '}');
|
||||||
|
}
|
||||||
|
free(idx);
|
||||||
|
jb_putc(&b, ']');
|
||||||
|
return el_wrap_str(b.buf);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_CURL
|
#ifdef HAVE_CURL
|
||||||
/* ── DHARMA network ─────────────────────────────────────────────────────────
|
/* ── DHARMA network ─────────────────────────────────────────────────────────
|
||||||
* Real implementation. Peers are addressed by `dharma_id` — either bare
|
* Real implementation. Peers are addressed by `dharma_id` — either bare
|
||||||
@@ -8478,7 +8975,7 @@ static el_val_t llm_provider_request(const char* url, const char* key,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static el_val_t llm_chain_call(const char* system_str, const char* user_str) {
|
static el_val_t llm_chain_call(const char* model_pref, const char* system_str, const char* user_str) {
|
||||||
char url_key[64], key_key[64], fmt_key[64], model_key[64];
|
char url_key[64], key_key[64], fmt_key[64], model_key[64];
|
||||||
for (int i = 0; i < LLM_MAX_PROVIDERS; i++) {
|
for (int i = 0; i < LLM_MAX_PROVIDERS; i++) {
|
||||||
snprintf(url_key, sizeof(url_key), "NEURON_LLM_%d_URL", i);
|
snprintf(url_key, sizeof(url_key), "NEURON_LLM_%d_URL", i);
|
||||||
@@ -8491,6 +8988,7 @@ static el_val_t llm_chain_call(const char* system_str, const char* user_str) {
|
|||||||
const char* fmt_s = getenv(fmt_key);
|
const char* fmt_s = getenv(fmt_key);
|
||||||
int fmt = (fmt_s && strcmp(fmt_s, "anthropic") == 0) ? 1 : 0;
|
int fmt = (fmt_s && strcmp(fmt_s, "anthropic") == 0) ? 1 : 0;
|
||||||
const char* model = getenv(model_key);
|
const char* model = getenv(model_key);
|
||||||
|
if (!model || !*model) model = model_pref; /* fall back to the caller-requested model */
|
||||||
fprintf(stderr, "[llm] trying provider %d (%s)\n", i, url);
|
fprintf(stderr, "[llm] trying provider %d (%s)\n", i, url);
|
||||||
el_val_t result = llm_provider_request(url, key, fmt, model, system_str, user_str);
|
el_val_t result = llm_provider_request(url, key, fmt, model, system_str, user_str);
|
||||||
const char* t = EL_CSTR(result);
|
const char* t = EL_CSTR(result);
|
||||||
@@ -8501,7 +8999,7 @@ static el_val_t llm_chain_call(const char* system_str, const char* user_str) {
|
|||||||
const char* api_key = getenv("ANTHROPIC_API_KEY");
|
const char* api_key = getenv("ANTHROPIC_API_KEY");
|
||||||
if (!api_key || !*api_key) return http_error_json("no LLM providers configured");
|
if (!api_key || !*api_key) return http_error_json("no LLM providers configured");
|
||||||
fprintf(stderr, "[llm] using legacy ANTHROPIC_API_KEY fallback\n");
|
fprintf(stderr, "[llm] using legacy ANTHROPIC_API_KEY fallback\n");
|
||||||
return llm_provider_request(LLM_API_URL, api_key, 1, NULL, system_str, user_str);
|
return llm_provider_request(LLM_API_URL, api_key, 1, model_pref, system_str, user_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Legacy llm_request — kept for backward compat with agentic loop internals */
|
/* Legacy llm_request — kept for backward compat with agentic loop internals */
|
||||||
@@ -8565,14 +9063,16 @@ static el_val_t llm_extract_text(el_val_t resp_val) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
el_val_t llm_call(el_val_t model, el_val_t prompt) {
|
el_val_t llm_call(el_val_t model, el_val_t prompt) {
|
||||||
|
const char* m = EL_CSTR(model);
|
||||||
const char* u = EL_CSTR(prompt); if (!u) u = "";
|
const char* u = EL_CSTR(prompt); if (!u) u = "";
|
||||||
return llm_chain_call(NULL, u);
|
return llm_chain_call(m, NULL, u);
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt) {
|
el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt) {
|
||||||
|
const char* m = EL_CSTR(model);
|
||||||
const char* s = EL_CSTR(system_prompt); if (!s) s = "";
|
const char* s = EL_CSTR(system_prompt); if (!s) s = "";
|
||||||
const char* u = EL_CSTR(user_prompt); if (!u) u = "";
|
const char* u = EL_CSTR(user_prompt); if (!u) u = "";
|
||||||
return llm_chain_call(s, u);
|
return llm_chain_call(m, s, u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Tool registry for llm_call_agentic ─────────────────────────────────── */
|
/* ── Tool registry for llm_call_agentic ─────────────────────────────────── */
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ el_val_t http_set_handler(el_val_t name);
|
|||||||
* existing handlers (e.g. products/web/server.el): it dispatches with
|
* existing handlers (e.g. products/web/server.el): it dispatches with
|
||||||
* (method, path, body), hardcodes 200 OK, and auto-detects content type. */
|
* (method, path, body), hardcodes 200 OK, and auto-detects content type. */
|
||||||
el_val_t http_serve_v2(el_val_t port, el_val_t handler);
|
el_val_t http_serve_v2(el_val_t port, el_val_t handler);
|
||||||
|
void http_serve_async(el_val_t port, el_val_t handler);
|
||||||
el_val_t http_set_handler_v2(el_val_t name);
|
el_val_t http_set_handler_v2(el_val_t name);
|
||||||
|
|
||||||
/* Build an HTTP response envelope. `headers_json` should be a JSON object
|
/* Build an HTTP response envelope. `headers_json` should be a JSON object
|
||||||
@@ -233,6 +234,8 @@ el_val_t url_decode(el_val_t s); /* '+' → space, %XX → byte */
|
|||||||
* {"p":[],"a":["href","title"],"strong":[],...}
|
* {"p":[],"a":["href","title"],"strong":[],...}
|
||||||
* where each value is the array of attribute names allowed for that tag. */
|
* 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 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 ──────────────────────────────────────────────────────────── */
|
/* ── Filesystem ──────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
@@ -537,6 +540,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 exit_program(el_val_t code);
|
||||||
el_val_t getpid_now(void);
|
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 ─────────────────────────────────────────────────────────────
|
/* ── CGI identity ─────────────────────────────────────────────────────────────
|
||||||
* Called at the start of main() in CGI programs (those with a `cgi {}` block).
|
* 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. */
|
* Records the program's DHARMA identity before any other code executes. */
|
||||||
@@ -636,6 +645,12 @@ el_val_t engram_list_layers_json(void);
|
|||||||
* no nodes promoted to working memory. */
|
* no nodes promoted to working memory. */
|
||||||
el_val_t engram_compile_layered_json(el_val_t intent, el_val_t depth);
|
el_val_t engram_compile_layered_json(el_val_t intent, el_val_t depth);
|
||||||
|
|
||||||
|
/* ── Working memory ──────────────────────────────────────────────────────────*/
|
||||||
|
el_val_t engram_wm_count(void);
|
||||||
|
el_val_t engram_wm_avg_weight(void);
|
||||||
|
el_val_t engram_wm_top_json(el_val_t n);
|
||||||
|
el_val_t engram_load_merge(el_val_t path);
|
||||||
|
|
||||||
/* ── LLM (Anthropic API client) ─────────────────────────────────────────────
|
/* ── LLM (Anthropic API client) ─────────────────────────────────────────────
|
||||||
* All functions call https://api.anthropic.com/v1/messages with the API key
|
* All functions call https://api.anthropic.com/v1/messages with the API key
|
||||||
* from env ANTHROPIC_API_KEY. Default model when empty: claude-sonnet-4-5. */
|
* from env ANTHROPIC_API_KEY. Default model when empty: claude-sonnet-4-5. */
|
||||||
|
|||||||
@@ -324,6 +324,10 @@ fn cg_html_parts(children: [Map<String, Any>], acc_var: String) -> String {
|
|||||||
let each_c: String = cg_html_each(child, acc_var)
|
let each_c: String = cg_html_each(child, acc_var)
|
||||||
let parts = native_list_append(parts, each_c)
|
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
|
let i = i + 1
|
||||||
}
|
}
|
||||||
str_join(parts, "")
|
str_join(parts, "")
|
||||||
@@ -413,6 +417,17 @@ fn cg_html_each(node: Map<String, Any>, 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 + "} } "
|
"{ 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<String, Any>, acc_var: String) -> String {
|
||||||
|
let cond_expr = node["cond"]
|
||||||
|
let then_children: [Map<String, Any>] = node["then"]
|
||||||
|
let else_children: [Map<String, Any>] = 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.
|
// Top-level HTML template codegen — returns a C statement-expression string.
|
||||||
fn cg_html_template(expr: Map<String, Any>) -> String {
|
fn cg_html_template(expr: Map<String, Any>) -> String {
|
||||||
let root = expr["root"]
|
let root = expr["root"]
|
||||||
@@ -3715,6 +3730,7 @@ fn codegen_streaming(tokens: [Any], sigs: [Map<String, Any>], source: String) ->
|
|||||||
cg_fn(stmt)
|
cg_fn(stmt)
|
||||||
el_release(stmt)
|
el_release(stmt)
|
||||||
el_arena_pop(fn_arena_mark)
|
el_arena_pop(fn_arena_mark)
|
||||||
|
el_mem_check()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if is_top_level_decl(stmt) {
|
if is_top_level_decl(stmt) {
|
||||||
|
|||||||
@@ -287,6 +287,9 @@ fn type_node_to_el(t: Map<String, Any>) -> String {
|
|||||||
|
|
||||||
// emit_header — write a .elh file from parsed statements.
|
// emit_header — write a .elh file from parsed statements.
|
||||||
// Scans for FnDef nodes and emits 'extern fn' declarations.
|
// 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<String, Any>], hdr_path: String) -> Void {
|
fn emit_header(stmts: [Map<String, Any>], hdr_path: String) -> Void {
|
||||||
let n: Int = native_list_len(stmts)
|
let n: Int = native_list_len(stmts)
|
||||||
let i = 0
|
let i = 0
|
||||||
@@ -325,6 +328,32 @@ fn emit_header(stmts: [Map<String, Any>], hdr_path: String) -> Void {
|
|||||||
let ok: Bool = fs_write(hdr_path, content)
|
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<String, Any>], 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 ────────────────────────────────────────────────────────
|
// ── Import resolution ────────────────────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// elc supports two forms of import:
|
// elc supports two forms of import:
|
||||||
@@ -536,16 +565,20 @@ fn main() -> Void {
|
|||||||
|
|
||||||
let src_path: String = native_list_get(positional, 0)
|
let src_path: String = native_list_get(positional, 0)
|
||||||
|
|
||||||
// When --emit-header is requested, parse the source file directly
|
// When --emit-header is requested, lex the source file and do a
|
||||||
// (without inlining imports) and write out a .elh file alongside the .c.
|
// 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 {
|
if do_emit_header {
|
||||||
|
el_mem_check()
|
||||||
let raw_source: String = fs_read(src_path)
|
let raw_source: String = fs_read(src_path)
|
||||||
let hdr_tokens: [Any] = lex(raw_source)
|
let hdr_tokens: [Any] = lex(raw_source)
|
||||||
let hdr_stmts: [Map<String, Any>] = parse(hdr_tokens)
|
let hdr_sigs: [Map<String, Any>] = scan_fn_sigs_el(hdr_tokens)
|
||||||
el_release(hdr_tokens)
|
el_release(hdr_tokens)
|
||||||
let hdr_path: String = str_slice(src_path, 0, str_len(src_path) - 3) + ".elh"
|
let hdr_path: String = str_slice(src_path, 0, str_len(src_path) - 3) + ".elh"
|
||||||
emit_header(hdr_stmts, hdr_path)
|
emit_header_from_sigs(hdr_sigs, hdr_path)
|
||||||
el_release(hdr_stmts)
|
el_release(hdr_sigs)
|
||||||
}
|
}
|
||||||
|
|
||||||
let source: String = resolve_imports(src_path)
|
let source: String = resolve_imports(src_path)
|
||||||
|
|||||||
+497
-94
@@ -293,6 +293,48 @@ fn is_void_element(name: String) -> Bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect all tokens as raw text until </tag_name> is encountered.
|
||||||
|
// Used for <style> and <script> elements to avoid parsing CSS/JS as El.
|
||||||
|
// Returns { "text": "...", "pos": p_after_closing_tag }
|
||||||
|
fn parse_raw_text_content(tokens: [Any], pos: Int, tag_name: String) -> Map<String, Any> {
|
||||||
|
let parts: [String] = native_list_empty()
|
||||||
|
let p = pos
|
||||||
|
let running = true
|
||||||
|
while running {
|
||||||
|
let k = tok_kind(tokens, p)
|
||||||
|
if str_eq(k, "Eof") {
|
||||||
|
let running = false
|
||||||
|
} else {
|
||||||
|
if str_eq(k, "Lt") {
|
||||||
|
let k2 = tok_kind(tokens, p + 1)
|
||||||
|
if str_eq(k2, "Slash") {
|
||||||
|
// Check if this is </tag_name>
|
||||||
|
let close_name = tok_value(tokens, p + 2)
|
||||||
|
if str_eq(close_name, tag_name) {
|
||||||
|
// consume </tag_name>
|
||||||
|
let p = p + 3
|
||||||
|
let p = expect(tokens, p, "Gt")
|
||||||
|
let running = false
|
||||||
|
} else {
|
||||||
|
let v = tok_value(tokens, p)
|
||||||
|
let parts = native_list_append(parts, v)
|
||||||
|
let p = p + 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let v = tok_value(tokens, p)
|
||||||
|
let parts = native_list_append(parts, v)
|
||||||
|
let p = p + 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let v = tok_value(tokens, p)
|
||||||
|
let parts = native_list_append(parts, v)
|
||||||
|
let p = p + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{ "text": str_join(parts, ""), "pos": p }
|
||||||
|
}
|
||||||
|
|
||||||
// Collect tokens as text content until we hit Lt, LBrace, Eof, or a
|
// Collect tokens as text content until we hit Lt, LBrace, Eof, or a
|
||||||
// closing-tag marker (Lt Slash). Returns { "text": "...", "pos": p }
|
// closing-tag marker (Lt Slash). Returns { "text": "...", "pos": p }
|
||||||
fn parse_html_text_tokens(tokens: [Any], pos: Int) -> Map<String, Any> {
|
fn parse_html_text_tokens(tokens: [Any], pos: Int) -> Map<String, Any> {
|
||||||
@@ -320,7 +362,7 @@ fn parse_html_text_tokens(tokens: [Any], pos: Int) -> Map<String, Any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{ "text": str_join(parts, " "), "pos": p }
|
{ "text": str_join(parts, ""), "pos": p }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse an attribute list: (attrname | attrname="val" | attrname={expr})*
|
// Parse an attribute list: (attrname | attrname="val" | attrname={expr})*
|
||||||
@@ -435,77 +477,125 @@ fn parse_html_children(tokens: [Any], pos: Int, parent_tag: String) -> Map<Strin
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if str_eq(k, "LBrace") {
|
if str_eq(k, "LBrace") {
|
||||||
// Interpolation: {expr} or {#each ...} or {/each}
|
// Interpolation: {expr}, {#each ...}, {#if ...}, {#else}, {/each}, {/if}
|
||||||
|
// Note: '#' (ASCII 35) is skipped by the lexer, so {#each} lexes as
|
||||||
|
// LBrace Ident:"each" ... and {#if} lexes as LBrace If ... and
|
||||||
|
// {#else} lexes as LBrace Else RBrace.
|
||||||
let k2 = tok_kind(tokens, p + 1)
|
let k2 = tok_kind(tokens, p + 1)
|
||||||
if str_eq(k2, "Hash") {
|
if str_eq(k2, "Slash") {
|
||||||
// {#each list as item}
|
// {/each} or {/if} — end of block, stop
|
||||||
let k3_v = tok_value(tokens, p + 2)
|
// skip { /
|
||||||
if str_eq(k3_v, "each") {
|
let p = p + 2
|
||||||
let p = p + 3
|
// skip the close-tag name token (each, if, etc.)
|
||||||
// parse list expr up to "as" keyword
|
let p = p + 1
|
||||||
|
// skip }
|
||||||
|
let p = expect(tokens, p, "RBrace")
|
||||||
|
let running = false
|
||||||
|
} else {
|
||||||
|
if str_eq(k2, "If") {
|
||||||
|
// {#if condition} ... {/if}
|
||||||
|
// Skip { if (2 tokens; '#' was silently skipped by lexer)
|
||||||
|
let p = p + 2
|
||||||
|
// Parse condition expression (no block expr)
|
||||||
let prev_no_block: String = state_get("__no_block_expr")
|
let prev_no_block: String = state_get("__no_block_expr")
|
||||||
state_set("__no_block_expr", "1")
|
state_set("__no_block_expr", "1")
|
||||||
let r_list = parse_expr(tokens, p)
|
let r_cond = parse_expr(tokens, p)
|
||||||
state_set("__no_block_expr", prev_no_block)
|
state_set("__no_block_expr", prev_no_block)
|
||||||
let list_expr = r_list["node"]
|
let cond_expr = r_cond["node"]
|
||||||
let p = r_list["pos"]
|
let p = r_cond["pos"]
|
||||||
// r_list result map fully consumed — release to free peak heap.
|
el_release(r_cond)
|
||||||
el_release(r_list)
|
|
||||||
// expect "as"
|
|
||||||
let p = expect(tokens, p, "As")
|
|
||||||
// item variable name
|
|
||||||
let item_name = tok_value(tokens, p)
|
|
||||||
let p = p + 1
|
|
||||||
// consume closing }
|
// consume closing }
|
||||||
let p = expect(tokens, p, "RBrace")
|
let p = expect(tokens, p, "RBrace")
|
||||||
// parse body until {/each}
|
// parse then-children until {#else} or {/if}
|
||||||
let r_body = parse_html_each_body(tokens, p)
|
let r_then = parse_html_children(tokens, p, "__if_then__")
|
||||||
let body_children = r_body["children"]
|
let then_children = r_then["children"]
|
||||||
let p = r_body["pos"]
|
let p = r_then["pos"]
|
||||||
// r_body result map fully consumed — release to free peak heap.
|
el_release(r_then)
|
||||||
el_release(r_body)
|
// check for {#else} — lexed as LBrace Else RBrace
|
||||||
let each_node: Map<String, Any> = { "html": "Each", "list": list_expr, "item": item_name, "body": body_children }
|
let else_children: [Map<String, Any>] = native_list_empty()
|
||||||
let children = native_list_append(children, each_node)
|
let ck = tok_kind(tokens, p)
|
||||||
} else {
|
if str_eq(ck, "LBrace") {
|
||||||
let p = p + 1
|
let ck2 = tok_kind(tokens, p + 1)
|
||||||
}
|
if str_eq(ck2, "Else") {
|
||||||
} else {
|
// consume { else }
|
||||||
if str_eq(k2, "Slash") {
|
let p = p + 2
|
||||||
// {/each} — end of each block, stop
|
let p = expect(tokens, p, "RBrace")
|
||||||
// skip {/each}
|
// parse else-children until {/if}
|
||||||
let p = p + 2
|
let r_else = parse_html_children(tokens, p, "__if_else__")
|
||||||
// skip "each"
|
let else_children = r_else["children"]
|
||||||
let p = p + 1
|
let p = r_else["pos"]
|
||||||
// skip }
|
el_release(r_else)
|
||||||
let p = expect(tokens, p, "RBrace")
|
|
||||||
let running = false
|
|
||||||
} else {
|
|
||||||
// regular {expr}
|
|
||||||
let r = parse_expr(tokens, p + 1)
|
|
||||||
let interp_val = r["node"]
|
|
||||||
let p = r["pos"]
|
|
||||||
// r result map fully consumed — release to free peak heap.
|
|
||||||
el_release(r)
|
|
||||||
let p = expect(tokens, p, "RBrace")
|
|
||||||
// Check if the expr is a call to raw()
|
|
||||||
let is_raw_call = false
|
|
||||||
let interp_kind: String = interp_val["expr"]
|
|
||||||
if str_eq(interp_kind, "Call") {
|
|
||||||
let fn_node = interp_val["func"]
|
|
||||||
let fn_kind: String = fn_node["expr"]
|
|
||||||
if str_eq(fn_kind, "Ident") {
|
|
||||||
let fn_name_v: String = fn_node["name"]
|
|
||||||
if str_eq(fn_name_v, "raw") {
|
|
||||||
let is_raw_call = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if is_raw_call {
|
let if_node: Map<String, Any> = { "html": "HtmlIf", "cond": cond_expr, "then": then_children, "else": else_children }
|
||||||
let raw_args = interp_val["args"]
|
let children = native_list_append(children, if_node)
|
||||||
let raw_inner = native_list_get(raw_args, 0)
|
} else {
|
||||||
let children = native_list_append(children, { "html": "Raw", "value": raw_inner })
|
if str_eq(k2, "Else") {
|
||||||
|
// {#else} sentinel — lexed as LBrace Else RBrace
|
||||||
|
// Do NOT consume — leave position for caller ({#if} handler checks for it)
|
||||||
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
let children = native_list_append(children, { "html": "Interp", "value": interp_val })
|
// Check for {#each list as item} — lexed as LBrace Ident:"each" ...
|
||||||
|
let k2_v = tok_value(tokens, p + 1)
|
||||||
|
if str_eq(k2_v, "each") {
|
||||||
|
let p = p + 2
|
||||||
|
// parse list expr up to "as" keyword
|
||||||
|
let prev_no_block: String = state_get("__no_block_expr")
|
||||||
|
state_set("__no_block_expr", "1")
|
||||||
|
let r_list = parse_expr(tokens, p)
|
||||||
|
state_set("__no_block_expr", prev_no_block)
|
||||||
|
let list_expr = r_list["node"]
|
||||||
|
let p = r_list["pos"]
|
||||||
|
// r_list result map fully consumed — release to free peak heap.
|
||||||
|
el_release(r_list)
|
||||||
|
// expect "as"
|
||||||
|
let p = expect(tokens, p, "As")
|
||||||
|
// item variable name
|
||||||
|
let item_name = tok_value(tokens, p)
|
||||||
|
let p = p + 1
|
||||||
|
// consume closing }
|
||||||
|
let p = expect(tokens, p, "RBrace")
|
||||||
|
// parse body until {/each}
|
||||||
|
let r_body = parse_html_each_body(tokens, p)
|
||||||
|
let body_children = r_body["children"]
|
||||||
|
let p = r_body["pos"]
|
||||||
|
// r_body result map fully consumed — release to free peak heap.
|
||||||
|
el_release(r_body)
|
||||||
|
let each_node: Map<String, Any> = { "html": "Each", "list": list_expr, "item": item_name, "body": body_children }
|
||||||
|
let children = native_list_append(children, each_node)
|
||||||
|
} else {
|
||||||
|
// regular {expr} — disable map-literal parsing so {fn(a,b)}
|
||||||
|
// does not trigger the LBrace→map path inside parse_primary
|
||||||
|
let prev_no_block: String = state_get("__no_block_expr")
|
||||||
|
state_set("__no_block_expr", "1")
|
||||||
|
let r = parse_expr(tokens, p + 1)
|
||||||
|
state_set("__no_block_expr", prev_no_block)
|
||||||
|
let interp_val = r["node"]
|
||||||
|
let p = r["pos"]
|
||||||
|
// r result map fully consumed — release to free peak heap.
|
||||||
|
el_release(r)
|
||||||
|
let p = expect(tokens, p, "RBrace")
|
||||||
|
// Check if the expr is a call to raw()
|
||||||
|
let is_raw_call = false
|
||||||
|
let interp_kind: String = interp_val["expr"]
|
||||||
|
if str_eq(interp_kind, "Call") {
|
||||||
|
let fn_node = interp_val["func"]
|
||||||
|
let fn_kind: String = fn_node["expr"]
|
||||||
|
if str_eq(fn_kind, "Ident") {
|
||||||
|
let fn_name_v: String = fn_node["name"]
|
||||||
|
if str_eq(fn_name_v, "raw") {
|
||||||
|
let is_raw_call = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if is_raw_call {
|
||||||
|
let raw_args = interp_val["args"]
|
||||||
|
let raw_inner = native_list_get(raw_args, 0)
|
||||||
|
let children = native_list_append(children, { "html": "Raw", "value": raw_inner })
|
||||||
|
} else {
|
||||||
|
let children = native_list_append(children, { "html": "Interp", "value": interp_val })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,6 +655,27 @@ fn parse_html_element(tokens: [Any], pos: Int) -> Map<String, Any> {
|
|||||||
if is_void_element(tag_name) {
|
if is_void_element(tag_name) {
|
||||||
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": native_list_empty(), "self_closing": true }, p)
|
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": native_list_empty(), "self_closing": true }, p)
|
||||||
}
|
}
|
||||||
|
// raw-text mode for style/script — collect content as plain text without parsing CSS/JS as El
|
||||||
|
if str_eq(tag_name, "style") {
|
||||||
|
let r_raw = parse_raw_text_content(tokens, p, "style")
|
||||||
|
let raw_text: String = r_raw["text"]
|
||||||
|
let p = r_raw["pos"]
|
||||||
|
el_release(r_raw)
|
||||||
|
let raw_child: Map<String, Any> = { "html": "Text", "text": raw_text }
|
||||||
|
let raw_children: [Map<String, Any>] = native_list_empty()
|
||||||
|
let raw_children = native_list_append(raw_children, raw_child)
|
||||||
|
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": raw_children, "self_closing": false }, p)
|
||||||
|
}
|
||||||
|
if str_eq(tag_name, "script") {
|
||||||
|
let r_raw = parse_raw_text_content(tokens, p, "script")
|
||||||
|
let raw_text: String = r_raw["text"]
|
||||||
|
let p = r_raw["pos"]
|
||||||
|
el_release(r_raw)
|
||||||
|
let raw_child: Map<String, Any> = { "html": "Text", "text": raw_text }
|
||||||
|
let raw_children: [Map<String, Any>] = native_list_empty()
|
||||||
|
let raw_children = native_list_append(raw_children, raw_child)
|
||||||
|
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": raw_children, "self_closing": false }, p)
|
||||||
|
}
|
||||||
// parse children
|
// parse children
|
||||||
let r_children = parse_html_children(tokens, p, tag_name)
|
let r_children = parse_html_children(tokens, p, tag_name)
|
||||||
let children = r_children["children"]
|
let children = r_children["children"]
|
||||||
@@ -718,44 +829,123 @@ fn parse_primary(tokens: [Any], pos: Int) -> Map<String, Any> {
|
|||||||
// as the start of the block they're expecting.
|
// as the start of the block they're expecting.
|
||||||
return make_result({ "expr": "Nil" }, pos)
|
return make_result({ "expr": "Nil" }, pos)
|
||||||
}
|
}
|
||||||
let p = pos + 1
|
// Distinguish map literal from interpolation chain.
|
||||||
let pairs: [Map<String, Any>] = native_list_empty()
|
// A map literal requires { key: value } — the second token inside { must be Colon.
|
||||||
let running = true
|
// An empty {} is a map literal. Everything else is an interpolation chain.
|
||||||
while running {
|
let first_k: String = tok_kind(tokens, pos + 1)
|
||||||
let k2 = tok_kind(tokens, p)
|
let second_k: String = tok_kind(tokens, pos + 2)
|
||||||
if k2 == "RBrace" {
|
if str_eq(first_k, "RBrace") {
|
||||||
let running = false
|
// Empty map literal {}
|
||||||
} else {
|
return make_result({ "expr": "Map", "pairs": native_list_empty() }, pos + 2)
|
||||||
if k2 == "Eof" {
|
}
|
||||||
|
if str_eq(second_k, "Colon") {
|
||||||
|
// MAP LITERAL: { key: value, ... }
|
||||||
|
let p = pos + 1
|
||||||
|
let pairs: [Map<String, Any>] = native_list_empty()
|
||||||
|
let running = true
|
||||||
|
while running {
|
||||||
|
let k2 = tok_kind(tokens, p)
|
||||||
|
if k2 == "RBrace" {
|
||||||
let running = false
|
let running = false
|
||||||
} else {
|
} else {
|
||||||
// key: Str token
|
if k2 == "Eof" {
|
||||||
let key = tok_value(tokens, p)
|
let running = false
|
||||||
let new_p: Int = p + 1
|
|
||||||
let new_p = expect(tokens, new_p, "Colon")
|
|
||||||
let r = parse_expr(tokens, new_p)
|
|
||||||
let val_node = r["node"]
|
|
||||||
let new_p = r["pos"]
|
|
||||||
// r result map fully consumed — release to free peak heap.
|
|
||||||
el_release(r)
|
|
||||||
let pair = { "key": key, "value": val_node }
|
|
||||||
let pairs = native_list_append(pairs, pair)
|
|
||||||
let k3 = tok_kind(tokens, new_p)
|
|
||||||
if k3 == "Comma" {
|
|
||||||
let new_p = new_p + 1
|
|
||||||
}
|
|
||||||
// Non-progress guard: malformed map content can leave
|
|
||||||
// parse_expr returning the same pos. Force advance.
|
|
||||||
if new_p <= p {
|
|
||||||
let p = p + 1
|
|
||||||
} else {
|
} else {
|
||||||
let p = new_p
|
// key: Str or Ident token
|
||||||
|
let key = tok_value(tokens, p)
|
||||||
|
let new_p: Int = p + 1
|
||||||
|
let new_p = expect(tokens, new_p, "Colon")
|
||||||
|
let r = parse_expr(tokens, new_p)
|
||||||
|
let val_node = r["node"]
|
||||||
|
let new_p = r["pos"]
|
||||||
|
// r result map fully consumed — release to free peak heap.
|
||||||
|
el_release(r)
|
||||||
|
let pair = { "key": key, "value": val_node }
|
||||||
|
let pairs = native_list_append(pairs, pair)
|
||||||
|
let k3 = tok_kind(tokens, new_p)
|
||||||
|
if k3 == "Comma" {
|
||||||
|
let new_p = new_p + 1
|
||||||
|
}
|
||||||
|
// Non-progress guard: malformed map content can leave
|
||||||
|
// parse_expr returning the same pos. Force advance.
|
||||||
|
if new_p <= p {
|
||||||
|
let p = p + 1
|
||||||
|
} else {
|
||||||
|
let p = new_p
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let p = expect(tokens, p, "RBrace")
|
||||||
|
return make_result({ "expr": "Map", "pairs": pairs }, p)
|
||||||
}
|
}
|
||||||
let p = expect(tokens, p, "RBrace")
|
// INTERPOLATION CHAIN: {expr}, {expr}{expr}, {expr}<html>, etc.
|
||||||
return make_result({ "expr": "Map", "pairs": pairs }, p)
|
// Build a BinOp(Plus, ...) concatenation chain.
|
||||||
|
let p = pos
|
||||||
|
let chain_node: Map<String, Any> = { "expr": "Nil" }
|
||||||
|
let chain_started = false
|
||||||
|
let chain_running = true
|
||||||
|
while chain_running {
|
||||||
|
let ck: String = tok_kind(tokens, p)
|
||||||
|
if str_eq(ck, "LBrace") {
|
||||||
|
let prev_no_block: String = state_get("__no_block_expr")
|
||||||
|
state_set("__no_block_expr", "1")
|
||||||
|
let r = parse_expr(tokens, p + 1)
|
||||||
|
state_set("__no_block_expr", prev_no_block)
|
||||||
|
let part = r["node"]
|
||||||
|
let p = r["pos"]
|
||||||
|
// r result map fully consumed — release to free peak heap.
|
||||||
|
el_release(r)
|
||||||
|
let p = expect(tokens, p, "RBrace")
|
||||||
|
if !chain_started {
|
||||||
|
let chain_node = part
|
||||||
|
let chain_started = true
|
||||||
|
} else {
|
||||||
|
let chain_node: Map<String, Any> = { "expr": "BinOp", "op": "Plus", "left": chain_node, "right": part }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if str_eq(ck, "Lt") {
|
||||||
|
let ck2: String = tok_kind(tokens, p + 1)
|
||||||
|
if str_eq(ck2, "Not") {
|
||||||
|
let r = parse_html_template(tokens, p)
|
||||||
|
let part = r["node"]
|
||||||
|
let p = r["pos"]
|
||||||
|
// r result map fully consumed — release to free peak heap.
|
||||||
|
el_release(r)
|
||||||
|
if !chain_started {
|
||||||
|
let chain_node = part
|
||||||
|
let chain_started = true
|
||||||
|
} else {
|
||||||
|
let chain_node: Map<String, Any> = { "expr": "BinOp", "op": "Plus", "left": chain_node, "right": part }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if str_eq(ck2, "Ident") {
|
||||||
|
let tag_candidate: String = tok_value(tokens, p + 1)
|
||||||
|
if is_html_tag_name(tag_candidate) {
|
||||||
|
let r = parse_html_template(tokens, p)
|
||||||
|
let part = r["node"]
|
||||||
|
let p = r["pos"]
|
||||||
|
// r result map fully consumed — release to free peak heap.
|
||||||
|
el_release(r)
|
||||||
|
if !chain_started {
|
||||||
|
let chain_node = part
|
||||||
|
let chain_started = true
|
||||||
|
} else {
|
||||||
|
let chain_node: Map<String, Any> = { "expr": "BinOp", "op": "Plus", "left": chain_node, "right": part }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let chain_running = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let chain_running = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let chain_running = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return make_result(chain_node, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if expression
|
// if expression
|
||||||
@@ -1875,6 +2065,219 @@ fn skip_expr_to_stmt_boundary(tokens: [Any], pos: Int) -> Int {
|
|||||||
p
|
p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// scan_type_el — read a type annotation starting at pos and return its El
|
||||||
|
// source representation as a string, plus the new position.
|
||||||
|
// Returns { "el": String, "pos": Int }.
|
||||||
|
// Handles: Ident, [Type], Map<K,V>, Type?, Type<T,...> (same shapes as skip_type).
|
||||||
|
fn scan_type_el(tokens: [Any], pos: Int) -> Map<String, Any> {
|
||||||
|
let k: String = tok_kind(tokens, pos)
|
||||||
|
// Array type: [Type]
|
||||||
|
if str_eq(k, "LBracket") {
|
||||||
|
let p: Int = pos + 1
|
||||||
|
let inner = scan_type_el(tokens, p)
|
||||||
|
let inner_str: String = inner["el"]
|
||||||
|
let p = inner["pos"]
|
||||||
|
el_release(inner)
|
||||||
|
let p = expect(tokens, p, "RBracket")
|
||||||
|
return { "el": "[" + inner_str + "]", "pos": p }
|
||||||
|
}
|
||||||
|
// Named type (possibly generic or optional)
|
||||||
|
if str_eq(k, "Ident") {
|
||||||
|
let name: String = tok_value(tokens, pos)
|
||||||
|
let p: Int = pos + 1
|
||||||
|
let k2: String = tok_kind(tokens, p)
|
||||||
|
if str_eq(k2, "Lt") {
|
||||||
|
// Generic params: collect until matching >
|
||||||
|
let p = p + 1
|
||||||
|
let depth: Int = 1
|
||||||
|
let parts: [String] = native_list_empty()
|
||||||
|
let parts = native_list_append(parts, name + "<")
|
||||||
|
let running: Bool = true
|
||||||
|
while running {
|
||||||
|
let kk: String = tok_kind(tokens, p)
|
||||||
|
if str_eq(kk, "Eof") {
|
||||||
|
let running = false
|
||||||
|
} else {
|
||||||
|
if str_eq(kk, "Lt") {
|
||||||
|
let depth = depth + 1
|
||||||
|
let parts = native_list_append(parts, "<")
|
||||||
|
let p = p + 1
|
||||||
|
} else {
|
||||||
|
if str_eq(kk, "Gt") {
|
||||||
|
let depth = depth - 1
|
||||||
|
let p = p + 1
|
||||||
|
if depth <= 0 {
|
||||||
|
let parts = native_list_append(parts, ">")
|
||||||
|
let running = false
|
||||||
|
} else {
|
||||||
|
let parts = native_list_append(parts, ">")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if str_eq(kk, "Comma") {
|
||||||
|
let parts = native_list_append(parts, ", ")
|
||||||
|
let p = p + 1
|
||||||
|
} else {
|
||||||
|
let parts = native_list_append(parts, tok_value(tokens, p))
|
||||||
|
let p = p + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let k3: String = tok_kind(tokens, p)
|
||||||
|
if str_eq(k3, "QuestionMark") { let p = p + 1 }
|
||||||
|
let result: String = str_join(parts, "")
|
||||||
|
el_release(parts)
|
||||||
|
return { "el": result, "pos": p }
|
||||||
|
}
|
||||||
|
// Optional marker
|
||||||
|
if str_eq(k2, "QuestionMark") {
|
||||||
|
return { "el": name + "?", "pos": p + 1 }
|
||||||
|
}
|
||||||
|
return { "el": name, "pos": p }
|
||||||
|
}
|
||||||
|
// Fallback: unknown token, treat as Any
|
||||||
|
{ "el": "Any", "pos": pos + 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan_params_el — scan a parameter list `(name: Type, ...)` starting at
|
||||||
|
// position `pos` (which should point at LParen) and return the El parameter
|
||||||
|
// declaration string (e.g. "a: String, b: Int") along with the new position.
|
||||||
|
// Returns { "el": String, "pos": Int }.
|
||||||
|
// Used by scan_fn_sigs_el for --emit-header without building full AST.
|
||||||
|
fn scan_params_el(tokens: [Any], pos: Int) -> Map<String, Any> {
|
||||||
|
let p: Int = expect(tokens, pos, "LParen")
|
||||||
|
let parts: [String] = native_list_empty()
|
||||||
|
let going: Bool = true
|
||||||
|
while going {
|
||||||
|
let kk: String = tok_kind(tokens, p)
|
||||||
|
if str_eq(kk, "RParen") {
|
||||||
|
let going = false
|
||||||
|
} else {
|
||||||
|
if str_eq(kk, "Eof") {
|
||||||
|
let going = false
|
||||||
|
} else {
|
||||||
|
let pname: String = tok_value(tokens, p)
|
||||||
|
let p = p + 1
|
||||||
|
let p = expect(tokens, p, "Colon")
|
||||||
|
let tr = scan_type_el(tokens, p)
|
||||||
|
let ptype: String = tr["el"]
|
||||||
|
let p = tr["pos"]
|
||||||
|
el_release(tr)
|
||||||
|
let parts = native_list_append(parts, pname + ": " + ptype)
|
||||||
|
let k2: String = tok_kind(tokens, p)
|
||||||
|
if str_eq(k2, "Comma") {
|
||||||
|
let p = p + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let p = expect(tokens, p, "RParen")
|
||||||
|
let el_str: String = str_join(parts, ", ")
|
||||||
|
el_release(parts)
|
||||||
|
{ "el": el_str, "pos": p }
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan_fn_sigs_el — lightweight token-level pre-scan for --emit-header.
|
||||||
|
//
|
||||||
|
// Like scan_fn_sigs but captures El-style type strings instead of C types.
|
||||||
|
// Only records fn/extern_fn entries (header generation ignores lets/blocks).
|
||||||
|
//
|
||||||
|
// Descriptor shape:
|
||||||
|
// { "kind": "fn"|"extern_fn", "name": String,
|
||||||
|
// "params_el": String, <- El param list, e.g. "a: String, b: Int"
|
||||||
|
// "ret_el": String } <- El return type, e.g. "String" or "Void"
|
||||||
|
//
|
||||||
|
// Peak memory: O(tokens) with no expression AST allocation.
|
||||||
|
fn scan_fn_sigs_el(tokens: [Any]) -> [Map<String, Any>] {
|
||||||
|
let total: Int = native_list_len(tokens) / 2
|
||||||
|
let sigs: [Map<String, Any>] = native_list_empty()
|
||||||
|
let pos: Int = 0
|
||||||
|
let going: Bool = true
|
||||||
|
while going {
|
||||||
|
if pos >= total {
|
||||||
|
let going = false
|
||||||
|
} else {
|
||||||
|
let k: String = tok_kind(tokens, pos)
|
||||||
|
if str_eq(k, "Eof") {
|
||||||
|
let going = false
|
||||||
|
} else {
|
||||||
|
// --- fn definition ---
|
||||||
|
if str_eq(k, "Fn") {
|
||||||
|
let p: Int = pos + 1
|
||||||
|
let name: String = tok_value(tokens, p)
|
||||||
|
let p = p + 1
|
||||||
|
let pr = scan_params_el(tokens, p)
|
||||||
|
let params_el: String = pr["el"]
|
||||||
|
let p = pr["pos"]
|
||||||
|
el_release(pr)
|
||||||
|
// read return type
|
||||||
|
let ret_el: String = "Any"
|
||||||
|
let k2: String = tok_kind(tokens, p)
|
||||||
|
if str_eq(k2, "Arrow") {
|
||||||
|
let p = p + 1
|
||||||
|
let tr = scan_type_el(tokens, p)
|
||||||
|
let ret_el = tr["el"]
|
||||||
|
let p = tr["pos"]
|
||||||
|
el_release(tr)
|
||||||
|
}
|
||||||
|
// skip body
|
||||||
|
let k3: String = tok_kind(tokens, p)
|
||||||
|
if str_eq(k3, "LBrace") {
|
||||||
|
let p = skip_to_rbrace(tokens, p)
|
||||||
|
}
|
||||||
|
if !str_eq(name, "main") {
|
||||||
|
let sigs = native_list_append(sigs, {
|
||||||
|
"kind": "fn",
|
||||||
|
"name": name,
|
||||||
|
"params_el": params_el,
|
||||||
|
"ret_el": ret_el
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let pos = p
|
||||||
|
} else {
|
||||||
|
// --- extern fn ---
|
||||||
|
if str_eq(k, "Extern") {
|
||||||
|
let p: Int = pos + 1
|
||||||
|
let k2: String = tok_kind(tokens, p)
|
||||||
|
if str_eq(k2, "Fn") {
|
||||||
|
let p = p + 1
|
||||||
|
let name: String = tok_value(tokens, p)
|
||||||
|
let p = p + 1
|
||||||
|
let pr = scan_params_el(tokens, p)
|
||||||
|
let params_el: String = pr["el"]
|
||||||
|
let p = pr["pos"]
|
||||||
|
el_release(pr)
|
||||||
|
let ret_el: String = "Any"
|
||||||
|
let k3: String = tok_kind(tokens, p)
|
||||||
|
if str_eq(k3, "Arrow") {
|
||||||
|
let p = p + 1
|
||||||
|
let tr = scan_type_el(tokens, p)
|
||||||
|
let ret_el = tr["el"]
|
||||||
|
let p = tr["pos"]
|
||||||
|
el_release(tr)
|
||||||
|
}
|
||||||
|
let sigs = native_list_append(sigs, {
|
||||||
|
"kind": "extern_fn",
|
||||||
|
"name": name,
|
||||||
|
"params_el": params_el,
|
||||||
|
"ret_el": ret_el
|
||||||
|
})
|
||||||
|
let pos = p
|
||||||
|
} else {
|
||||||
|
let pos = pos + 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Let, Cgi, Service, Import, Type, Enum, From — skip to boundary.
|
||||||
|
let p: Int = pos + 1
|
||||||
|
let p = skip_expr_to_stmt_boundary(tokens, p)
|
||||||
|
let pos = p
|
||||||
|
}}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sigs
|
||||||
|
}
|
||||||
|
|
||||||
// scan_params_c — scan a parameter list `(name: Type, ...)` starting at
|
// scan_params_c — scan a parameter list `(name: Type, ...)` starting at
|
||||||
// position `pos` (which should point at LParen) and return the C parameter
|
// position `pos` (which should point at LParen) and return the C parameter
|
||||||
// declaration string along with the new position.
|
// declaration string along with the new position.
|
||||||
|
|||||||
+50
-2
@@ -77,6 +77,33 @@ fn parse_manifest_entry(src: String) -> String {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse_manifest_c_sources - collect all `c_source "path"` lines from the
|
||||||
|
// build block. Returns a flat list of path strings.
|
||||||
|
fn parse_manifest_c_sources(src: String) -> [String] {
|
||||||
|
let result: [String] = native_list_empty()
|
||||||
|
let lines: [String] = str_split(src, "\n")
|
||||||
|
let n: Int = native_list_len(lines)
|
||||||
|
let i = 0
|
||||||
|
while i < n {
|
||||||
|
let line: String = native_list_get(lines, i)
|
||||||
|
let t: String = str_trim(line)
|
||||||
|
if str_starts_with(t, "c_source ") {
|
||||||
|
let after: String = str_slice(t, 9, str_len(t))
|
||||||
|
let trimmed: String = str_trim(after)
|
||||||
|
if str_starts_with(trimmed, "\"") {
|
||||||
|
let inner: String = str_slice(trimmed, 1, str_len(trimmed))
|
||||||
|
let q: Int = str_index_of(inner, "\"")
|
||||||
|
if q >= 0 {
|
||||||
|
let path: String = str_slice(inner, 0, q)
|
||||||
|
let result = native_list_append(result, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_manifest_name(src: String) -> String {
|
fn parse_manifest_name(src: String) -> String {
|
||||||
let lines: [String] = str_split(src, "\n")
|
let lines: [String] = str_split(src, "\n")
|
||||||
let n: Int = native_list_len(lines)
|
let n: Int = native_list_len(lines)
|
||||||
@@ -274,7 +301,18 @@ fn link_binary(c_files: [String], out_bin: String, runtime_path: String, out_dir
|
|||||||
// Detect clang vs gcc: -fbracket-depth is clang-only; silently ignored
|
// Detect clang vs gcc: -fbracket-depth is clang-only; silently ignored
|
||||||
// if unsupported but gcc rejects it with an error.
|
// if unsupported but gcc rejects it with an error.
|
||||||
let bracket_flag: String = "$(cc --version 2>&1 | grep -q clang && printf -- '-fbracket-depth=1024' || true)"
|
let bracket_flag: String = "$(cc --version 2>&1 | grep -q clang && printf -- '-fbracket-depth=1024' || true)"
|
||||||
let parts = native_list_append(parts, "cc -O2 " + bracket_flag + " -I " + dirname_of(runtime_path) + " -I " + out_dir)
|
// On macOS, OpenSSL is not on the default linker path. Detect homebrew
|
||||||
|
// prefix and add it if present (no-op on Linux where libssl is in /usr/lib).
|
||||||
|
let ossl_lib_flag: String = "$(brew --prefix openssl 2>/dev/null | xargs -I{} printf -- '-L{}/lib' 2>/dev/null || true)"
|
||||||
|
let ossl_inc_flag: String = "$(brew --prefix openssl 2>/dev/null | xargs -I{} printf -- '-I{}/include' 2>/dev/null || true)"
|
||||||
|
// Force-include the C-level master declarations header so every translation
|
||||||
|
// unit sees all cross-module function signatures. Handles packages (like ELP)
|
||||||
|
// where modules call each other without explicit El import statements.
|
||||||
|
// The header is generated by elb --gen-decls or manually placed in out_dir.
|
||||||
|
let master_decls: String = out_dir + "/elp-c-decls.h"
|
||||||
|
let has_master: String = str_trim(exec_capture("test -f " + master_decls + " && echo yes || echo no"))
|
||||||
|
let include_flag: String = if str_eq(has_master, "yes") { "-include " + master_decls } else { "" }
|
||||||
|
let parts = native_list_append(parts, "cc -O2 " + bracket_flag + " " + ossl_inc_flag + " " + include_flag + " -I " + dirname_of(runtime_path) + " -I " + out_dir)
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
let f: String = native_list_get(c_files, i)
|
let f: String = native_list_get(c_files, i)
|
||||||
@@ -282,7 +320,7 @@ fn link_binary(c_files: [String], out_bin: String, runtime_path: String, out_dir
|
|||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
let parts = native_list_append(parts, runtime_path)
|
let parts = native_list_append(parts, runtime_path)
|
||||||
let parts = native_list_append(parts, "-lcurl -lssl -lcrypto -lpthread -lm")
|
let parts = native_list_append(parts, ossl_lib_flag + " -lcurl -lssl -lcrypto -lpthread -lm")
|
||||||
let parts = native_list_append(parts, "-o " + out_bin)
|
let parts = native_list_append(parts, "-o " + out_bin)
|
||||||
let cmd: String = str_join(parts, " ")
|
let cmd: String = str_join(parts, " ")
|
||||||
println(" link " + out_bin)
|
println(" link " + out_bin)
|
||||||
@@ -315,6 +353,7 @@ fn main() -> Void {
|
|||||||
|
|
||||||
let pkg_name: String = parse_manifest_name(manifest_src)
|
let pkg_name: String = parse_manifest_name(manifest_src)
|
||||||
let entry: String = parse_manifest_entry(manifest_src)
|
let entry: String = parse_manifest_entry(manifest_src)
|
||||||
|
let extra_c: [String] = parse_manifest_c_sources(manifest_src)
|
||||||
if str_eq(entry, "") {
|
if str_eq(entry, "") {
|
||||||
println("elb: manifest.el has no 'entry' declaration")
|
println("elb: manifest.el has no 'entry' declaration")
|
||||||
exit(1)
|
exit(1)
|
||||||
@@ -393,6 +432,15 @@ fn main() -> Void {
|
|||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append any extra C sources declared in the manifest (e.g. platform stubs)
|
||||||
|
let ei = 0
|
||||||
|
let en: Int = native_list_len(extra_c)
|
||||||
|
while ei < en {
|
||||||
|
let ec: String = native_list_get(extra_c, ei)
|
||||||
|
let c_files = native_list_append(c_files, ec)
|
||||||
|
let ei = ei + 1
|
||||||
|
}
|
||||||
|
|
||||||
// Link
|
// Link
|
||||||
let out_bin: String = out_dir + "/" + pkg_name
|
let out_bin: String = out_dir + "/" + pkg_name
|
||||||
let linked: Bool = link_binary(c_files, out_bin, runtime_path, out_dir, dry_run)
|
let linked: Bool = link_binary(c_files, out_bin, runtime_path, out_dir, dry_run)
|
||||||
|
|||||||
+10062
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user