1use crate::env::ty_from_canon;
23use crate::types::{EffectSet, Ty};
24use indexmap::IndexMap;
25use std::collections::HashMap;
26use std::sync::OnceLock;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum IndexConvention {
33 None,
35 Byte,
37 Codepoint,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum BuiltinKind {
44 Pure,
47 VmNative,
50 Effect,
52}
53
54#[derive(Debug, Clone, Copy)]
55pub struct BuiltinDef {
56 pub module: &'static str,
57 pub name: &'static str,
58 pub ty: &'static str,
60 pub kind: BuiltinKind,
61 pub index: IndexConvention,
62 pub doc: &'static str,
64 pub complexity: Option<&'static str>,
66}
67
68const fn pure(
69 module: &'static str,
70 name: &'static str,
71 ty: &'static str,
72 index: IndexConvention,
73 doc: &'static str,
74 complexity: Option<&'static str>,
75) -> BuiltinDef {
76 BuiltinDef { module, name, ty, kind: BuiltinKind::Pure, index, doc, complexity }
77}
78
79const fn native(
80 module: &'static str,
81 name: &'static str,
82 ty: &'static str,
83 doc: &'static str,
84 complexity: Option<&'static str>,
85) -> BuiltinDef {
86 BuiltinDef {
87 module,
88 name,
89 ty,
90 kind: BuiltinKind::VmNative,
91 index: IndexConvention::None,
92 doc,
93 complexity,
94 }
95}
96
97use IndexConvention::{Byte, Codepoint, None as NoIndex};
98
99pub const BUILTINS: &[BuiltinDef] = &[
102 pure("str", "is_empty", "(Str) -> Bool", NoIndex,
104 "`true` when the string has no bytes.", None),
105 pure("str", "to_int", "(Str) -> Option[Int]", NoIndex,
106 "Parse a decimal integer (optional leading `-`); `None` on any other input.", None),
107 pure("str", "to_float", "(Str) -> Option[Float]", NoIndex,
108 "Parse a float literal; `None` on any other input.", None),
109 pure("str", "concat", "(Str, Str) -> Str", NoIndex,
110 "Concatenate two strings; `a + b` is the same operation.", None),
111 pure("str", "len", "(Str) -> Int", Byte,
112 "Length in UTF-8 bytes, not characters: `str.len(\"é\")` is 2.", Some("O(1)")),
113 pure("str", "char_at", "(Str, Int) -> Str", Byte,
114 "The byte at a byte index as a one-character string for ASCII bytes; `\"\"` for a non-ASCII byte or an index out of range. Never fails.",
115 Some("O(1)")),
116 pure("str", "split", "(Str, Str) -> List[Str]", NoIndex,
117 "Split on a separator; an empty separator splits into characters.", None),
118 pure("str", "join", "(List[Str], Str) -> Str", NoIndex,
119 "Join the elements with a separator; fails if an element is not a `Str`.", None),
120 pure("str", "starts_with", "(Str, Str) -> Bool", NoIndex,
121 "`true` when the first string begins with the second.", None),
122 pure("str", "ends_with", "(Str, Str) -> Bool", NoIndex,
123 "`true` when the first string ends with the second.", None),
124 pure("str", "contains", "(Str, Str) -> Bool", NoIndex,
125 "`true` when the second string occurs anywhere in the first.", None),
126 pure("str", "cmp", "(Str, Str) -> Int", NoIndex,
127 "Three-way byte-order comparison: `-1`, `0` or `1`. Use the comparison operators for a `Bool` (#440).", None),
128 pure("str", "replace", "(Str, Str, Str) -> Str", NoIndex,
129 "Replace every non-overlapping occurrence of the second string with the third.", None),
130 pure("str", "trim", "(Str) -> Str", NoIndex,
131 "Strip leading and trailing Unicode whitespace.", None),
132 pure("str", "to_upper", "(Str) -> Str", NoIndex,
133 "Unicode uppercase.", None),
134 pure("str", "to_lower", "(Str) -> Str", NoIndex,
135 "Unicode lowercase.", None),
136 pure("str", "strip_prefix", "(Str, Str) -> Option[Str]", NoIndex,
137 "The remainder after a prefix, or `None` when the prefix is absent.", None),
138 pure("str", "strip_suffix", "(Str, Str) -> Option[Str]", NoIndex,
139 "The remainder before a suffix, or `None` when the suffix is absent.", None),
140 pure("str", "slice", "(Str, Int, Int) -> Str", Codepoint,
141 "Half-open range of codepoint indices `[lo, hi)`; indices clamp to the codepoint count and a reversed range fails (#620).",
142 Some("O(distance from the previous slice or find on the same string) (#764)")),
143 pure("str", "is_ascii", "(Str) -> Bool", NoIndex,
144 "`true` when every byte is below 128; one native pass (#768).", Some("O(len)")),
145 pure("str", "find", "(Str, Str, Int) -> Option[Int]", Codepoint,
146 "Codepoint index of the first occurrence of the needle at or after `from`; `from` clamps to the string and an empty needle matches at `from` (#764).",
147 Some("O(distance scanned)")),
148 pure("str", "find_any", "(Str, Str, Int) -> Option[Int]", Codepoint,
149 "Codepoint index of the first character at or after `from` that occurs in the set string (#764).",
150 Some("O(distance scanned)")),
151 native("list", "map", "(List[a], (a) -> [| E] b) -> [| E] List[b]",
153 "Apply the closure to every element; the closure's effects flow to the call.", None),
154 native("list", "par_map", "(List[a], (a) -> [| E] b) -> [| E] List[b]",
155 "`map` on a worker pool capped by `LEX_PAR_MAX_CONCURRENCY` (#305).", None),
156 native("list", "sort_by", "(List[a], (a) -> [| E] b) -> [| E] List[a]",
157 "Stable sort by the key the closure derives; `Int`, `Float` and `Str` keys order natively, other shapes keep their input order (#338).",
158 Some("O(n log n)")),
159 native("list", "filter", "(List[a], (a) -> [| E] Bool) -> [| E] List[a]",
160 "Keep the elements the closure accepts.", None),
161 native("list", "fold", "(List[a], b, (b, a) -> [| E] b) -> [| E] b",
162 "Left fold from the initial accumulator.", None),
163 pure("list", "len", "(List[a]) -> Int", NoIndex,
164 "Number of elements.", Some("O(1)")),
165 pure("list", "is_empty", "(List[a]) -> Bool", NoIndex,
166 "`true` when the list has no elements.", Some("O(1)")),
167 pure("list", "range", "(Int, Int) -> List[Int]", NoIndex,
168 "Integers from `lo` up to but excluding `hi`; empty when `hi <= lo`.", None),
169 pure("list", "head", "(List[a]) -> Option[a]", NoIndex,
170 "The first element, or `None` for an empty list.", Some("O(1)")),
171 pure("list", "tail", "(List[a]) -> List[a]", NoIndex,
172 "Every element but the first; empty for an empty list.", Some("O(1) when the list is uniquely owned, otherwise O(n) (#774)")),
173 pure("list", "concat", "(List[a], List[a]) -> List[a]", NoIndex,
174 "The first list followed by the second.", None),
175 pure("list", "reverse", "(List[a]) -> List[a]", NoIndex,
176 "Elements in reverse order.", None),
177 pure("list", "cons", "(a, List[a]) -> List[a]", NoIndex,
178 "Prepend one element (#334).", Some("amortised O(1)")),
179 pure("list", "enumerate", "(List[a]) -> List[(Int, a)]", NoIndex,
180 "Pair every element with its zero-based index.", None),
181];
182
183pub fn declared_modules() -> Vec<&'static str> {
186 let mut out: Vec<&'static str> = Vec::new();
187 for d in BUILTINS {
188 if !out.contains(&d.module) {
189 out.push(d.module);
190 }
191 }
192 out
193}
194
195pub fn is_declared_module(module: &str) -> bool {
196 BUILTINS.iter().any(|d| d.module == module)
197}
198
199pub fn defs_for(module: &str) -> Vec<&'static BuiltinDef> {
201 BUILTINS.iter().filter(|d| d.module == module).collect()
202}
203
204pub fn lookup(module: &str, name: &str) -> Option<&'static BuiltinDef> {
205 BUILTINS.iter().find(|d| d.module == module && d.name == name)
206}
207
208pub(crate) const EFF_VAR_BASE: u32 = 1000;
215
216pub fn parse_signature(def: &BuiltinDef, eff_var: u32) -> Result<Ty, String> {
219 let src = format!("type Sig = {}\n", def.ty);
220 let program = lex_syntax::parse_source(&src)
221 .map_err(|e| format!("{}.{}: cannot parse `{}`: {e:?}", def.module, def.name, def.ty))?;
222 let stages = lex_ast::canonicalize_program(&program);
223 let te = stages
224 .iter()
225 .find_map(|s| match s {
226 lex_ast::Stage::TypeDecl(td) => Some(&td.definition),
227 _ => None,
228 })
229 .ok_or_else(|| format!("{}.{}: no type in `{}`", def.module, def.name, def.ty))?;
230 if !matches!(te, lex_ast::TypeExpr::Function { .. }) {
231 return Err(format!("{}.{}: `{}` is not a function type", def.module, def.name, def.ty));
232 }
233 let mut params: Vec<String> = Vec::new();
234 collect_type_vars(te, &mut params);
235 let first_row = params.len();
236 collect_row_vars(te, &mut params);
237 let mut ty = ty_from_canon(te, ¶ms);
238 if params.len() > first_row + 1 {
239 return Err(format!(
240 "{}.{}: `{}` names more than one effect row variable",
241 def.module, def.name, def.ty
242 ));
243 }
244 if params.len() == first_row + 1 {
245 remap_eff_var(&mut ty, first_row as u32, eff_var);
246 }
247 Ok(ty)
248}
249
250fn is_type_var(name: &str) -> bool {
253 let mut cs = name.chars();
254 matches!((cs.next(), cs.next()), (Some(c), None) if c.is_ascii_lowercase())
255}
256
257fn collect_type_vars(te: &lex_ast::TypeExpr, out: &mut Vec<String>) {
258 use lex_ast::TypeExpr as T;
259 match te {
260 T::Named { name, args } => {
261 if args.is_empty() && is_type_var(name) && !out.contains(name) {
262 out.push(name.clone());
263 }
264 for a in args {
265 collect_type_vars(a, out);
266 }
267 }
268 T::Function { params, ret, .. } => {
269 for p in params {
270 collect_type_vars(p, out);
271 }
272 collect_type_vars(ret, out);
273 }
274 T::Tuple { items } => {
275 for i in items {
276 collect_type_vars(i, out);
277 }
278 }
279 T::Record { fields } | T::RecordWithSpreads { fields, .. } => {
280 for f in fields {
281 collect_type_vars(&f.ty, out);
282 }
283 }
284 T::Union { variants } => {
285 for v in variants {
286 if let Some(p) = &v.payload {
287 collect_type_vars(p, out);
288 }
289 }
290 }
291 T::Refined { base, .. } => collect_type_vars(base, out),
292 }
293}
294
295fn collect_row_vars(te: &lex_ast::TypeExpr, out: &mut Vec<String>) {
296 use lex_ast::TypeExpr as T;
297 match te {
298 T::Function { params, effect_row_var, ret, .. } => {
299 if let Some(v) = effect_row_var {
300 if !out.contains(v) {
301 out.push(v.clone());
302 }
303 }
304 for p in params {
305 collect_row_vars(p, out);
306 }
307 collect_row_vars(ret, out);
308 }
309 T::Named { args, .. } => {
310 for a in args {
311 collect_row_vars(a, out);
312 }
313 }
314 T::Tuple { items } => {
315 for i in items {
316 collect_row_vars(i, out);
317 }
318 }
319 T::Record { fields } | T::RecordWithSpreads { fields, .. } => {
320 for f in fields {
321 collect_row_vars(&f.ty, out);
322 }
323 }
324 T::Union { variants } => {
325 for v in variants {
326 if let Some(p) = &v.payload {
327 collect_row_vars(p, out);
328 }
329 }
330 }
331 T::Refined { base, .. } => collect_row_vars(base, out),
332 }
333}
334
335fn remap_eff_var(ty: &mut Ty, from: u32, to: u32) {
336 match ty {
337 Ty::Function { params, effects, ret } => {
338 if effects.var == Some(from) {
339 *effects = EffectSet { concrete: effects.concrete.clone(), var: Some(to) };
340 }
341 for p in params {
342 remap_eff_var(p, from, to);
343 }
344 remap_eff_var(ret, from, to);
345 }
346 Ty::List(inner) => remap_eff_var(inner, from, to),
347 Ty::Tuple(items) => {
348 for i in items {
349 remap_eff_var(i, from, to);
350 }
351 }
352 Ty::Record(fields) => {
353 for v in fields.values_mut() {
354 remap_eff_var(v, from, to);
355 }
356 }
357 Ty::Con(_, args) => {
358 for a in args {
359 remap_eff_var(a, from, to);
360 }
361 }
362 Ty::Var(_) | Ty::Prim(_) | Ty::Unit | Ty::Never => {}
363 }
364}
365
366pub fn module_record(module: &str) -> Option<Ty> {
374 static CACHE: OnceLock<HashMap<&'static str, Ty>> = OnceLock::new();
375 let cache = CACHE.get_or_init(|| {
376 let mut out = HashMap::new();
377 for m in declared_modules() {
378 let mut fields = IndexMap::new();
379 for (i, def) in defs_for(m).into_iter().enumerate() {
380 let ty = parse_signature(def, EFF_VAR_BASE + i as u32)
381 .unwrap_or_else(|e| panic!("stdlib_spec: {e}"));
382 fields.insert(def.name.to_string(), ty);
383 }
384 out.insert(m, Ty::Record(fields));
385 }
386 out
387 });
388 cache.get(module).cloned()
389}