Skip to main content

blue_lang_syntax/
forms.rs

1//! The surface forms, one row each: how a form is written, the tatara-lisp
2//! tree it lowers to, and one line on what it means.
3//!
4//! This is the table `blue reference` prints as the grammar card. A reference
5//! written by hand next to a parser drifts the first time the parser changes;
6//! here every row's `example` is parsed by the real parser in
7//! `every_form_lowers_as_its_row_says`, and the tree must be exactly
8//! `lowers_to`. So a row that stops being true is a red build, not a stale
9//! document.
10//!
11//! Operators live in [`crate::parse::INFIX`] and reserved words in
12//! [`crate::parse::SURFACE_KEYWORDS`] / [`crate::parse::BLOCK_KEYWORDS`],
13//! each with its own line; this table holds the forms that are neither.
14
15/// One surface form.
16#[derive(Clone, Copy, Debug)]
17pub struct Form {
18    /// The form as an author writes it. Parsed by the gate.
19    pub example: &'static str,
20    /// The tree `example` parses to, as `Sexp`'s `Display` prints it.
21    pub lowers_to: &'static str,
22    /// One line for a reader.
23    pub doc: &'static str,
24}
25
26/// Every surface form that is not an operator or a keyword.
27pub const FORMS: &[Form] = &[
28    Form {
29        example: "42",
30        lowers_to: "42",
31        doc: "An Int. Six or more digits are grouped by threes, 1_000_000; fewer are not (B0033).",
32    },
33    Form {
34        example: "3.5",
35        lowers_to: "3.5",
36        doc: "A Float.",
37    },
38    Form {
39        example: "\"text\"",
40        lowers_to: "\"text\"",
41        doc: "A string. Escapes: \\n, \\t, \\\", \\\\, \\u{1b}; a character a reader cannot see is always escaped (B0033).",
42    },
43    Form {
44        example: "\"n = #{n}!\"",
45        lowers_to: "(concat (concat \"n = \" n) \"!\")",
46        doc: "Interpolation: any expression inside #{…}, rendered with to_s. A literal that must contain #{ is built with concat(\"#\", \"{\").",
47    },
48    Form {
49        example: ":done",
50        lowers_to: ":done",
51        doc: "A keyword: a name as a value. It may not end in ? or !.",
52    },
53    Form {
54        example: "[1, \"a\"]",
55        lowers_to: "(list 1 \"a\")",
56        doc: "A list. Indexing is nth(i, xs); blue has no xs[i].",
57    },
58    Form {
59        example: "%w[a b]",
60        lowers_to: "(list \"a\" \"b\")",
61        doc: "A list of strings, one per word, separated by spaces. The formatter writes any literal list of two or more such strings this way. Only [ ] delimits it, and a word holds no , \" ' # \\ [ ].",
62    },
63    Form {
64        example: "%i[a b]",
65        lowers_to: "(list :a :b)",
66        doc: "A list of keywords, one per name: [:a, :b], written this way from two names on.",
67    },
68    Form {
69        example: "{a: 1, \"k\" => 2}",
70        lowers_to: "(hash-map :a 1 \"k\" 2)",
71        doc: "A map. `a:` is the keyword key :a; `=>` takes any key. Read with get(m, k), extend with assoc(m, k, v).",
72    },
73    Form {
74        example: "{a:, b:}",
75        lowers_to: "(hash-map :a a :b b)",
76        doc: "A map whose values are the variables of the same names: `{a:}` is `{a: a}`. Mix it with `{a:, n: 1}`.",
77    },
78    Form {
79        example: "{**a, b: 1}",
80        lowers_to: "(blue-merge (blue-splice a) (hash-map :b 1))",
81        doc: "A map that inherits every key of a map, `**a`, and lets a later entry override it. nil spreads as an empty map; anything else that is not a map is a type error. The map sibling of the splat `*xs`.",
82    },
83    Form {
84        example: "f(x, **a, b: 1)",
85        lowers_to: "(f x (blue-kwargs (blue-splice a) (hash-map :b 1)))",
86        doc: "A call whose last argument is the map built from `**` spreads and entries, `f(x, {**a, b: 1})` without the braces. Entries after a `**` are the call's last argument, so it cannot also carry a splat `*xs`.",
87    },
88    Form {
89        example: "x = 5",
90        lowers_to: "(define x 5)",
91        doc: "A binding, only as a statement. There is no `let`; a binding lasts to the end of its body.",
92    },
93    Form {
94        example: "f(x, y)",
95        lowers_to: "(f x y)",
96        doc: "A call. This is the one form the formatter writes, except for a function that takes no parameters: that is called by its bare name, now_ms, never now_ms() (B0023), and passed as a value as fn() now_ms end.",
97    },
98    Form {
99        example: "f(a, *xs)",
100        lowers_to: "(apply f a xs)",
101        doc: "A splat: the list's elements are the call's last arguments. The one spelling of apply(f, a, xs) with f a name (B0042).",
102    },
103    Form {
104        example: "(a, *rest) -> rest",
105        lowers_to: "(lambda (a &rest rest) rest)",
106        doc: "A rest parameter: `*name` last in a def, fn or arrow parameter list collects the remaining arguments into a list, the signature-side sibling of the call-side splat. A call passes every fixed parameter and any number more.",
107    },
108    Form {
109        example: "def f(a, b?) b end",
110        lowers_to: "(define (f a &optional b) b)",
111        doc: "An optional parameter: `name?` after the required ones in a def is nil when the call leaves it out. A call passes every required parameter and up to the optional ones; more raises, naming the range. A required or rest parameter cannot follow an optional one.",
112    },
113    Form {
114        example: "cap.status",
115        lowers_to: "(blue-field cap :status)",
116        doc: "A field read: a map's keyword or string key, a keyed list such as exec_capture's result or a parsed JSON object, or an error's kind, message, data, cause or trace. A missing field raises naming the fields there are; get(m, :k) reads one that may be absent.",
117    },
118    Form {
119        example: "x == nil",
120        lowers_to: "(blue-field x :nil?)",
121        doc: "A nil test: x.nil? is true of nil and of nothing else. Read, and written x.nil?, from nil?(x), x == nil and nil == x (B0047).",
122    },
123    Form {
124        example: "x.f(y)",
125        lowers_to: "(f x y)",
126        doc: "A method-style call: the receiver becomes the first argument. Read, and written f(x, y) (B0028).",
127    },
128    Form {
129        example: "xs |> f(a)",
130        lowers_to: "(f xs a)",
131        doc: "A pipe: the value becomes the FIRST argument. Read, and written f(xs, a) (B0028). map(f, xs) is function-first: xs |> map(f) is map(xs, f), an error.",
132    },
133    Form {
134        example: "not x",
135        lowers_to: "(not x)",
136        doc: "Negation; `!x` is read and written `not x` (B0025).",
137    },
138    Form {
139        example: "-x",
140        lowers_to: "(- 0 x)",
141        doc: "Unary minus. The formatter writes 0 - x as -x.",
142    },
143    Form {
144        example: "use(\"retsu\")",
145        lowers_to: "(use \"retsu\")",
146        doc: "Import a bidama by name, found on BLUE_PATH. Every definition it makes joins one flat namespace.",
147    },
148    Form {
149        example: "raise(:parse, \"bad row\")",
150        lowers_to: "(raise :parse \"bad row\")",
151        doc: "Raise an error; raise(:k, \"m\", {data: d, cause: e}) attaches data and the error it wraps, raise(e) raises e again. throw(error(…)) is read as this (B0037).",
152    },
153    Form {
154        example: "risky(x) rescue fallback",
155        lowers_to: "(try (risky x) (catch (_) fallback))",
156        doc: "The value of risky(x), or fallback when it raises. Binds loosest of all, below |>.",
157    },
158    Form {
159        example: "begin\n  risky(x)\nrescue :timeout as e\n  wait(e)\nend",
160        lowers_to: "(try (risky x) (catch (rescued-error) (cond ((blue-rescues? rescued-error :timeout) (let ((e rescued-error)) (wait e))) (else (blue-reraise rescued-error)))))",
161        doc: "Catch by kind, binding the error; `rescue e` (last) catches every error, an unmatched kind raises again, and `ensure` runs on both paths. A def body takes the same clauses without begin.",
162    },
163    Form {
164        example: "test \"isolated\", {satisfies: [:nist_ac_6], target: parse}\n  assert parse(1)\nend",
165        lowers_to: "(deftest \"isolated\" (blue-test-meta (hash-map :satisfies (list :nist_ac_6) :target parse)) (blue-assert '(parse 1) (parse 1)))",
166        doc: "A test that declares the controls it verifies, each a catalog keyword (:nist_ac_6 is NIST_SP_800_53R5_AC_6, B0069), and the definition it targets. `blue test --facts` hands both, with each one's def_hash, to tameshi. Written satisfies then target, the controls sorted (B0068).",
167    },
168    Form {
169        example: "@doc \"\"\"\ninc adds one to a number.\n\n    inc(1) #=> 2\n\"\"\"\ndef inc(n)\n  n + 1\nend",
170        lowers_to: "(blue-doc \"inc adds one to a number.\\n\\n    inc(1) #=> 2\" (define (inc n) (+ n 1)))",
171        doc: "Documents the definition below it: one sentence of intent starting with its name, then an indented example whose `expr #=> value` lines `blue test` runs as tests. A heredoc: no escapes, no interpolation. Everything else about a definition is derived (B0056–B0060).",
172    },
173    Form {
174        example: "@moduledoc \"\"\"\nkazu holds the number verbs.\n\n    kazu::clamp(9, 1, 5) #=> 5\n\"\"\"",
175        lowers_to: "(blue-moduledoc \"kazu holds the number verbs.\\n\\n    kazu::clamp(9, 1, 5) #=> 5\")",
176        doc: "Documents a whole file, a bidama: its purpose and one whole example. The first form of the file.",
177    },
178];
179
180#[cfg(test)]
181mod tests {
182    use super::FORMS;
183    use crate::parse::{
184        keyword_doc, parse_program_documented, BLOCK_KEYWORDS, INFIX, SURFACE_KEYWORDS,
185    };
186
187    fn lowered(src: &str) -> String {
188        parse_program_documented(src)
189            .unwrap_or_else(|e| panic!("`{src}` does not parse: {e}"))
190            .iter()
191            .map(|f| f.to_sexp().to_string())
192            .collect::<Vec<_>>()
193            .join(" ")
194    }
195
196    /// Red run, 2026-09-29: changing the pipe row's `lowers_to` to
197    /// `(f a xs)` failed with "`xs |> f(a)` lowers to (f xs a), and its row
198    /// says (f a xs)".
199    #[test]
200    fn every_form_lowers_as_its_row_says() {
201        for form in FORMS {
202            let got = lowered(form.example);
203            assert_eq!(
204                got, form.lowers_to,
205                "`{}` lowers to {got}, and its row says {}",
206                form.example, form.lowers_to
207            );
208        }
209    }
210
211    /// Red run, 2026-09-29: emptying the `"%"` row's doc failed with
212    /// "the operator `%` has no doc"; deleting the `"case"` arm of
213    /// `keyword_doc` failed with "the keyword `case` has no doc".
214    #[test]
215    fn every_operator_and_keyword_is_described() {
216        for op in INFIX {
217            assert!(
218                !op.doc.trim().is_empty(),
219                "the operator `{}` has no doc",
220                op.op
221            );
222        }
223        for word in SURFACE_KEYWORDS.iter().chain(BLOCK_KEYWORDS) {
224            assert!(
225                !keyword_doc(word).trim().is_empty(),
226                "the keyword `{word}` has no doc"
227            );
228        }
229        for form in FORMS {
230            assert!(
231                !form.doc.trim().is_empty(),
232                "the form `{}` has no doc",
233                form.example
234            );
235        }
236    }
237}