#!/usr/bin/env bash # tools/lsp/build.sh — Build the El LSP server binary. # # Output: tools/lsp/dist/el-lsp # # Usage: # cd tools/lsp && ./build.sh # EL_HOME=/path/to/el ./build.sh # if el root is elsewhere # ELC=/path/to/custom/elc ./build.sh # use a specific compiler binary # # Prerequisites: # - elc binary (either on PATH or ELC env var) # - el-compiler/runtime/el_runtime.c # - cc (clang or gcc), libcurl, pthreads # # NOTE — seed primitive __read_n: # el-lsp.el calls __read_n(n: Int) -> String, which reads exactly n bytes # from stdin. This primitive must be present in el_runtime.c and declared # in el_runtime.h for the compiled binary to work correctly. # # Until __read_n is added to the runtime, the LSP will compile and link # successfully but lsp_read_message() will silently use the __read_n symbol # — causing a linker error. The seed agent must: # 1. Add el_val_t __read_n(el_val_t n) to el-compiler/runtime/el_runtime.c # 2. Declare el_val_t __read_n(el_val_t n) in el-compiler/runtime/el_runtime.h # 3. Add "__read_n" with arity 1 to the builtin_arity table in # el-compiler/src/codegen.el # 4. Rebuild the elc binary # # Implementation sketch for __read_n in C: # # el_val_t __read_n(el_val_t n) { # int64_t count = (int64_t)n; # if (count <= 0) return EL_STR(""); # char* buf = malloc(count + 1); # if (!buf) return EL_STR(""); # size_t total = 0; # while (total < (size_t)count) { # size_t got = fread(buf + total, 1, (size_t)count - total, stdin); # if (got == 0) break; # total += got; # } # buf[total] = '\0'; # return EL_STR(buf); # } set -euo pipefail cd "$(dirname "$0")" # ── Locate el root ───────────────────────────────────────────────────────────── EL_HOME="${EL_HOME:-$(cd ../.. && pwd)}" ELC="${ELC:-${EL_HOME}/dist/platform/elc}" RUNTIME_DIR="${EL_HOME}/el-compiler/runtime" # ── Validate prerequisites ───────────────────────────────────────────────────── if [ ! -x "${ELC}" ]; then echo "error: elc not found at ${ELC}" >&2 echo " Set ELC=/path/to/elc or EL_HOME=/path/to/el-root" >&2 exit 1 fi if [ ! -f "${RUNTIME_DIR}/el_runtime.c" ]; then echo "error: el_runtime.c not found at ${RUNTIME_DIR}/el_runtime.c" >&2 exit 1 fi mkdir -p dist # ── Concatenate runtime modules + LSP source ─────────────────────────────────── # Load order follows runtime/manifest.el: string → math → state → env → # fs → exec → time → json → http → then our LSP source. RUNTIME_SRCS=( "${EL_HOME}/runtime/string.el" "${EL_HOME}/runtime/math.el" "${EL_HOME}/runtime/state.el" "${EL_HOME}/runtime/env.el" "${EL_HOME}/runtime/fs.el" "${EL_HOME}/runtime/exec.el" "${EL_HOME}/runtime/time.el" "${EL_HOME}/runtime/json.el" ) # Check that runtime modules exist MISSING=0 for f in "${RUNTIME_SRCS[@]}"; do if [ ! -f "${f}" ]; then echo "warning: runtime module not found: ${f}" >&2 MISSING=$((MISSING + 1)) fi done if [ "${MISSING}" -gt 0 ]; then echo "error: ${MISSING} runtime module(s) missing — cannot build" >&2 exit 1 fi COMBINED="dist/el-lsp-combined.el" echo "==> Combining sources..." cat "${RUNTIME_SRCS[@]}" el-lsp.el > "${COMBINED}" echo " ${COMBINED}" # ── Compile El → C ───────────────────────────────────────────────────────────── C_OUT="dist/el-lsp.c" echo "==> Compiling El → C..." "${ELC}" "${COMBINED}" > "${C_OUT}" echo " ${C_OUT}" # ── Compile C → binary ───────────────────────────────────────────────────────── BIN="dist/el-lsp" echo "==> Compiling C → binary..." cc -std=c11 -O2 \ -I "${RUNTIME_DIR}" \ -o "${BIN}" \ "${C_OUT}" "${RUNTIME_DIR}/el_runtime.c" \ -lcurl -lpthread echo " ${BIN}" echo echo "==> Build complete: ${BIN}" echo echo " Run as LSP server (editors connect via stdin/stdout):" echo " ${BIN}" echo echo " Test with a synthetic LSP initialize request:" echo " echo -e 'Content-Length: 97\\r\\n\\r\\n{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"capabilities\":{}}}' | ${BIN}"