Skip to main content

lang_forge/
lib.rs

1//! # lang_forge
2//!
3//! LexerSketch: forge a working language front end — lexer, parser, and
4//! lossless syntax tree — from a `.lsf` schematic.
5//!
6//! A schematic is a short NOML document that describes a language: its name,
7//! how its tokens look, its grammar, and the capabilities (passes) it
8//! includes. [`Language::from_lsf`] reads it, checks it, and compiles it into
9//! tables; [`Language::parse`] then turns source text into a
10//! [`syntax_lang::Node`] tree with [`diag_lang::Diagnostic`]s for anything
11//! malformed. There is no code generation step and nothing to build: the
12//! language is ready the moment the schematic is forged.
13//!
14//! lang-forge is the capstone of the `-lang` language-construction family. Its
15//! trees are the family's lossless CST, so a forged language plugs straight
16//! into the formatter, incremental reparser, language server, and tree-sitter
17//! crates; its diagnostics render with `diag-lang`; and its capabilities run
18//! on `pass-lang`.
19//!
20//! ## A first language
21//!
22//! ```
23//! use lang_forge::Language;
24//!
25//! let calc = Language::from_lsf(
26//!     r##"
27//!     [language]
28//!     name = "calc"
29//!
30//!     [lexer]
31//!     line_comments = ["#"]
32//!
33//!     [rules]
34//!     program = "stmt*"
35//!     stmt    = "'let' IDENT '=' expr ';' | expr ';'"
36//!     group   = "'(' expr ')'"
37//!
38//!     [rules.expr]
39//!     operand = "NUMBER | IDENT | group"
40//!     levels  = [
41//!         { left   = ["+", "-"] },
42//!         { left   = ["*", "/"] },
43//!         { prefix = ["-"] },
44//!         { right  = ["^"] },
45//!     ]
46//!     "##,
47//! )?;
48//!
49//! let parse = calc.parse("let area = 3 * r ^ 2; # circle-ish\n");
50//! assert!(!parse.has_errors());
51//!
52//! // `^` binds tighter than `*`, so the product's right operand is `r ^ 2`.
53//! let binary = calc.kind("binary").expect("the default operator node");
54//! let product = parse.tree().descendants().find(|n| *n.kind() == binary).expect("3 * r ^ 2");
55//! assert_eq!(product.text(parse.source()), Some("3 * r ^ 2"));
56//! assert_eq!(product.child_nodes().last().and_then(|n| n.text(parse.source())), Some("r ^ 2"));
57//! # Ok::<(), lang_forge::Error>(())
58//! ```
59//!
60//! ## The rule language
61//!
62//! Each entry of `[rules]` is a rule. A string rule is a sequence of elements:
63//!
64//! | Element | Matches |
65//! |---|---|
66//! | `'text'` or `"text"` | a keyword (if it looks like an identifier) or a symbol |
67//! | `IDENT`, `NUMBER`, `STRING`, `NEWLINE` | a token of that built-in class |
68//! | `name` | the rule `name`, as a child node |
69//! | `a b` | `a` then `b` |
70//! | `a \| b` | `a`, or else `b` — the first that matches wins |
71//! | `a*`, `a+`, `a?` | zero or more, one or more, zero or one |
72//! | `( ... )` | grouping |
73//!
74//! A rule builds a node named after itself, unless its name starts with `_`,
75//! in which case its children are placed directly in the parent. The first
76//! rule is the start rule unless `[language] start` names another; its node
77//! is the root of every tree.
78//!
79//! A table rule, `[rules.name]`, is an *expression rule*: an `operand` and
80//! operator `levels`, lowest precedence first. Each level is `left`,
81//! `right`, or `none` (binary, by associativity), `prefix`, or `postfix`,
82//! with an optional `then` (more grammar after the operator, for calls,
83//! indexing, or `?:`) and an optional `node` name.
84//!
85//! ## Errors and recovery
86//!
87//! Forging reports every problem in a schematic at once, each with a span
88//! into the schematic ([`Error`]). Parsing never fails: a missing token is
89//! reported and assumed, an unexpected token is reported and wrapped in an
90//! `ERROR` node, and the tree always covers the whole source.
91//!
92//! ## Features
93//!
94//! - `std` (default) — the standard library. Without it the crate is
95//!   `no_std` and needs only `alloc`.
96//!
97//! ## Re-exports
98//!
99//! [`syntax_lang`], [`diag_lang`], and [`pass_lang`] are re-exported whole, so
100//! code that walks trees, renders diagnostics, or writes capability passes
101//! names the same versions lang-forge was built against.
102//!
103//! ## Stability
104//!
105//! The public surface is frozen as of `1.0.0` and follows Semantic Versioning:
106//! no breaking change before `2.0`, additions arrive in minor releases, and the
107//! MSRV (Rust 1.85) only rises in a minor. The promise covers the API, the
108//! `.lsf` schematic format, the trees forged languages build from valid input,
109//! and the parser's guarantees; it is set out in full in
110//! [`docs/API.md`](https://github.com/jamesgober/lang-forge/blob/main/docs/API.md#stability).
111
112#![cfg_attr(not(feature = "std"), no_std)]
113#![cfg_attr(docsrs, feature(doc_cfg))]
114#![forbid(unsafe_code)]
115#![deny(warnings)]
116#![deny(missing_docs)]
117#![deny(unsafe_op_in_unsafe_fn)]
118#![deny(unused_must_use)]
119#![deny(unused_results)]
120#![deny(clippy::unwrap_used)]
121#![deny(clippy::expect_used)]
122#![deny(clippy::todo)]
123#![deny(clippy::unimplemented)]
124#![deny(clippy::print_stdout)]
125#![deny(clippy::print_stderr)]
126#![deny(clippy::dbg_macro)]
127#![deny(clippy::unreachable)]
128#![deny(clippy::undocumented_unsafe_blocks)]
129
130extern crate alloc;
131#[cfg(test)]
132extern crate std;
133
134mod error;
135mod grammar;
136mod kind;
137mod language;
138mod lexer;
139mod noml;
140mod parse;
141mod parser;
142mod rule;
143mod schematic;
144mod set;
145mod tree;
146
147pub use error::Error;
148pub use kind::Kind;
149pub use language::{Capability, Language};
150pub use parse::Parse;
151
152// Re-exported whole: trees are `syntax_lang` trees, problems are `diag_lang`
153// diagnostics, and capabilities are `pass_lang` passes.
154pub use diag_lang;
155pub use pass_lang;
156pub use syntax_lang;
157
158/// Compiles and runs the `rust` code blocks in `README.md` and `docs/API.md` as
159/// part of `cargo test`, so the published examples cannot drift from the API.
160///
161/// Present only while collecting doctests (`#[cfg(doctest)]`); it is not part of
162/// the public surface and does not appear in the built library or its docs.
163#[cfg(doctest)]
164#[doc = include_str!("../README.md")]
165#[doc = include_str!("../docs/API.md")]
166pub struct MarkdownDocTests;