1#[derive(Clone, Copy, Debug)]
17pub struct Form {
18 pub example: &'static str,
20 pub lowers_to: &'static str,
22 pub doc: &'static str,
24}
25
26pub 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: "{a: 1, \"k\" => 2}",
60 lowers_to: "(hash-map :a 1 \"k\" 2)",
61 doc: "A map. `a:` is the keyword key :a; `=>` takes any key. Read with get(m, k), extend with assoc(m, k, v).",
62 },
63 Form {
64 example: "x = 5",
65 lowers_to: "(define x 5)",
66 doc: "A binding, only as a statement. There is no `let`; a binding lasts to the end of its body.",
67 },
68 Form {
69 example: "f(x, y)",
70 lowers_to: "(f x y)",
71 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.",
72 },
73 Form {
74 example: "f(a, *xs)",
75 lowers_to: "(apply f a xs)",
76 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).",
77 },
78 Form {
79 example: "cap.status",
80 lowers_to: "(blue-field cap :status)",
81 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.",
82 },
83 Form {
84 example: "x.f(y)",
85 lowers_to: "(f x y)",
86 doc: "A method-style call: the receiver becomes the first argument. Read, and written f(x, y) (B0028).",
87 },
88 Form {
89 example: "xs |> f(a)",
90 lowers_to: "(f xs a)",
91 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.",
92 },
93 Form {
94 example: "not x",
95 lowers_to: "(not x)",
96 doc: "Negation; `!x` is read and written `not x` (B0025).",
97 },
98 Form {
99 example: "-x",
100 lowers_to: "(- 0 x)",
101 doc: "Unary minus. The formatter writes 0 - x as -x.",
102 },
103 Form {
104 example: "use(\"retsu\")",
105 lowers_to: "(use \"retsu\")",
106 doc: "Import a bidama by name, found on BLUE_PATH. Every definition it makes joins one flat namespace.",
107 },
108 Form {
109 example: "raise(:parse, \"bad row\")",
110 lowers_to: "(raise :parse \"bad row\")",
111 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).",
112 },
113 Form {
114 example: "risky(x) rescue fallback",
115 lowers_to: "(try (risky x) (catch (_) fallback))",
116 doc: "The value of risky(x), or fallback when it raises. Binds loosest of all, below |>.",
117 },
118 Form {
119 example: "begin\n risky(x)\nrescue :timeout as e\n wait(e)\nend",
120 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)))))",
121 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.",
122 },
123];
124
125#[cfg(test)]
126mod tests {
127 use super::FORMS;
128 use crate::parse::{keyword_doc, parse_program, BLOCK_KEYWORDS, INFIX, SURFACE_KEYWORDS};
129
130 fn lowered(src: &str) -> String {
131 parse_program(src)
132 .unwrap_or_else(|e| panic!("`{src}` does not parse: {e}"))
133 .iter()
134 .map(ToString::to_string)
135 .collect::<Vec<_>>()
136 .join(" ")
137 }
138
139 #[test]
143 fn every_form_lowers_as_its_row_says() {
144 for form in FORMS {
145 let got = lowered(form.example);
146 assert_eq!(
147 got, form.lowers_to,
148 "`{}` lowers to {got}, and its row says {}",
149 form.example, form.lowers_to
150 );
151 }
152 }
153
154 #[test]
158 fn every_operator_and_keyword_is_described() {
159 for op in INFIX {
160 assert!(
161 !op.doc.trim().is_empty(),
162 "the operator `{}` has no doc",
163 op.op
164 );
165 }
166 for word in SURFACE_KEYWORDS.iter().chain(BLOCK_KEYWORDS) {
167 assert!(
168 !keyword_doc(word).trim().is_empty(),
169 "the keyword `{word}` has no doc"
170 );
171 }
172 for form in FORMS {
173 assert!(
174 !form.doc.trim().is_empty(),
175 "the form `{}` has no doc",
176 form.example
177 );
178 }
179 }
180}