Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# VCS
.git
.github/
.gitattributes
.gitignore

# OS/editor junk
.DS_Store
Thumbs.db
*.swp
*.swo
*~
*.log
.idea/
.vscode/

# Local build outputs
bin/

src/**/*.o
src/**/*.a

src/anal/cruncher
src/anal/deverbal
src/anal/findbase
src/anal/morpheus
src/anal/pname

src/gener/checkstype
src/gener/do_conj
src/gener/gener

src/gkdict/combitype
src/gkdict/conj1
src/gkdict/fixgend
src/gkdict/fixhesc
src/gkdict/indexcomps
src/gkdict/indexnoms
src/gkdict/indexvbs
src/gkdict/latnom
src/gkdict/latvb
src/gkdict/newlems
src/gkdict/newlems2
src/gkdict/setquant
src/gkdict/splitlat
src/gkdict/splitlems

src/gkends/buildderiv
src/gkends/buildend
src/gkends/buildword
src/gkends/indderivtables
src/gkends/indendtables

# update.sh outputs
stemlib/Latin/conjfile
stemlib/Latin/oddfile

stemlib/**/endtables/ascii/**
stemlib/**/endtables/indices/**
stemlib/**/endtables/out/**

stemlib/**/derivs/ascii/
stemlib/**/derivs/indices/
stemlib/**/derivs/out/

stemlib/**/steminds/

stemlib/Latin/stemsrc/nom.irreg
stemlib/Latin/stemsrc/vbs.irreg

# Optional: repo docs not needed for the build
doc/
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ bin
src/anal/cruncher
src/anal/deverbal
src/anal/findbase
src/anal/morpheus
src/anal/pname
src/gener/checkstype
src/gener/do_conj
Expand Down
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM ubuntu:24.04

ARG DEBIAN_FRONTEND=noninteractive

# Install dependencies for compiling and for the data generation scripts
RUN apt-get update -qq && apt-get install -qq -y \
build-essential \
flex \
perl

ADD . /morpheus
WORKDIR /morpheus

# fix line endings / make executable for the perl script (still needed)
RUN chmod +x stemlib/Greek/getentities.pl && \
sed -i 's/\r$//' stemlib/Greek/getentities.pl

RUN make && \
echo "salve" | MORPHLIB=stemlib bin/cruncher -L
62 changes: 62 additions & 0 deletions README_DOCKER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
Docker
======

### Build the image

```bash
docker build -t [IMAGE_ID] .
```
[<a href="#fn1" id="ref1">1</a>]

### Run the image

You can run the tools either interactively or directly via a one-shot command.

#### Option A: Interactively

``` bash
docker run -it --rm [IMAGE_ID] bash
```

Inside the container:

- **cruncher** (always available):

```bash
echo "rosa" | MORPHLIB=stemlib bin/cruncher -L
```
[<a href="#fn2" id="ref2">2</a>]

- **morpheus** XML output [<a href="#fn3" id="ref3">3</a>]

```bash
echo "rosa" | MORPHLIB=stemlib bin/morpheus -L
```

#### Option B: One-shot command

You can also execute a run directly from your host without entering an
interactive shell:

```bash
docker run --rm [IMAGE_ID] /bin/sh -c 'echo "rosa" | MORPHLIB=stemlib bin/cruncher -L'
```

### Footnotes

<ol>
<li id="fn1">
IMAGE_ID stands for any name you want to give
<a href="#ref1">↩</a>
</li>
<li id="fn2">
"-L" selects Latin; drop it for Greek (the default). See the main
README for the rest of the flags
<a href="#ref2">↩</a>
</li>
<li id="fn3">
Only if this build includes it; check first
with "ls bin/morpheus" inside the container, or "docker run --rm [IMAGE_ID] ls bin/" from outside
<a href="#ref3">↩</a>
</li>
</ol>
207 changes: 207 additions & 0 deletions README_XML.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
morpheus XML output
====================

This branch adds `morpheus`, a second binary alongside `cruncher` that shares
the same analysis pipeline (`AnalyzeString`, `SortAnals`, `GoodAnals`) but
emits Perseus-style XML instead of the `<NL>`-line format. It is unconditional:
morpheus always emits XML, there is no flag to switch it off.

Why XML
-------

`cruncher`'s Perseus format represents each analysis as a single positional
text line: abbreviated vocabulary (`nom`, `masc`, `pres`), a single
part-of-speech prefix (`P/N/V/E/I`), and multi-value fields on one line
(e.g. `nom/voc/acc masc/fem pl`). morpheus renders the same analyses as a
structured document instead: full-word vocabulary, a refined part of speech
per analysis, and multi-value fields expanded into a cartesian product of
`<infl>` elements (one case/gender/etc. combination per element), so
downstream code doesn't have to re-parse a slash-separated field.

Because both binaries run through the same `SortAnals`/`GoodAnals` filtering,
the *set* of analyses is guaranteed identical between the two. XML is a
different serialization of the same data, not a different analysis.

Building
--------

`morpheus` is built as part of the normal top-level build:
```bash
make
```

It installs to `bin/morpheus` alongside `cruncher`.

Architecture / rebase isolation
--------------------------------

This is meant to be a long-lived branch that stays rebasable against
upstream. The XML support is kept isolated to two files:

- `src/anal/morpheus.c` — dedicated main loop (arg parsing, word intake).
- `src/anal/prntalph.c` — XML formatting and vocabulary-mapping tables.

The core analysis pipeline and dictionary compilation (`checkstring`,
`AnalyzeString`, `gener/`, `gkdict/`) are untouched. If you rebase and the
dictionary changes, `morpheus`'s output changes exactly as much as
`cruncher`'s does. No separate mapping logic to keep in sync.

Running morpheus
-----------------
```bash
MORPHLIB=stemlib bin/morpheus < wordlist
```

Words can also be given directly as arguments instead of on stdin:

```bash
MORPHLIB=stemlib bin/morpheus lo/gos timh/
```

For Latin, add -L:

```bash
echo "rosa" | MORPHLIB=stemlib bin/morpheus -L
```

```bash
MORPHLIB=stemlib bin/morpheus -L rosa dominus
```

The `echo | ...` form pipes the word in on stdin; the
`bin/morpheus -L rosa dominus` form passes words as argv arguments instead.
Both produce identical output for a single word — see above for how the two
input paths differ.

Each argv word is analyzed as its own word (not split further); after argv words
are consumed, morpheus exits rather than falling through to read stdin.

### Flags

Shares most analysis flags with `cruncher`:

`-L` Latin instead of Greek. `-I` Italian. `-S` disables strict case matching
(allows capitalized/sentence-initial words). `-n` accent-insensitive retry.
`-V` verb forms only. `-i` adds a `<dump_analysis>` block per analysis with
low-level internal fields (raw stem/suffix/preverb pieces, morphflag bytes).

`-c`, `-p`, `-x` are accepted (for `getopt` parity with `cruncher`'s flag set)
but currently have no effect on morpheus's output: it always produces XML
regardless.

### Output format

Root element is `<words>...</words>`. Comment-only lines in the input
(starting with `#`) pass through as XML comments; literal `--` inside a
comment is neutralized to `- ` so it can't collide with `<!--`/`-->`.

Each analyzed word is a `<word>` element containing a `<form>` (the input
form) and one `<entry>` per distinct lemma. Each `<entry>` has a `<dict>`
block (`<hdwd>` = lemma, `<pofs>` = part of speech with an `order` attribute,
`<gend>` for nouns) followed by one `<infl>` per case/gender/mood/tense/etc.
combination that analysis covers.

Unrecognized words are emitted inline as `<unknown xml:lang="...">word</unknown>`
rather than being dropped to stderr the way `cruncher` handles them.

If there are no words at all (empty stdin, no argv words, no comments),
morpheus emits `<error>No words provided</error>` with no `<words>` wrapper
and exits `2`.

### Intentional divergences from cruncher

Deliberate, not bugs. See `tests/run_xml_tests.sh` (tests N1–N8) for the
checks that pin them down:

- Long-form vocabulary (`nominative`, `singular`) instead of cruncher's
abbreviations (`nom`, `sg`).
- `<pofs>` is a refined category (pronoun/article/preposition/etc., not just
cruncher's coarse `P/N/V/E/I` prefix), with a sentinel `order="0"` for
categories not in the ordering table.
- Case/gender combinations expand into a cartesian product of `<infl>`
elements (one per combination) rather than a single combined cruncher line.
- A line with multiple whitespace-separated words only analyzes the first
token (matches cruncher's batch behavior; diverges from the original
upstream alpheios code, which analyzed every token on the line).

Testing
-------

```bash
bash tests/run_xml_tests.sh
```

This is intentionally independent of `make test` / `tests/run_tests.sh` and
is not wired into the Makefile. Run it by hand. To (re)generate golden
baselines:

```bash
bash tests/run_xml_tests.sh --update
```

Only regenerate a baseline after you've deliberately changed something that
affects that fixture's output *and* manually confirmed the new output is
correct. A baseline diff on its own is not a reason to `--update`; it's the
suite's signal to go look at what changed, not to silently accept it.

### Test ID legend

IDs are grouped by what they assert:

- **P*n*** — Positive: morpheus **should** agree with cruncher on the same
word (same known/unknown status, same lemmas, same stemtypes, etc.), or
otherwise behave as documented (flag parsing, argv handling). A failure
here means morpheus disagrees with cruncher, or with its own documented
behavior, where it shouldn't.
- **N*n*** — Negative / divergence: morpheus is **deliberately** different
from cruncher here (richer vocabulary, expanded fields, inline `<unknown>`,
etc.). A failure here means morpheus accidentally matches cruncher's old
behavior where it was supposed to have changed it, or produced something
outside the intended divergence.

Plain-English meaning of each:

| ID | What it checks |
|----------------|-------------------------------------------------------------------------|
| flag-n-arg | `-n` doesn't swallow the next argv word as an option-argument |
| argv-cleanup | argv words get the same digit-trimming cleanup as stdin words |
| P1 | known/unknown word partition matches cruncher |
| P2 | lemma set and first-occurrence order match cruncher |
| P3 | stemtype set matches cruncher |
| P4 | (soft) feature-token coverage (approximate, see comparator notes) |
| P5 | (soft) case/gender cartesian-expansion count (approximate) |
| P6 | determinism: same input twice gives byte-identical output |
| P7 | exit codes, and the empty-input error document is well-formed |
| P8 | after consuming argv words, morpheus exits instead of hanging on stdin |
| N1 | long-form vocabulary (`nominative`) instead of cruncher's abbreviations |
| N2 | refined `<pofs>` categories, with a sentinel `order="0"` fallback |
| N3 | runtime XML escaping (literal `&amp;`) instead of a pre-escaped table |
| N4 | unknown words appear inline as `<unknown>` instead of going to stderr |
| N5 | dialect table isn't truncated early; `prose`/`Homeric`/etc. reachable |
| N6 | no literal `(null)` text leaks into gender/pofs output |
| N7 | comment lines produce a single well-formed `<words>` document |
| N8 | a multi-token input line only analyzes the first token |

### Result levels

- `PASS`/`FAIL` — hard checks: build/link smoke tests, well-formedness,
flag/argv-handling checks (flag-n-arg, argv-cleanup, P8), known/unknown-word
partition parity (P1), lemma set/order parity (P2), stemtype set parity
(P3), determinism (P6), exit codes (P7), and the N1–N8 divergence checks.
A `FAIL` here blocks the suite (`exit 1`).
- `WARN` — soft checks: golden-baseline text diffs (informational — see
above), and feature-token coverage (P4) / case-gender expansion counts (P5)
from the comparator script, which use an approximate normalization map and
can produce false positives on multi-value `<dial>`/`<morph>` tags. Don't
block on these without reading the diff first.

P4 deliberately excludes anything rendered via `<gend>` as gender is
already validated via P1–P3 and N1, and P4 checking it too would just be
redundant duplication of a stricter check.
- `XFAIL`/`XPASS` — reserved for a known, already-diagnosed defect that's
expected to fail until fixed. None currently active in this suite; if you
introduce one, add the `xfail`/`xpass` wrapper and a comment explaining
what's broken, and convert it back to plain `pass`/`fail` once fixed.

Requires `python3` (or `xmllint`) for well-formedness checks and
`bin/cruncher` for the parity suite. Both are skipped gracefully if absent.
Loading