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}