1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
//! # lang_forge
//!
//! LexerSketch: forge a working language front end — lexer, parser, and
//! lossless syntax tree — from a `.lsf` schematic.
//!
//! A schematic is a short NOML document that describes a language: its name,
//! how its tokens look, its grammar, and the capabilities (passes) it
//! includes. [`Language::from_lsf`] reads it, checks it, and compiles it into
//! tables; [`Language::parse`] then turns source text into a
//! [`syntax_lang::Node`] tree with [`diag_lang::Diagnostic`]s for anything
//! malformed. There is no code generation step and nothing to build: the
//! language is ready the moment the schematic is forged.
//!
//! lang-forge is the capstone of the `-lang` language-construction family. Its
//! trees are `syntax-lang` trees, the family's lossless CST, which the
//! formatter, incremental reparser, language server, and tree-sitter crates
//! are designed to consume; the adapters that connect a forged language to
//! those crates are not part of lang-forge and arrive with LexerSketch. Its
//! diagnostics render with `diag-lang`, and its capabilities run on
//! `pass-lang`.
//!
//! ## A first language
//!
//! ```
//! use lang_forge::Language;
//!
//! let calc = Language::from_lsf(
//! r##"
//! [language]
//! name = "calc"
//!
//! [lexer]
//! line_comments = ["#"]
//!
//! [rules]
//! program = "stmt*"
//! stmt = "'let' IDENT '=' expr ';' | expr ';'"
//! group = "'(' expr ')'"
//!
//! [rules.expr]
//! operand = "NUMBER | IDENT | group"
//! levels = [
//! { left = ["+", "-"] },
//! { left = ["*", "/"] },
//! { prefix = ["-"] },
//! { right = ["^"] },
//! ]
//! "##,
//! )?;
//!
//! let parse = calc.parse("let area = 3 * r ^ 2; # circle-ish\n");
//! assert!(!parse.has_errors());
//!
//! // `^` binds tighter than `*`, so the product's right operand is `r ^ 2`.
//! let binary = calc.kind("binary").expect("the default operator node");
//! let product = parse.tree().descendants().find(|n| *n.kind() == binary).expect("3 * r ^ 2");
//! assert_eq!(product.text(parse.source()), Some("3 * r ^ 2"));
//! assert_eq!(product.child_nodes().last().and_then(|n| n.text(parse.source())), Some("r ^ 2"));
//! # Ok::<(), lang_forge::Error>(())
//! ```
//!
//! ## The rule language
//!
//! Each entry of `[rules]` is a rule. A string rule is a sequence of elements:
//!
//! | Element | Matches |
//! |---|---|
//! | `'text'` or `"text"` | a keyword (if it looks like an identifier) or a symbol |
//! | `IDENT`, `NUMBER`, `STRING`, `NEWLINE` | a token of that built-in class |
//! | `name` | the rule `name`, as a child node |
//! | `a b` | `a` then `b` |
//! | `a \| b` | `a`, or else `b` — the first that matches wins |
//! | `a*`, `a+`, `a?` | zero or more, one or more, zero or one |
//! | `( ... )` | grouping |
//!
//! A rule builds a node named after itself, unless its name starts with `_`,
//! in which case its children are placed directly in the parent. The first
//! rule is the start rule unless `[language] start` names another; its node
//! is the root of every tree.
//!
//! A table rule, `[rules.name]`, is an *expression rule*: an `operand` and
//! operator `levels`, lowest precedence first. Each level is `left`,
//! `right`, or `none` (binary, by associativity), `prefix`, or `postfix`,
//! with an optional `then` (more grammar after the operator, for calls,
//! indexing, or `?:`) and an optional `node` name.
//!
//! ## Format 2
//!
//! A sketch that says `[sketch] format = 2` uses LSF2's syntax: custom token
//! classes (regular expressions), lexer modes with a mode stack, string
//! classes with interpolation, heredocs, and raw delimiters, contextual and
//! case-insensitive keywords, indentation layout, labelled fields on tree
//! edges, predicates (`&e`, `!e`), text back-references, `EOF` /
//! `LINE_START` / `NL_BEFORE`, injections, `[ast]` supertypes, and a check
//! that rejects greedy repetitions that would silently reject valid input.
//! A sketch with no `[sketch]` table, or `format = 1`, forges exactly as
//! lang-forge 1.x did.
//!
//! ```
//! use lang_forge::Language;
//!
//! let lang = Language::from_lsf(
//! r##"
//! [sketch]
//! format = 2
//!
//! [language]
//! name = "tmpl"
//! version = "1.0.0"
//!
//! [lexer]
//! initial_mode = "text"
//!
//! [lexer.tokens]
//! OPEN = { literal = "{{", modes = ["text"], action = "push main" }
//! CLOSE = { literal = "}}", action = "pop" }
//! VAR = { regex = '\$[a-z]+' }
//!
//! [lexer.modes.text]
//! tokens = ["OPEN"]
//! text = "TEXT"
//!
//! [rules]
//! page = "(TEXT | hole)*"
//! hole = "OPEN value:VAR filters:('|' IDENT)* CLOSE"
//! "##,
//! )?;
//!
//! let parse = lang.parse("Hello, {{ $name | upper }}!");
//! assert!(!parse.has_errors());
//! let hole = parse.tree().child_nodes().next().expect("a hole");
//! let fields: Vec<&str> = (0..hole.len())
//! .filter_map(|i| lang.field_label(hole, i).and_then(|l| lang.label_name(l)))
//! .collect();
//! assert_eq!(fields, ["value", "filters", "filters"]);
//! # Ok::<(), lang_forge::Error>(())
//! ```
//!
//! A forged language can be saved as an image ([`Language::to_image`]) and
//! loaded without forging ([`Language::from_image`]); a sketch can span
//! several files ([`Sketch`]); and every diagnostic carries a stable code
//! (`LSF` for sketches, `LF0xxx` lexical and `LF1xxx` parse errors in
//! source).
//!
//! ## Errors and recovery
//!
//! Forging reports every problem in a schematic at once, each with a span
//! into the schematic ([`Error`]). Parsing never fails: a missing token is
//! reported and assumed, an unexpected token is reported and wrapped in an
//! `ERROR` node, and the tree always covers the whole source.
//!
//! ## Features
//!
//! - `std` (default) — the standard library. Without it the crate is
//! `no_std` and needs only `alloc`.
//!
//! ## Re-exports
//!
//! [`syntax_lang`], [`diag_lang`], and [`pass_lang`] are re-exported whole, so
//! code that walks trees, renders diagnostics, or writes capability passes
//! names the same versions lang-forge was built against.
//!
//! ## Stability
//!
//! This is `2.0.0-alpha.1`, a pre-release of the next major version. The
//! format-1 surface and behaviour of 1.x are kept (format-1 sketches forge
//! and parse exactly as before); the breaking changes are listed in the
//! CHANGELOG with a migration guide. The format-2 surface may still change
//! before `2.0.0`. The 1.x promise and what 2.0 changes are set out in
//! [`docs/API.md`](https://github.com/jamesgober/lang-forge/blob/main/docs/API.md#stability).
extern crate alloc;
extern crate std;
pub use Error;
pub use ;
pub use ;
pub use Kind;
pub use ;
pub use ;
pub use Sketch;
// Re-exported whole: trees are `syntax_lang` trees, problems are `diag_lang`
// diagnostics, and capabilities are `pass_lang` passes.
pub use diag_lang;
pub use pass_lang;
pub use syntax_lang;
/// Compiles and runs the `rust` code blocks in `README.md` and `docs/API.md` as
/// part of `cargo test`, so the published examples cannot drift from the API.
///
/// Present only while collecting doctests (`#[cfg(doctest)]`); it is not part of
/// the public surface and does not appear in the built library or its docs.
;