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