Skip to main content

nibli_types/
ast.rs

1//! AST types produced by the nibli-kr emitter — the INTERNAL interchange
2//! between the front-end and the semantic compiler, NOT a WIT boundary.
3//!
4//! `AstBuffer` is produced by nibli-kr's validating emit walk and consumed by
5//! `nibli_semantics::compile_from_ast` over a plain Rust function call; only
6//! `logic.rs`'s `LogicBuffer` crosses the WASM component boundary. The type
7//! lives HERE (not in nibli-kr) for three reasons:
8//! - dependency direction: nibli-semantics must not depend on nibli-kr;
9//! - it is render's INPUT: `nibli_kr::render` spells buffers back to KR text
10//!   (the fixpoint / round-trip layer);
11//! - it is the validated PROGRAMMATIC-BUILD target: hand-built buffers enter
12//!   compilation through `validate_ast_buffer` (index-bounds + acyclicity —
13//!   the "corrupt AST buffer" reject), so tools may construct ASTs directly.
14//!
15//! The flat shape — parallel `Predicate`/`Argument`/`Sentence` arrays with
16//! `u32` child indices — is a WIT-era inheritance KEPT for properties that
17//! are load-bearing today: `parse_text`'s per-statement recovery rolls a
18//! failed statement back by truncating the four Vecs, and structural
19//! validation is an iterative index-walk (no recursion to overflow on
20//! adversarially deep inputs).
21
22/// Index into the `predicates` array of an `AstBuffer`.
23pub type PredicateId = u32;
24/// Index into the `arguments` array of an `AstBuffer`.
25pub type ArgumentId = u32;
26
27/// Modal tag: a modal built from a predicate reference (the `via` tag) —
28/// a newtype over the tagged predicate's id.
29#[derive(Clone, Copy, Debug)]
30pub struct ModalTag(pub PredicateId);
31
32/// Place conversion: permutes the x1 place with another.
33/// Swap12=x1↔x2, Swap13=x1↔x3, Swap14=x1↔x4, Swap15=x1↔x5.
34#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
35pub enum Conversion {
36    Swap12,
37    Swap13,
38    Swap14,
39    Swap15,
40}
41
42/// Logical connective: AND(∧), OR(∨), IFF(↔), XOR(⊕).
43#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
44pub enum Connective {
45    And,
46    Or,
47    Iff,
48    Xor,
49}
50
51/// Determiner (article/descriptor): determines how a description term binds.
52#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
53pub enum Determiner {
54    /// Indefinite description (at least one entity satisfying the predicate).
55    Indefinite,
56    /// Definite description (an opaque rigid designator).
57    Definite,
58    /// Universal over an indefinite description (`every X`).
59    Every,
60    /// Universal over a definite description (`every the X`).
61    EveryThe,
62}
63
64/// Abstraction kind: wraps a sub-sentence into a argument.
65#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
66pub enum AbstractionKind {
67    /// Event abstraction.
68    Event,
69    /// Propositional (fact) abstraction.
70    Fact,
71    /// Property abstraction.
72    Property,
73    /// Quantity/amount abstraction.
74    Amount,
75    /// Concept abstraction.
76    Concept,
77}
78
79/// Relative clause kind: restrictive or non-restrictive (incidental).
80#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
81pub enum RelClauseKind {
82    /// Restrictive relative clause.
83    Restrictive,
84    /// Non-restrictive (incidental) relative clause.
85    Incidental,
86}
87
88/// A relative clause attached to a argument.
89#[derive(Clone, Debug)]
90pub struct RelClause {
91    pub kind: RelClauseKind,
92    /// Index into the `sentences` array of the containing `AstBuffer`.
93    pub body_sentence: u32,
94}
95
96/// A positional marker: not a constant — nibli-semantics consumes each as a
97/// binding signal (`it` → the enclosing rel-clause's bound entity, `slot` →
98/// the enclosing `property { … }`'s open place, `?` → a fresh witness
99/// variable).
100#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
101pub enum Marker {
102    /// The relativized entity of a where/also clause body (`it`).
103    It,
104    /// The open place of a `property { … }` body (`slot`).
105    Slot,
106    /// The witness marker (`?`): binds a fresh variable per occurrence.
107    Witness,
108}
109
110/// The fixed pronoun inventory — the closed set of pro-argument constants.
111/// [`Pronoun::as_str`] is the SINGLE spelling authority (the emit walk
112/// constructs from these spellings' keywords, render prints them, and
113/// nibli-semantics interns them as constants); a conformance test pins every
114/// spelling against nibli-lexicon's reserved-word list and the surface
115/// round-trip.
116#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
117pub enum Pronoun {
118    Me,
119    You,
120    We,
121    WeAll,
122    WeOthers,
123    YouAll,
124    This,
125    That,
126    Yonder,
127    ItA,
128    ItE,
129    ItI,
130    ItO,
131    ItU,
132}
133
134impl Pronoun {
135    /// The canonical KR spelling — also the interned constant's identity.
136    pub fn as_str(&self) -> &'static str {
137        match self {
138            Pronoun::Me => "me",
139            Pronoun::You => "you",
140            Pronoun::We => "we",
141            Pronoun::WeAll => "we_all",
142            Pronoun::WeOthers => "we_others",
143            Pronoun::YouAll => "you_all",
144            Pronoun::This => "this",
145            Pronoun::That => "that",
146            Pronoun::Yonder => "yonder",
147            Pronoun::ItA => "it_a",
148            Pronoun::ItE => "it_e",
149            Pronoun::ItI => "it_i",
150            Pronoun::ItO => "it_o",
151            Pronoun::ItU => "it_u",
152        }
153    }
154
155    /// Every variant, for conformance sweeps.
156    pub const ALL: [Pronoun; 14] = [
157        Pronoun::Me,
158        Pronoun::You,
159        Pronoun::We,
160        Pronoun::WeAll,
161        Pronoun::WeOthers,
162        Pronoun::YouAll,
163        Pronoun::This,
164        Pronoun::That,
165        Pronoun::Yonder,
166        Pronoun::ItA,
167        Pronoun::ItE,
168        Pronoun::ItI,
169        Pronoun::ItO,
170        Pronoun::ItU,
171    ];
172}
173
174/// A argument (argument term) in the AST.
175#[derive(Clone, Debug)]
176pub enum Argument {
177    /// A `$`-sigiled logic variable, preserved VERBATIM (the sigil IS the
178    /// variable signal all the way through the IR — the interner and the
179    /// free-variable/scope passes key on the `$`-prefixed string, and proof
180    /// traces display it). INVARIANT: the payload starts with `$`;
181    /// `validate_ast_buffer` rejects a sigil-less payload as corrupt (a bare
182    /// name here would silently become a free, never-closed IR variable).
183    Variable(String),
184    /// A positional marker consumed by nibli-semantics (never a constant):
185    /// `it` (bound entity), `slot` (open place), `?` (witness).
186    Marker(Marker),
187    /// A fixed pronoun constant (me, you, we, we_all, …, it_u); lowers to an
188    /// interned constant of its [`Pronoun::as_str`] spelling.
189    Pronoun(Pronoun),
190    /// Determiner description: `some`/`the` + predicate. Fields: (determiner, predicate-id).
191    Description((Determiner, PredicateId)),
192    /// Named entity: a capitalized rigid Name.
193    Name(String),
194    /// Quoted string literal: `"any text"`.
195    QuotedLiteral(String),
196    /// Unspecified placeholder (`_` or an omitted place).
197    Unspecified,
198    /// Place-tagged argument: (zero-based place index 0..=4, inner-argument-id).
199    Tagged((u8, ArgumentId)),
200    /// Modal-tagged argument: (modal-tag, inner-argument-id).
201    ModalTagged((ModalTag, ArgumentId)),
202    /// Argument with a relative clause: (inner-argument-id, relative-clause).
203    Restricted((ArgumentId, RelClause)),
204    /// Numeric literal argument.
205    Number(f64),
206    /// Quantified description: `exactly N` + determiner + predicate.
207    /// Fields: (count, determiner, predicate-id).
208    QuantifiedDescription((u32, Determiner, PredicateId)),
209}
210
211/// A predicate (predicate relation) in the AST.
212#[derive(Clone, Debug)]
213pub enum Predicate {
214    /// Root relation: an atomic corpus name, or a compound entry's relation
215    /// ident (`computer_user`) — compounds resolve to their entry BEFORE
216    /// emission, so no compound structure survives into the AST.
217    Root(String),
218    /// Modifier+head pair (a compound predicate). Fields: (modifier-id, head-id).
219    Pair((PredicateId, PredicateId)),
220    /// SE-converted predicate. Fields: (conversion, inner-id).
221    Converted((Conversion, PredicateId)),
222    /// Negated predicate (`na`). Payload: inner-id.
223    Negated(PredicateId),
224    /// Grouped predicate (`[ ... ]` bracket group). Payload: inner-id.
225    Grouped(PredicateId),
226    /// Predicate with linked arguments. Fields: (core-id, argument-ids).
227    WithArgs((PredicateId, Vec<ArgumentId>)),
228    /// Abstraction: `event`/`fact`/`property`/`amount`/`concept` block.
229    /// Fields: (kind, sentence-id).
230    Abstraction((AbstractionKind, u32)),
231}
232
233/// Tense marker: past (pu), present (ca), future (ba).
234#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
235pub enum Tense {
236    Past,
237    Now,
238    Future,
239}
240
241/// Deontic deontic: ei (obligation/should), e'e (competence/permission/may).
242#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
243pub enum DeonticMood {
244    Obligation,
245    Permission,
246}
247
248/// A proposition (predication): predicate + argument terms + modifiers.
249#[derive(Clone, Debug)]
250pub struct Proposition {
251    pub relation: PredicateId,
252    /// All argument ids in EMISSION order: the explicit-x1 positional (when
253    /// present) FIRST, then the remaining positionals/tagged/modal args.
254    /// Consumers' place counters and surface-ordered scope markers depend on
255    /// this order (the old head/tail split's chained order).
256    pub terms: Vec<ArgumentId>,
257    /// Whether an explicit untagged x1 positional was written — when true it
258    /// is `terms[0]`. False ⇒ x1 is implicit: the rel-clause bound-entity
259    /// injection point in nibli-semantics, and render's bare-sugar /
260    /// inject-`it` spelling.
261    pub x1_present: bool,
262    pub negated: bool,
263    pub tense: Option<Tense>,
264    pub deontic: Option<DeonticMood>,
265}
266
267/// Sentence connective for sentence-level connection.
268#[derive(Clone, Debug)]
269pub enum SentenceConnective {
270    /// Conditional (implication) sentence connective.
271    Implies,
272    /// Conjunctive (and) sentence connective.
273    And,
274    /// Afterthought connective between two sentences.
275    Afterthought(Connective),
276}
277
278/// The quantifier kind of a [`Sentence::Quantified`] block.
279#[derive(Clone, Copy, Debug, Eq, PartialEq)]
280pub enum BlockQuant {
281    /// `exactly N X $v:` — exact-count over the indefinite restrictor.
282    ExactCount(u32),
283    /// `exactly N the X $v:` — exact-count over the opaque definite domain.
284    ExactCountDefinite(u32),
285    /// `every the X $v:` — universal over the opaque definite domain.
286    UniversalDefinite,
287}
288
289/// A sentence: a simple proposition, two connected sentences, a
290/// prenex-quantified body, or a quantified binder block.
291#[derive(Clone, Debug)]
292pub enum Sentence {
293    /// Simple predication.
294    Simple(Proposition),
295    /// Connected sentences. Fields: (connective, left-sentence-id, right-sentence-id).
296    Connected((SentenceConnective, u32, u32)),
297    /// Prenex `all $x, $y: <body>`: a sequence of universally quantified
298    /// logic variables scoping a body sentence. Fields: (variable names in
299    /// prenex order, body-sentence-id). Lowers to nested `∀` over the body
300    /// in nibli-semantics.
301    Prenex((Vec<String>, u32)),
302    /// Quantified binder block: `exactly N [the] X $v: body` / `every the X
303    /// $v: body`. Fields: (kind, variable particle `$v`,
304    /// restrictor-predicate-id, optional where-clause-sentence-id folded on
305    /// the DOMAIN side, body-sentence-id). The variable binds by name across
306    /// the whole block (the prenex mechanism); lowers to `Count{v, N,
307    /// And(domain, body)}` / `ForAll(v, Or(Not(domain), body))` in
308    /// nibli-semantics, where the definite kinds' domain is the opaque
309    /// `the_domain_<head>` restrictor. (`the X $v:` blocks never reach the
310    /// AST — they desugar by substitution at emission.)
311    Quantified((BlockQuant, String, PredicateId, Option<u32>, u32)),
312}
313
314/// Flat AST buffer: parallel arrays indexed by u32 IDs — the
315/// nibli-kr→nibli-semantics interchange and render's input (see the module
316/// doc; never crosses WASM). Hand-built buffers are validated at the compile
317/// boundary by `validate_ast_buffer`.
318#[derive(Clone, Debug)]
319pub struct AstBuffer {
320    pub predicates: Vec<Predicate>,
321    pub arguments: Vec<Argument>,
322    pub sentences: Vec<Sentence>,
323    /// Root sentence indices — one top-level sentence per statement.
324    pub roots: Vec<u32>,
325}
326
327/// A per-sentence parse error with location context.
328#[derive(Clone, Debug)]
329pub struct ParseError {
330    pub message: String,
331    pub line: u32,
332    pub column: u32,
333}
334
335/// Result of parsing: partial AST buffer + per-sentence errors.
336#[derive(Clone, Debug)]
337pub struct ParseResult {
338    pub buffer: AstBuffer,
339    pub errors: Vec<ParseError>,
340}