tersmu is a semantic parser for Lojban. It translates Lojban text into predicate logic.
Disclaimer: This is an unofficial project. While it aims to follow baseline prescription where feasible, the semantics it assigns to Lojban may in some cases be non-standard or incorrect.
To build a slim, production-ready image and run the HTTP REST API:
# Build the production image
docker build -t tersmu .
# Run the API
docker run --rm -p 8080:8080 tersmuThe API will be available at http://localhost:8080. See HTTP REST API for usage.
You can also run the command-line parser using the same production image:
# Parse a file
docker run --rm -it --entrypoint tersmu tersmu examples/1.jbo
# Parse from stdin (paste Lojban, then Ctrl-D)
docker run --rm -it --entrypoint tersmu tersmuFor local development, you can use docker-compose with a container that watches your source files and automatically reloads the server using ghcid.
The parser files must be generated before the application can run. Since the necessary tools (Python, Make) are installed within the Docker image, you should run the generation commands using docker compose:
# Start the container
docker compose -f docker-compose.dev.yml up -d
# Generate pappy files and Haskell modules inside the container
docker compose -f docker-compose.dev.yml exec tersmu python3 scripts/gen_pappy.py Lojban.pest Lojban.pappy.rhs -o Lojban.pappy
docker compose -f docker-compose.dev.yml exec tersmu python3 scripts/gen_pappy.py Morphology.pest Morphology.pappy.rhs -o Morphology.pappy
docker compose -f docker-compose.dev.yml exec tersmu make pappy/pappy/pappy
docker compose -f docker-compose.dev.yml exec tersmu make Pappy/Parse.hs Lojban.hs Morphology.hsIf you haven't already:
docker compose -f docker-compose.dev.yml up --build- Hot Reloading: Your local directory is mounted into the container. Any changes to
.hsfiles will trigger an automatic incremental rebuild and restart oftersmu-serverviaghcid. - Persistent Cache: Cabal dependencies and build artifacts are stored in Docker volumes (
cabal_store,cabal_dist) to keep rebuilds fast. - Port: The dev server listens on port
8080.
- Edit your
.hsfiles locally - Save the file
ghcidinside the container automatically detects changes, recompiles, and restarts the server- Test your changes at
http://localhost:8080
If you modify the .pest or .pappy.rhs grammar files, you need to regenerate the parser files. You can do this inside the running container:
docker compose -f docker-compose.dev.yml exec tersmu python3 scripts/gen_pappy.py Lojban.pest Lojban.pappy.rhs -o Lojban.pappy
docker compose -f docker-compose.dev.yml exec tersmu make Lojban.hsghcid will notice the changed .hs files and reload automatically.
The server exposes a small REST API on port 8080.
| Method | Path | Description |
|---|---|---|
GET |
/ |
API description |
GET |
/health |
Health check |
POST |
/ |
Parse Lojban text (request body) |
POST |
/parse |
Parse Lojban text (request body) |
Send the Lojban text as the request body (plain text, UTF-8). One line per sentence; blank lines are allowed.
Response: The API returns JSON (Content-Type: application/json). All string values are trimmed of leading/trailing spaces and newlines.
Single line: One JSON object with keys:
input— the trimmed input linelogical— logical form (bracket notation), ornullon errorcanonical— canonicalized Lojban form, ornullon errortree— structured AST (JSON array), ornullon errorerror—nullon success, or the error message (morphology/parse) on failure
Multiple lines: One JSON object with key results — an array of objects, each with the same keys as above.
Example (curl):
# Single line
curl -s -X POST -H "Content-Type: text/plain; charset=utf-8" \
-d "mi klama le zarci" \
http://localhost:8080/parse
# From file
curl -s -X POST -H "Content-Type: text/plain; charset=utf-8" \
--data-binary @examples/1.jbo \
http://localhost:8080/parseExample response (success):
{
"input": "mi klama le zarci",
"logical": "non-veridical: zarci(c0)\nklama(mi,c0)",
"canonical": "ju'a nai cy no zarci\n.i mi klama cy no",
"tree": [
{
"type": "modal",
"modal": { "type": "veridical", "tag": "non-veridical", "term": null },
"child": {
"type": "relation",
"relation": { "name": "zarci", "type": "brivla" },
"terms": [{ "type": "constant", "value": "c0" }]
}
},
{
"type": "relation",
"relation": { "name": "klama", "type": "brivla" },
"terms": [
{ "type": "named", "value": "mi" },
{ "type": "constant", "value": "c0" }
]
}
],
"error": null
}Example response (parse error):
{
"input": "mi klama",
"logical": null,
"canonical": null,
"tree": null,
"error": "Parse error:\n\t{...}\n\t ^"
}curl -s http://localhost:8080/health
# okWith the container running (docker run -d -p 8080:8080 --name tersmu-api tersmu):
./test_api_examples.shOptional: use a different API base URL:
TERSMU_API_URL=http://your-host:8080 ./test_api_examples.shWhen run locally (see Installation) or via docker run ... --entrypoint tersmu tersmu:
tersmu [OPTIONS] [FILE]- No FILE: read from stdin (interactive; one paragraph per input).
- FILE: read from file. Use
-L/--linesto treat each line as a separate Lojban text.
Options:
| Option | Long | Description |
|---|---|---|
-l |
--loj |
Output logical form only |
-j |
--jbo |
Output forethoughtful Lojban form only |
-L |
--lines |
One line = one Lojban text |
-p |
--paragraphs |
One blank-line-separated block = one text |
-u |
--utf8 |
Output UTF-8 (default: ASCII) |
-h |
--help |
Show help |
-v |
--version |
Show version |
--json |
Output one JSON object per line (NDJSON) with input, logical, canonical, tree, error |
Examples:
# Parse a file (one line per sentence)
tersmu -L examples/1.jbo
# Parse stdin
echo "mi klama le zarci" | tersmu -L| Path | Description |
|---|---|
| docs/overview.md | Architecture and code overview |
| docs/BUGS.md | Known limitations |
| docs/TODO.md | Planned work |
| docs/CHANGELOG.md | Version history |
| docs/design-notes.md | Design notes |
| docs/notes/ | Topic notes (bridi-operators, drt, illocution, questions, etc.) |
tersmu can be compiled to WebAssembly for use in web browsers.
The easiest way to build the WASM module is using the provided script, which uses a specialized Docker environment (Dockerfile.wasm) to handle the cross-compilation toolchain:
./build_wasm.shThis script:
- Builds the
tersmu-wasm-builderDocker image. - Extracts the compiled
tersmu.wasmfrom the container. - Places it in the
wasm-web-app/directory alongside the HTML/JS frontend.
Once built, you can serve the web app locally:
cd wasm-web-app
python3 -m http.server 8000Then open http://localhost:8000 in your browser.
See wasm-web-app/README.md for more details.
- Parser sources: The canonical grammar is in Pest format (
.pest+.pappy.rhs) for portability to Rust. The Makefile generatesLojban.pappyandMorphology.pappyfromLojban.pest/Lojban.pappy.rhsandMorphology.pest/Morphology.pappy.rhsviascripts/gen_pappy.py, then a patched Pappy generatesLojban.hsandMorphology.hs. To refresh.pestand.pappy.rhsfrom edited.pappyfiles, runpython3 scripts/pappy_to_pest.py Lojban.pappy(and similarly for Morphology). See the Makefile for targets and dependencies. - Architecture: See docs/overview.md.
- License: GPL-3. See COPYING.
- Thanks: John Clifford, selpa'i, Alex Burka, tsani, and especially Jorge Llambías (see docs/THANKS.md).