Add GrammarJsonServices: deterministic LCM Grammar JSON export#392
Closed
johnml1135 wants to merge 1 commit into
Closed
Add GrammarJsonServices: deterministic LCM Grammar JSON export#392johnml1135 wants to merge 1 commit into
johnml1135 wants to merge 1 commit into
Conversation
Contributor
|
What's the actual schema for this? Also is there a reason you didn't define a class to represent the shape of the data and then just call serialize on it? That feels like it would be much simpler and less error prone than using a json text writer. |
Contributor
|
Jason is out of town for a week. I don't think that this should be approved until he has had a chance to look at it. |
johnml1135
force-pushed
the
feature/grammar-json-export
branch
4 times, most recently
from
July 17, 2026 12:28
7b878cf to
b664523
Compare
LCM Grammar JSON ({"format":"lcm-grammar","version":1}) is a
deterministic, GUID-keyed JSON projection of the parser-relevant subset
of a project - phonology, morphology, and lexicon - for external
morphological-parser tooling: grammar verification ("does this change
parse better?"), conformance fixtures, and field deployment (a ~56 MB
.fwdata project exports to ~2.4 MB pretty-printed, ~250 KB gzipped,
loadable by web/wasm parsers without LibLCM). It is a read-only
projection: not an editing, synchronization, or storage format.
The exporter, SIL.LCModel.DomainServices.GrammarJsonServices, sits next
to M3ModelExportServices and follows the same pattern: one static
service class, no new project, no new runtime dependency.
The contract ships with the code and cannot drift from it:
- doc/lcm-grammar.md - the specification: conventions (GUID keys,
determinism/ordering rules incl. normative key order, omission
semantics, tagged unions, never-silent skips), per-section structure,
a referential-integrity section stating each reference field's
resolution scope (the schema checks GUID shape, never existence),
the additive-only versioning policy, and an appendix of built-in
FieldWorks GUIDs (word boundary, the 17 morph types) so importers
can be self-contained.
- doc/lcm-grammar.schema.json - strict JSON Schema (draft-07),
enforced by unit tests: empty-project and populated-project exports
must validate.
Determinism: owning/reference sequences keep model order (rule order,
slot order, allomorph disjunctive order are semantic); unordered
collections sort by GUID string; multistring values sort by writing-
system tag. Two exports of the same data are byte-identical.
Unrepresentable data (unknown morph types, dangling references,
malformed parser-parameter XML) is skipped with a message in an
optional warnings collection, never silently. An affix process with an
unrepresentable input part is skipped whole, since its output mappings
reference input parts by position.
17 NUnit tests (memory-only backend) cover schema validation, byte-
determinism, entry/phonology/MSA/sense round-trips, GUID sort order,
parser-parameter parsing, affix processes, ad hoc rules, entry-ref
discrimination, and warn-on-skip behavior. The exporter was reviewed
field-by-field against the format's independent reference
implementation (a Rust .fwdata reader producing the same format); a
cross-implementation byte-equality gate on real projects is a planned
follow-up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
johnml1135
force-pushed
the
feature/grammar-json-export
branch
from
July 17, 2026 13:14
b664523 to
d564a71
Compare
Contributor
Author
|
This is being closed for the time being in favor of using the HC grammar export. This may be revisited in the future, as it does hold promise in the right domain, especially for a richer basis for a conformance suite and a more configurable distribution format. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
The format contract:
doc/lcm-grammar.md— the LCM Grammar JSON specification: conventions (GUID keys, determinism/ordering rules, omission rules, tagged unions, never-silent skips), per-section structure, additive-only versioning policy, and an appendix of built-in FieldWorks GUIDs (word boundary, the 17 morph types) so future importers can be self-contained.doc/lcm-grammar.schema.json— the machine-checkable JSON Schema (draft-07, strictadditionalProperties). Enforced by unit tests: empty-project and populated-project exports must validate, so the schema and the exporter cannot drift apart.The exporter:
SIL.LCModel.DomainServices.GrammarJsonServices.ExportGrammar(LcmCache, TextWriter, ICollection<string> warnings = null)— a deterministic JSON export of the parser-relevant subset of a project (phonology, morphology, lexicon), with the envelope{"format":"lcm-grammar","version":1,...}. It sits next toM3ModelExportServices(the existing parser-model export) and follows the same pattern: one static service class, no new project, no new runtime dependency (Newtonsoft.Json is already in the tree via SIL.LCModel.Utils; NJsonSchema is test-only, MIT).What it's for
An interchange format for external morphological-parser tooling:
It is a read-only projection: explicitly not an editing format, not a sync/merge format, and not a replacement for fwdata.
Design points
{"ws","form"}arrays sorted by tag. Two exports of the same data are byte-identical.HCLoader/M3ModelExportServicesconsumption (e.g. natural-class names come fromAbbreviation,notOnCliticsdefaults to true, the word-boundary marker is excluded fromboundaryMarkers).Tests
17 NUnit tests in
GrammarJsonServicesTests(memory-only backend): schema validation of empty and populated exports, envelope/sections, byte-determinism, lexical entry round-trip, GUID sort order, phonology, parser-parameter defaults and XML parsing, affix-process export and skip-on-unrepresentable-input, morpheme ad hoc rule adjacency, entry-ref variant/complex-form discrimination, and warn-on-skip behavior.Review notes
The exporter was additionally reviewed against the format's independent reference implementation (a Rust .fwdata reader that produces the same format) and the findings folded in — notably the entry-ref discriminator edge case (a ref carrying both variant and complex-form types is a variant) and positional-index alignment for affix-process mappings.
Beyond the reference-implementation review, the exporter was adversarially probed at runtime (hostile data driving every skip path — this found and fixed two schema-contract violations where all-items-skipped collections emitted
[]) and smoke-tested against a real 1,462-entry project opened through LibLCM: the export is schema-clean with zero warnings and semantically identical to the reference implementation's output. That smoke test surfaced and fixed two real-data bugs: phoneme names authored only in vernacular writing systems were silently lost (name fields now fall back analysis-then-vernacular), and output text was NFD (LCM's in-memory form) rather than the NFC of the .fwdata at-rest form — exports are now NFC, preserving the byte-reproducibility contract.A fourth review round tested the contract itself: a "naive importer" reviewer working from the spec + schema + a real export alone (no source access) drove several contract hardenings — a normative key-order rule, a referential-integrity section stating each reference field's resolution scope (notably:
sense.msaresolves document-wide, proven necessary by real Sena 3 data, which the exporter now warns about), documentedfeatureTypeopacity, and a tightenedcomplexFormschema branch. A second real-project smoke test (Amharic: 417 Ethiopic phonemes, real templatic affix processes) came back fully clean — schema-valid, zero warnings, byte-exact Ethiopic text, and structurally identical to the reference implementation.A cross-implementation byte-equality gate against real projects (Sena 3, Amharic) is a planned follow-up.
🤖 Generated with Claude Code
This change is