cove_runtime/builtins.rs
1//! Builtin methods, associated functions, and constructors.
2//!
3//! Everything here is dispatched dynamically on a receiver value, a type name,
4//! or a constructor name. The MVP has no method table derived from types yet,
5//! so an arity or type mismatch is an ordinary [`RuntimeError`] that names the
6//! method it came from.
7//!
8//! What each builtin *is* — its parameters, its result, and whether its
9//! receiver is `var self` — is [`cove_schema::builtins`], two tables below
10//! both this crate and the compiler: one for what is called on a receiver and
11//! one for the constructors and assertions, which are called on nothing. This
12//! module is the other half: the bodies, which have to be here because a body
13//! reaches into a [`Value`] and `cove-schema` has no values. Every question a
14//! name alone can answer is asked of the schema rather than answered twice —
15//! which names are namespaces, which methods mutate, which names construct,
16//! which assert, how many arguments each takes, and which receivers report a
17//! `length` — and `tests/builtin_schema.rs` drives every entry in both tables
18//! through a real interpreter, so a signature declared with no body behind it
19//! fails a test rather than a program.
20
21use std::collections::{BTreeMap, BTreeSet};
22use std::rc::Rc;
23
24use cove_diag::Span;
25use cove_schema::builtins::{FreeBuiltinKind, FreeBuiltinSchema, MAP_ENTRY};
26
27use crate::error::RuntimeError;
28use crate::shared::SharedCell;
29use crate::value::{InvalidKey, MapKey, RangeBounds, Repr, Value, VectorStorage};
30
31/// Type names a program may write as a namespace, such as `Vector.of`.
32///
33/// This is [`cove_schema::builtins::is_builtin_type`], re-exported so that
34/// the interpreter still asks one module about builtins.
35pub use cove_schema::builtins::is_builtin_type;
36
37/// The methods that take a `var self` receiver and therefore need a mutable
38/// place at the call site.
39///
40/// This is [`cove_schema::builtins::is_mutating_method`]: `push`, `set`,
41/// `pop`, `remove` and `freeze` declare `mutating` in the shared table, and
42/// nothing here restates them.
43pub use cove_schema::builtins::is_mutating_method;
44
45/// How the builtins call back into the evaluator.
46///
47/// Higher-order builtins such as `Result.mapError` invoke a Cove callback, so
48/// they need the interpreter that owns the call stack.
49pub trait Callable {
50 /// Allocates growable vector storage in the running task's heap.
51 ///
52 /// Every `Vector` a program can reach is created through this, so the
53 /// collector's table of objects is the complete set of values that can
54 /// form a cycle. A builtin that makes one asks its caller rather than
55 /// calling [`VectorStorage::new`] directly.
56 fn allocate_vector(&mut self, elements: Vec<Value>) -> Value;
57
58 /// Calls a closure value with already evaluated arguments.
59 ///
60 /// **The arguments are taken out of `args`, which is left empty.** The
61 /// vector belongs to the caller and comes back to it, capacity and all,
62 /// which is what lets a per-element callback be invoked without
63 /// allocating a vector per element: `walk_with` hands the same one
64 /// down for the whole walk. Issue #193 is the cost that made that worth
65 /// arranging — `map`, `filter`, `fold` and `sorted` built and dropped a
66 /// `Vec<Value>` for every element they visited, which is the same shape
67 /// the predecessor's own argument vectors had before #184 and on the
68 /// one path that scheme could not reach.
69 ///
70 /// A caller that fails partway is still handed back a vector it may
71 /// reuse: an implementation drains what it was given before it runs
72 /// anything, so `args` is empty whether the call answered or raised.
73 fn call_value(
74 &mut self,
75 callee: &Value,
76 args: &mut Vec<Value>,
77 span: Span,
78 ) -> Result<Value, RuntimeError>;
79
80 /// The number of parameters `callee` declares, when it is a closure.
81 fn arity(&self, callee: &Value) -> Option<usize>;
82
83 /// The independent copy `Snapshot` makes of one value.
84 ///
85 /// A hook on this trait, whose one implementor is `Interpreter`, because
86 /// a struct and an enum answer their own `impl Snapshot for Type`
87 /// through a declaration that only the interpreter reaches this way. The
88 /// linear-memory backend puts the same recursion in the lowering
89 /// instead, exactly because a builtin never calls back into Cove —
90 /// `docs/LINEAR_VM.md` says why. [`snapshot`] recurses through here so
91 /// that a `Vector` of structs reaches the interpreter's own answer for
92 /// each one.
93 fn snapshot(&mut self, value: &Value, span: Span) -> Result<Value, RuntimeError>;
94}
95
96/// The independent copy `Snapshot` makes of a value that no declared
97/// conformance answers for.
98///
99/// The Language Reference makes an independent copy an explicit `impl
100/// Snapshot for Type`, and this is everything that decision leaves over: a
101/// value with nothing mutable inside it returns itself, because a copy of it
102/// is not observable, and a `Vector` — the one thing a copy is observable of
103/// — allocates storage of its own and snapshots what it held.
104///
105/// An `Array`, a `Map` and a `Set` are cloned rather than walked, which is
106/// `Interpreter::snapshot`'s own answer and not a shortcut taken here: each
107/// is immutable, so an element that shares storage with something else went
108/// on sharing it before this was called and there is nothing for a copy to
109/// separate.
110///
111/// A struct, an enum and a `dyn` are not here. They are what the caller
112/// answers, through [`Callable::snapshot`].
113pub fn snapshot(
114 callable: &mut dyn Callable,
115 value: &Value,
116 span: Span,
117) -> Result<Value, RuntimeError> {
118 match value {
119 Value(Repr::Unit)
120 | Value(Repr::Bool(_))
121 | Value(Repr::Int(_))
122 | Value(Repr::Float(_))
123 | Value(Repr::Duration(_))
124 | Value(Repr::Str(_))
125 | Value(Repr::Array(_))
126 | Value(Repr::Map(_))
127 | Value(Repr::Set(_))
128 | Value(Repr::Range { .. }) => Ok(value.clone()),
129 Value(Repr::Vector(storage)) => {
130 check_live(storage, "snapshot", span)?;
131 let elements = storage.elements.borrow().clone();
132 let mut snapshotted = Vec::with_capacity(elements.len());
133 for item in &elements {
134 snapshotted.push(callable.snapshot(item, span)?);
135 }
136 Ok(callable.allocate_vector(snapshotted))
137 }
138 other => Err(no_snapshot_conformance(other, span)),
139 }
140}
141
142/// What a `...` argument that is neither an `Array` nor a `Vector` is
143/// refused with.
144///
145/// A spread passes an existing sequence where a variadic parameter's
146/// elements would go, so the two sequences are what it reads; `bind_params`
147/// reports anything else, and the linear-memory backend reports it from the
148/// instruction that does the appending. One wording, because it is one
149/// failure.
150pub fn spread_needs_a_sequence(span: Span) -> RuntimeError {
151 RuntimeError::new("`...` spreads an `Array` or a `Vector`").at(span)
152}
153
154/// What a value that implements no `Snapshot` conformance is refused with.
155///
156/// Only the interpreter reaches it: a struct or an enum whose type wrote
157/// none, met while walking a `Vector` whose element type is not known until
158/// the value is. The linear-memory backend has no runtime version of this
159/// question — a `Vector`'s element type is part of its layout, so whether it
160/// snapshots itself or calls a conformance is decided when the walk is
161/// lowered, not when it runs.
162pub fn no_snapshot_conformance(value: &Value, span: Span) -> RuntimeError {
163 RuntimeError::new(format!(
164 "`{}` does not implement `Snapshot`",
165 value.type_name()
166 ))
167 .at(span)
168 .with_rule(
169 "Closures, synchronized values, and Host resources do not implement `Snapshot` by default; a struct or enum conforms explicitly with `impl Snapshot for Type`.",
170 )
171}
172
173/// The builtins that are called on nothing: the constructors `Ok`, `Err`,
174/// `Some`, `Error`, and `Shared`, and the assertions `assert` and
175/// `assertEqual`.
176///
177/// This is [`cove_schema::builtins::free_builtin`], re-exported so that the
178/// interpreter still asks one module about builtins. Which of the two kinds
179/// an entry is decides which path a call is dispatched through, and how many
180/// arguments it declares is what that call is held to.
181pub use cove_schema::builtins::free_builtin;
182
183/// `assert(condition: Bool) -> Result<Unit, Error>` and
184/// `assertEqual(actual: T, expected: T) -> Result<Unit, Error>`.
185///
186/// `sources` holds the source text of each argument expression, in order.
187/// That text is the whole reason these are builtins rather than a library:
188/// a failure message says which condition failed in the words the test was
189/// written in, and only the compiler has them.
190///
191/// A failing assertion is an expected failure, so it is an `Err` rather than
192/// a panic — panics stay reserved for broken invariants. `assertEqual`
193/// reports both values, since knowing only that they differ rarely explains
194/// why.
195///
196/// How many arguments each takes and what each one is called are the shared
197/// table's, so the arity this enforces is the arity `cove check` reported on.
198pub fn call_assertion(
199 name: &str,
200 args: &mut Vec<Value>,
201 sources: &[&str],
202 span: Span,
203) -> Result<Value, RuntimeError> {
204 let Some(schema) =
205 free_builtin(name).filter(|schema| schema.kind == FreeBuiltinKind::Assertion)
206 else {
207 return Err(RuntimeError::new(format!("unknown assertion `{name}`")).at(span));
208 };
209 let args = expect_args(name, args, schema.arity(), span)?;
210 match name {
211 "assert" => {
212 let Value(Repr::Bool(holds)) = &args[0] else {
213 return Err(declared_type_error(schema, 0, &args[0], span));
214 };
215 if *holds {
216 return Ok(Value::ok(Value(Repr::Unit)));
217 }
218 Ok(assertion_failure(format!(
219 "assertion failed: `{}`",
220 source_of(sources, 0)
221 )))
222 }
223 "assertEqual" => {
224 // `assertEqual` compares the way `==` does, so it refuses the
225 // same comparison `==` refuses. The shared table says as much by
226 // naming one type parameter twice.
227 if args[0].type_name() != args[1].type_name() {
228 return Err(RuntimeError::new(format!(
229 "`assertEqual` cannot compare `{}` with `{}`",
230 args[0].type_name(),
231 args[1].type_name()
232 ))
233 .at(span)
234 .with_rule("`==` means value equality between values of the same type."));
235 }
236 if args[0].eq_value(&args[1]) {
237 return Ok(Value::ok(Value(Repr::Unit)));
238 }
239 Ok(assertion_failure(format!(
240 "assertion failed: `{}` is `{}`, expected `{}`",
241 source_of(sources, 0),
242 args[0],
243 args[1]
244 )))
245 }
246 // The table admitted the name, so this is a table entry with no body
247 // behind it, which `tests/builtin_schema.rs` is what catches.
248 _ => Err(RuntimeError::new(format!("unknown assertion `{name}`")).at(span)),
249 }
250}
251
252/// A free builtin was given an argument its declared parameter does not
253/// admit.
254///
255/// The parameter's name and type are read out of the shared table, so
256/// `Error("boom")` and `assert(1)` are refused in the words the table
257/// declares them in.
258fn declared_type_error(
259 schema: &FreeBuiltinSchema,
260 index: usize,
261 found: &Value,
262 span: Span,
263) -> RuntimeError {
264 let param = &schema.params[index];
265 type_error(schema.name, param.name, ¶m.ty.to_string(), found, span)
266}
267
268/// The `Err` a failed assertion produces.
269fn assertion_failure(message: String) -> Value {
270 Value::err(Value::error(message))
271}
272
273/// The source text of argument `index`, or a placeholder when the caller
274/// could not supply it.
275fn source_of<'a>(sources: &[&'a str], index: usize) -> &'a str {
276 sources.get(index).copied().unwrap_or("?")
277}
278
279/// `Ok(v)`, `Err(e)`, `Some(v)`, `Error("message")`, `Shared(value)`.
280///
281/// Which names these are and how many arguments each carries come from the
282/// shared table; what each one builds is here, because building one needs a
283/// [`Value`].
284pub fn call_constructor(
285 name: &str,
286 args: &mut Vec<Value>,
287 span: Span,
288) -> Result<Value, RuntimeError> {
289 let Some(schema) =
290 free_builtin(name).filter(|schema| schema.kind == FreeBuiltinKind::Constructor)
291 else {
292 return Err(RuntimeError::new(format!("unknown constructor `{name}`")).at(span));
293 };
294 let args = expect_args(name, args, schema.arity(), span)?;
295 let value = args.remove(0);
296 Ok(match name {
297 "Ok" => Value::ok(value),
298 "Err" => Value::err(value),
299 "Some" => Value::some(value),
300 // `Shared` is the one constructor that can refuse its payload: what
301 // it wraps must be task-safe, since a `Shared` is reachable from
302 // every task it was given to.
303 "Shared" => Value(Repr::Shared(SharedCell::wrap(&value, span)?)),
304 "Error" => match value {
305 Value(Repr::Str(message)) => Value::error(message.to_string()),
306 other => {
307 return Err(declared_type_error(schema, 0, &other, span));
308 }
309 },
310 // As in `call_assertion`: a name the table declares and nothing here
311 // builds is what `tests/builtin_schema.rs` refuses to let happen.
312 _ => return Err(RuntimeError::new(format!("unknown constructor `{name}`")).at(span)),
313 })
314}
315
316/// `Vector.of(...)` and `Int.parse(...)`.
317pub fn call_associated(
318 host: &mut dyn Callable,
319 type_name: &str,
320 name: &str,
321 args: &mut Vec<Value>,
322 span: Span,
323) -> Result<Value, RuntimeError> {
324 match (type_name, name) {
325 ("Vector", "of") => Ok(host.allocate_vector(std::mem::take(args))),
326 // `Map.of` takes the `MapEntry` values `MapEntry(key:, value:)`
327 // builds. A literal with two identical keys is a mistake, not an
328 // intent, so a duplicate key is rejected rather than resolved by
329 // silently keeping the first or last entry.
330 ("Map", "of") => {
331 let mut map: BTreeMap<MapKey, Value> = BTreeMap::new();
332 for arg in args.drain(..) {
333 let Value(Repr::Struct(entry)) = &arg else {
334 return Err(expects_map_entry(&arg, span));
335 };
336 if &*entry.type_name != MAP_ENTRY.name {
337 return Err(expects_map_entry(&arg, span));
338 }
339 let key_value = entry.get("key").expect("MapEntry always has a `key` field");
340 let key = to_map_key("Map.of", "map key", key_value, span)?;
341 if map.contains_key(&key) {
342 return Err(duplicate_key_error("Map.of", "key", &key, span));
343 }
344 let value = entry
345 .get("value")
346 .expect("MapEntry always has a `value` field")
347 .clone();
348 map.insert(key, value);
349 }
350 Ok(Value(Repr::Map(Rc::new(map))))
351 }
352 // `Set.of` rejects a duplicate element for the same reason `Map.of`
353 // rejects a duplicate key.
354 ("Set", "of") => {
355 let mut set: BTreeSet<MapKey> = BTreeSet::new();
356 for item in args.drain(..) {
357 let key = to_map_key("Set.of", "set element", &item, span)?;
358 if !set.insert(key.clone()) {
359 return Err(duplicate_key_error("Set.of", "element", &key, span));
360 }
361 }
362 Ok(Value(Repr::Set(Rc::new(set))))
363 }
364 // `Duration.nanos(count)`: the one primitive builder left.
365 // `micros` through `hours` are `std.duration.ofMicros` and its four
366 // neighbours now — see `cove_schema::builtins::standard_associated_binding`
367 // — and this arm no longer names them, so there is no factor to
368 // multiply by and nothing here can overflow: a `Duration` is signed
369 // nanoseconds and every `Int` is already a valid count of them.
370 ("Duration", "nanos") => {
371 let args = expect_args("Duration.nanos", args, 1, span)?;
372 let Value(Repr::Int(count)) = &args[0] else {
373 return Err(type_error("Duration.nanos", "count", "Int", &args[0], span));
374 };
375 Ok(Value(Repr::Duration(*count)))
376 }
377 ("Int", "parse") => {
378 let args = expect_args("Int.parse", args, 1, span)?;
379 let Value(Repr::Str(text)) = &args[0] else {
380 return Err(type_error("Int.parse", "text", "String", &args[0], span));
381 };
382 Ok(match text.parse::<i64>() {
383 Ok(value) => Value::ok(Value(Repr::Int(value))),
384 Err(_) => Value::err(Value::error(format!("`{text}` is not an Int"))),
385 })
386 }
387 // `Int.parse` in a base other than ten. A `radix` outside `2..=36`
388 // names no notation, so it stops the run the way an empty
389 // `String.split` separator does; text that is not a number in a
390 // radix that does exist is the data's failure and answers `Err`,
391 // which is the same line `Int.parse` draws. Rust's
392 // `i64::from_str_radix` reads a leading `+` or `-` and no digit
393 // separators, exactly as `parse::<i64>` above does.
394 ("Int", "parseRadix") => {
395 let args = expect_args("Int.parseRadix", args, 2, span)?;
396 let Value(Repr::Str(text)) = &args[0] else {
397 return Err(type_error(
398 "Int.parseRadix",
399 "text",
400 "String",
401 &args[0],
402 span,
403 ));
404 };
405 let Value(Repr::Int(radix)) = &args[1] else {
406 return Err(type_error("Int.parseRadix", "radix", "Int", &args[1], span));
407 };
408 let Some(radix) = (2..=36).contains(radix).then_some(*radix as u32) else {
409 return Err(radix_error(*radix, span));
410 };
411 Ok(match i64::from_str_radix(text, radix) {
412 Ok(value) => Value::ok(Value(Repr::Int(value))),
413 Err(_) => Value::err(Value::error(format!(
414 "`{text}` is not an Int in radix {radix}"
415 ))),
416 })
417 }
418 // The one-character `String` a Unicode code point names. A character
419 // in Cove is a `String` of length 1 — `chars()` answers an array of
420 // them — so this is that decomposition run backwards, and there is
421 // no `Character` type for it to answer instead.
422 ("String", "fromCodePoint") => {
423 let args = expect_args("String.fromCodePoint", args, 1, span)?;
424 let Value(Repr::Int(code_point)) = &args[0] else {
425 return Err(type_error(
426 "String.fromCodePoint",
427 "codePoint",
428 "Int",
429 &args[0],
430 span,
431 ));
432 };
433 Ok(from_code_point(*code_point))
434 }
435 // Mirrors `Int.parse` exactly in shape. Rust's `f64::from_str`
436 // accepts `inf`, `-inf`, and `NaN`, which is why this does too, and
437 // it rejects the `_` digit separators a `Float` literal may be
438 // written with — the same thing `Int.parse` above already does,
439 // not a new choice made here.
440 ("Float", "parse") => {
441 let args = expect_args("Float.parse", args, 1, span)?;
442 let Value(Repr::Str(text)) = &args[0] else {
443 return Err(type_error("Float.parse", "text", "String", &args[0], span));
444 };
445 Ok(match text.parse::<f64>() {
446 Ok(value) => Value::ok(Value(Repr::Float(value))),
447 Err(_) => Value::err(Value::error(format!("`{text}` is not a Float"))),
448 })
449 }
450 _ => Err(
451 RuntimeError::new(format!("`{type_name}` has no associated function `{name}`"))
452 .at(span),
453 ),
454 }
455}
456
457/// Dispatches `receiver.name(args)` to a builtin method.
458pub fn call_method(
459 host: &mut dyn Callable,
460 receiver: &Value,
461 name: &str,
462 args: &mut Vec<Value>,
463 span: Span,
464) -> Result<Value, RuntimeError> {
465 // A receiver that answers `length()` is one a program might have written
466 // `count()` on, so the shared table's own methods are what decide who is
467 // taught the spelling. The name is compared first, so an ordinary call
468 // never asks.
469 if name == "count" {
470 let type_name = receiver.type_name();
471 if cove_schema::builtins::declares_length(&type_name) {
472 return Err(count_is_spelled_length(&type_name, span));
473 }
474 }
475 match receiver {
476 Value(Repr::Array(items)) => match name {
477 "get" => Ok(index_of("Array.get", args, span)?
478 .and_then(|i| items.get(i).cloned())
479 .map(Value::some)
480 .unwrap_or_else(Value::none)),
481 "length" => {
482 expect_args(name, args, 0, span)?;
483 Ok(Value(Repr::Int(items.len() as i64)))
484 }
485 // `isEmpty` used to answer here too, `length() == 0`. It does
486 // not reach this arm any more: `Interpreter::eval_method_call`
487 // resolves it to a call into `std.array.isEmpty` before this
488 // function is ever asked about it — see
489 // `cove_schema::builtins::standard_binding`.
490 "contains" => contains("Array.contains", items, args, span),
491 "indexOf" => index_of_element("Array.indexOf", items, args, span),
492 "slice" => Ok(Value(Repr::Array(slice("Array.slice", items, args, span)?))),
493 // `Vector.toArray` run backwards: a growable copy of these
494 // elements that nothing else holds a handle to, so a `freeze()`
495 // on it is the O(1) one. The elements are cloned as they are
496 // rather than snapshotted, which is `toArray`'s own rule — this
497 // separates the sequence and nothing inside it. The storage
498 // comes from the running task's heap like every other `Vector`,
499 // so the collector sees it.
500 "toVector" => {
501 expect_args("toVector", args, 0, span)?;
502 Ok(host.allocate_vector(items.to_vec()))
503 }
504 // `filter` and `fold` used to answer here too, through
505 // `walk_with` below. Neither reaches this arm any more:
506 // `Interpreter::eval_method_call` resolves both to a call into
507 // `std.array.filter` and `std.array.fold` before this function is
508 // ever asked about them — see
509 // `cove_schema::builtins::standard_binding`.
510 "map" | "sorted" => walk_with(host, "Array", items.to_vec(), name, args, span),
511 _ => Err(no_method("Array", name, span)),
512 },
513 Value(Repr::Vector(storage)) => {
514 check_live(storage, name, span)?;
515 match name {
516 "push" => {
517 let args = expect_args("push", args, 1, span)?;
518 storage.elements.borrow_mut().push(args.remove(0));
519 Ok(Value(Repr::Unit))
520 }
521 // Replaces the element at `index` and answers what was
522 // there, or answers `None` and writes nothing when `index`
523 // is not already in the vector — which is `get`'s answer to
524 // the same bad index, so a program has one rule about
525 // indices rather than two. The write goes through the
526 // storage handle, exactly as `push`'s does, so an alias
527 // observes it and there is nothing to write back to the
528 // receiver's own slot.
529 "set" => {
530 let args = expect_args("Vector.set", args, 2, span)?;
531 let value = args.remove(1);
532 let Some(index) = index_of("Vector.set", args, span)? else {
533 return Ok(Value::none());
534 };
535 let mut elements = storage.elements.borrow_mut();
536 let Some(slot) = elements.get_mut(index) else {
537 return Ok(Value::none());
538 };
539 Ok(Value::some(std::mem::replace(slot, value)))
540 }
541 // Takes the last element out and answers it, or answers
542 // `None` and writes nothing when there is no last element.
543 //
544 // The empty case is `remove(length() - 1)` on an empty
545 // vector, where that index is `-1` — which `get`, `set` and
546 // `remove` all answer `None` for. One rule about indices,
547 // rather than a rule about indices and a rule about
548 // emptiness.
549 "pop" => {
550 expect_args("Vector.pop", args, 0, span)?;
551 Ok(storage
552 .elements
553 .borrow_mut()
554 .pop()
555 .map(Value::some)
556 .unwrap_or_else(Value::none))
557 }
558 // Takes the element at `index` out, moves everything after
559 // it down one, and answers what was there — or answers
560 // `None` and removes nothing for an index that is not
561 // already in the vector, which is `get`'s answer and
562 // `set`'s. The write goes through the storage handle, as
563 // `push`'s and `set`'s do, so an alias observes the shrink.
564 "remove" => {
565 let Some(index) = index_of("Vector.remove", args, span)? else {
566 return Ok(Value::none());
567 };
568 let mut elements = storage.elements.borrow_mut();
569 if index >= elements.len() {
570 return Ok(Value::none());
571 }
572 Ok(Value::some(elements.remove(index)))
573 }
574 "get" => Ok(index_of("Vector.get", args, span)?
575 .and_then(|i| storage.elements.borrow().get(i).cloned())
576 .map(Value::some)
577 .unwrap_or_else(Value::none)),
578 "contains" => contains("Vector.contains", &storage.elements.borrow(), args, span),
579 "indexOf" => {
580 index_of_element("Vector.indexOf", &storage.elements.borrow(), args, span)
581 }
582 "slice" => {
583 let sliced = slice("Vector.slice", &storage.elements.borrow(), args, span)?;
584 Ok(Value(Repr::Array(sliced)))
585 }
586 "length" => {
587 expect_args(name, args, 0, span)?;
588 Ok(Value(Repr::Int(storage.len() as i64)))
589 }
590 // `isEmpty` used to answer here too, `storage.is_empty()`.
591 // It does not reach this arm any more:
592 // `Interpreter::eval_method_call` resolves it to a call into
593 // `std.vector.isEmpty` before this function is ever asked
594 // about it — see `cove_schema::builtins::standard_binding`.
595 "freeze" => {
596 expect_args("freeze", args, 0, span)?;
597 freeze(storage, span)
598 }
599 "toArray" => {
600 expect_args("toArray", args, 0, span)?;
601 Ok(Value(Repr::Array(
602 storage.elements.borrow().iter().cloned().collect(),
603 )))
604 }
605 // `filter` and `fold` used to answer here too, through
606 // `walk_with` below, taking the same copy first. Neither
607 // reaches this arm any more: `Interpreter::eval_method_call`
608 // resolves both to a call into `std.vector.filter` and
609 // `std.vector.fold` before this function is ever asked about
610 // them — see `cove_schema::builtins::standard_binding`.
611 "map" | "sorted" => {
612 // The elements come out here, before the first callback,
613 // and the borrow ends with this statement. Both matter:
614 // a callback can reach this very vector and push onto it
615 // or `freeze` it, and it must find neither a live borrow
616 // nor a walk that changes under it.
617 let elements = storage.elements.borrow().clone();
618 walk_with(host, "Vector", elements, name, args, span)
619 }
620 _ => Err(no_method("Vector", name, span)),
621 }
622 }
623 Value(Repr::Map(entries)) => match name {
624 "get" => {
625 let args = expect_args("Map.get", args, 1, span)?;
626 let key = to_map_key("Map.get", "map key", &args[0], span)?;
627 Ok(entries
628 .get(&key)
629 .cloned()
630 .map(Value::some)
631 .unwrap_or_else(Value::none))
632 }
633 "contains" => {
634 let args = expect_args("Map.contains", args, 1, span)?;
635 let key = to_map_key("Map.contains", "map key", &args[0], span)?;
636 Ok(Value(Repr::Bool(entries.contains_key(&key))))
637 }
638 "length" => {
639 expect_args(name, args, 0, span)?;
640 Ok(Value(Repr::Int(entries.len() as i64)))
641 }
642 // `isEmpty` used to answer here too, `entries.is_empty()`. It
643 // does not reach this arm any more: `Interpreter::eval_method_call`
644 // resolves it to a call into `std.map.isEmpty` before this
645 // function is ever asked about it — see
646 // `cove_schema::builtins::standard_binding`.
647 // Ascending key order, matching the `BTreeMap` storage and the
648 // order `for` iterates.
649 "keys" => {
650 expect_args(name, args, 0, span)?;
651 Ok(Value(Repr::Array(
652 entries.keys().map(MapKey::to_value).collect(),
653 )))
654 }
655 "values" => {
656 expect_args(name, args, 0, span)?;
657 Ok(Value(Repr::Array(entries.values().cloned().collect())))
658 }
659 // `Map` is immutable, so `inserted`/`removed` return a new map
660 // rather than write through `entries`; the past-participle names
661 // say so, unlike `Vector`'s mutating `push`.
662 "inserted" => {
663 let args = expect_args("Map.inserted", args, 2, span)?;
664 let value = args.remove(1);
665 let key = to_map_key("Map.inserted", "map key", &args[0], span)?;
666 let mut next = (**entries).clone();
667 next.insert(key, value);
668 Ok(Value(Repr::Map(Rc::new(next))))
669 }
670 "removed" => {
671 let args = expect_args("Map.removed", args, 1, span)?;
672 let key = to_map_key("Map.removed", "map key", &args[0], span)?;
673 let mut next = (**entries).clone();
674 next.remove(&key);
675 Ok(Value(Repr::Map(Rc::new(next))))
676 }
677 _ => Err(no_method("Map", name, span)),
678 },
679 Value(Repr::Set(items)) => match name {
680 "contains" => {
681 let args = expect_args("Set.contains", args, 1, span)?;
682 let key = to_map_key("Set.contains", "set element", &args[0], span)?;
683 Ok(Value(Repr::Bool(items.contains(&key))))
684 }
685 "length" => {
686 expect_args(name, args, 0, span)?;
687 Ok(Value(Repr::Int(items.len() as i64)))
688 }
689 // `isEmpty` used to answer here too, `items.is_empty()`. It does
690 // not reach this arm any more: `Interpreter::eval_method_call`
691 // resolves it to a call into `std.set.isEmpty` before this
692 // function is ever asked about it — see
693 // `cove_schema::builtins::standard_binding`.
694 "toArray" => {
695 expect_args(name, args, 0, span)?;
696 Ok(Value(Repr::Array(
697 items.iter().map(MapKey::to_value).collect(),
698 )))
699 }
700 "inserted" => {
701 let args = expect_args("Set.inserted", args, 1, span)?;
702 let key = to_map_key("Set.inserted", "set element", &args[0], span)?;
703 let mut next = (**items).clone();
704 next.insert(key);
705 Ok(Value(Repr::Set(Rc::new(next))))
706 }
707 "removed" => {
708 let args = expect_args("Set.removed", args, 1, span)?;
709 let key = to_map_key("Set.removed", "set element", &args[0], span)?;
710 let mut next = (**items).clone();
711 next.remove(&key);
712 Ok(Value(Repr::Set(Rc::new(next))))
713 }
714 _ => Err(no_method("Set", name, span)),
715 },
716 Value(Repr::Str(text)) => match name {
717 "length" => {
718 expect_args(name, args, 0, span)?;
719 Ok(Value(Repr::Int(text.chars().count() as i64)))
720 }
721 // `isEmpty` used to answer here too, `text.is_empty()`. It does
722 // not reach this arm any more: `Interpreter::eval_method_call`
723 // resolves it to a call into `std.string.isEmpty` before this
724 // function is ever asked about it — see
725 // `cove_schema::builtins::standard_binding`.
726 "words" => {
727 expect_args(name, args, 0, span)?;
728 Ok(Value(Repr::Array(
729 text.split_ascii_whitespace()
730 .map(|w| Value(Repr::Str(w.into())))
731 .collect(),
732 )))
733 }
734 "chars" => {
735 expect_args(name, args, 0, span)?;
736 Ok(Value(Repr::Array(
737 text.chars()
738 .map(|c| Value(Repr::Str(one_character(c))))
739 .collect(),
740 )))
741 }
742 "split" => {
743 let args = expect_args("String.split", args, 1, span)?;
744 let separator = expect_str("String.split", "separator", &args[0], span)?;
745 if separator.is_empty() {
746 return Err(empty_needle_error(
747 "String.split",
748 "separator",
749 "use `chars()` to take a string apart character by character",
750 span,
751 ));
752 }
753 Ok(Value(Repr::Array(
754 text.split(separator)
755 .map(|part| Value(Repr::Str(part.into())))
756 .collect(),
757 )))
758 }
759 "join" => {
760 let args = expect_args("String.join", args, 1, span)?;
761 let Value(Repr::Array(parts)) = &args[0] else {
762 return Err(type_error(
763 "String.join",
764 "parts",
765 "Array<String>",
766 &args[0],
767 span,
768 ));
769 };
770 let mut joined = String::new();
771 for (index, part) in parts.iter().enumerate() {
772 if index > 0 {
773 joined.push_str(text);
774 }
775 joined.push_str(expect_str("String.join", "parts", part, span)?);
776 }
777 Ok(Value(Repr::Str(joined.into())))
778 }
779 "slice" => {
780 let args = expect_args("String.slice", args, 2, span)?;
781 let Value(Repr::Int(from)) = &args[0] else {
782 return Err(type_error("String.slice", "from", "Int", &args[0], span));
783 };
784 let Value(Repr::Int(to)) = &args[1] else {
785 return Err(type_error("String.slice", "to", "Int", &args[1], span));
786 };
787 let chars: Vec<char> = text.chars().collect();
788 let len = chars.len() as i64;
789 let from = (*from).clamp(0, len) as usize;
790 let to = (*to).clamp(0, len) as usize;
791 Ok(Value(Repr::Str(if to <= from {
792 "".into()
793 } else {
794 chars[from..to].iter().collect::<String>().into()
795 })))
796 }
797 "trim" => {
798 expect_args(name, args, 0, span)?;
799 Ok(Value(Repr::Str(text.trim().into())))
800 }
801 "contains" => {
802 let args = expect_args("String.contains", args, 1, span)?;
803 let needle = expect_str("String.contains", "text", &args[0], span)?;
804 Ok(Value(Repr::Bool(text.contains(needle))))
805 }
806 "startsWith" => {
807 let args = expect_args("String.startsWith", args, 1, span)?;
808 let prefix = expect_str("String.startsWith", "prefix", &args[0], span)?;
809 Ok(Value(Repr::Bool(text.starts_with(prefix))))
810 }
811 "endsWith" => {
812 let args = expect_args("String.endsWith", args, 1, span)?;
813 let suffix = expect_str("String.endsWith", "suffix", &args[0], span)?;
814 Ok(Value(Repr::Bool(text.ends_with(suffix))))
815 }
816 "indexOf" => {
817 let args = expect_args("String.indexOf", args, 1, span)?;
818 let needle = expect_str("String.indexOf", "text", &args[0], span)?;
819 Ok(match text.find(needle) {
820 // `find` answers a byte offset; the characters before it
821 // are counted to convert that into the character index
822 // `length()` already counts in.
823 Some(byte_index) => {
824 Value::some(Value(Repr::Int(text[..byte_index].chars().count() as i64)))
825 }
826 None => Value::none(),
827 })
828 }
829 "replace" => {
830 let args = expect_args("String.replace", args, 2, span)?;
831 let old = expect_str("String.replace", "old", &args[0], span)?;
832 if old.is_empty() {
833 return Err(empty_needle_error(
834 "String.replace",
835 "old",
836 "`old` is the text to look for, and an empty `old` names none",
837 span,
838 ));
839 }
840 let new = expect_str("String.replace", "new", &args[1], span)?;
841 Ok(Value(Repr::Str(text.replace(old, new).into())))
842 }
843 "toUpper" => {
844 expect_args(name, args, 0, span)?;
845 Ok(Value(Repr::Str(text.to_uppercase().into())))
846 }
847 "toLower" => {
848 expect_args(name, args, 0, span)?;
849 Ok(Value(Repr::Str(text.to_lowercase().into())))
850 }
851 // The three byte-counted operations. Their diagnostics are
852 // written out again in `crates/cove-runtime/src/vm/builtins/text.rs`
853 // rather than shared, as every other builtin's are; what holds
854 // the two readings together is `tests/e2e/values_string`, which
855 // runs on both backends against one `expected.out`.
856 "byteLength" => {
857 expect_args(name, args, 0, span)?;
858 Ok(Value(Repr::Int(text.len() as i64)))
859 }
860 "byteAt" => {
861 let args = expect_args("String.byteAt", args, 1, span)?;
862 let Value(Repr::Int(offset)) = &args[0] else {
863 return Err(type_error("String.byteAt", "offset", "Int", &args[0], span));
864 };
865 // Refused rather than answered, which is `sliceBytes`'s rule
866 // and not `codePointAtByte`'s: a byte offset out of range is
867 // one this type never handed out, and `byteLength()` is how a
868 // caller knows the range. The VM's `Inst::ByteAt` refuses in
869 // the same words.
870 match usize::try_from(*offset)
871 .ok()
872 .and_then(|at| text.as_bytes().get(at))
873 {
874 Some(byte) => Ok(Value(Repr::Int(*byte as i64))),
875 None => Err(RuntimeError::new(format!(
876 "`byteAt` is `{offset}`, and a byte offset into this string is 0 to {}",
877 text.len() as i64 - 1
878 ))
879 .at(span)),
880 }
881 }
882 "codePointAtByte" => {
883 let args = expect_args("String.codePointAtByte", args, 1, span)?;
884 let Value(Repr::Int(offset)) = &args[0] else {
885 return Err(type_error(
886 "String.codePointAtByte",
887 "offset",
888 "Int",
889 &args[0],
890 span,
891 ));
892 };
893 // Past the end, before the start, and inside a character are
894 // one answer on purpose: a scanner that advances by the width
895 // of what it read reaches none of them, and telling them
896 // apart would cost a `Result` on the one path this exists for.
897 Ok(
898 match usize::try_from(*offset)
899 .ok()
900 .filter(|at| *at < text.len() && text.is_char_boundary(*at))
901 .and_then(|at| text[at..].chars().next())
902 {
903 Some(character) => Value::some(Value(Repr::Int(character as i64))),
904 None => Value::none(),
905 },
906 )
907 }
908 "sliceBytes" => {
909 let args = expect_args("String.sliceBytes", args, 2, span)?;
910 let Value(Repr::Int(from)) = &args[0] else {
911 return Err(type_error(
912 "String.sliceBytes",
913 "from",
914 "Int",
915 &args[0],
916 span,
917 ));
918 };
919 let Value(Repr::Int(to)) = &args[1] else {
920 return Err(type_error("String.sliceBytes", "to", "Int", &args[1], span));
921 };
922 Ok(match byte_range(text, *from, *to) {
923 Ok(range) => Value::ok(Value(Repr::Str(text[range].into()))),
924 Err(message) => Value::err(Value::error(message)),
925 })
926 }
927 _ => Err(no_method("String", name, span)),
928 },
929 Value(Repr::Range {
930 start,
931 end,
932 inclusive_end,
933 }) => {
934 let bounds = RangeBounds::of(*start, *end, *inclusive_end);
935 match name {
936 "length" => {
937 expect_args(name, args, 0, span)?;
938 Ok(Value(Repr::Int(bounds.len())))
939 }
940 "isEmpty" => {
941 expect_args(name, args, 0, span)?;
942 Ok(Value(Repr::Bool(bounds.is_empty())))
943 }
944 "contains" => {
945 let args = expect_args("contains", args, 1, span)?;
946 let Value(Repr::Int(value)) = &args[0] else {
947 return Err(type_error("Range.contains", "value", "Int", &args[0], span));
948 };
949 Ok(Value(Repr::Bool(bounds.contains(*value))))
950 }
951 _ => Err(no_method("Range", name, span)),
952 }
953 }
954 // `Option` and `Result` do not answer here at all any more. Every
955 // one of their methods — `isSome`, `isNone`, `unwrapOr` on the one,
956 // `isOk`, `isError`, `unwrapOr`, `mapError` on the other — is
957 // resolved by `Interpreter::eval_method_call` to a call into
958 // `std.option` or `std.result` before this function is ever asked;
959 // see `cove_schema::builtins::standard_binding`.
960 //
961 // `mapError` was the last to go and it needed a language change
962 // rather than a migration: while a callback of no parameters could
963 // stand in for one that takes the error, no Cove body could call it.
964 // ADR 0044 removed that exception.
965 Value(Repr::Int(n)) => match name {
966 "toFloat" => {
967 expect_args(name, args, 0, span)?;
968 Ok(Value(Repr::Float(*n as f64)))
969 }
970 // `min`, `max`, and `abs` used to answer here too, `(*n).min(*other)`,
971 // `(*n).max(*other)`, and `n.checked_abs()`. None reaches this arm
972 // any more: `Interpreter::eval_method_call` resolves them to a
973 // call into `std.int.min`/`std.int.max`/`std.int.abs` before this
974 // function is ever asked about them — see
975 // `cove_schema::builtins::standard_binding`.
976 _ => Err(no_method("Int", name, span)),
977 },
978 Value(Repr::Float(x)) => match name {
979 "toInt" => {
980 expect_args(name, args, 0, span)?;
981 Ok(float_to_int(*x))
982 }
983 "round" => {
984 expect_args(name, args, 0, span)?;
985 Ok(Value(Repr::Float(x.round())))
986 }
987 "abs" => {
988 expect_args(name, args, 0, span)?;
989 Ok(Value(Repr::Float(x.abs())))
990 }
991 "sqrt" => {
992 expect_args(name, args, 0, span)?;
993 Ok(Value(Repr::Float(x.sqrt())))
994 }
995 "min" => {
996 let args = expect_args("Float.min", args, 1, span)?;
997 let Value(Repr::Float(other)) = &args[0] else {
998 return Err(type_error("Float.min", "other", "Float", &args[0], span));
999 };
1000 Ok(Value(Repr::Float(x.min(*other))))
1001 }
1002 "max" => {
1003 let args = expect_args("Float.max", args, 1, span)?;
1004 let Value(Repr::Float(other)) = &args[0] else {
1005 return Err(type_error("Float.max", "other", "Float", &args[0], span));
1006 };
1007 Ok(Value(Repr::Float(x.max(*other))))
1008 }
1009 "format" => {
1010 let args = expect_args("Float.format", args, 1, span)?;
1011 let Value(Repr::Int(digits)) = &args[0] else {
1012 return Err(type_error("Float.format", "digits", "Int", &args[0], span));
1013 };
1014 if !(0..=17).contains(digits) {
1015 return Err(format_digits_error(*digits, span));
1016 }
1017 Ok(Value(Repr::Str(
1018 format!("{:.*}", *digits as usize, x).into(),
1019 )))
1020 }
1021 _ => Err(no_method("Float", name, span)),
1022 },
1023 // `d.nanos()`: the one primitive reader left. `micros` through
1024 // `hours` are `std.duration.micros` and its four neighbours now,
1025 // resolved by `Interpreter::eval_method_call` before this function
1026 // is ever asked — see
1027 // `cove_schema::builtins::standard_binding`.
1028 Value(Repr::Duration(ns)) => match name {
1029 "nanos" => {
1030 expect_args(name, args, 0, span)?;
1031 Ok(Value(Repr::Int(*ns)))
1032 }
1033 _ => Err(no_method("Duration", name, span)),
1034 },
1035 other => Err(no_method(&other.type_name(), name, span)),
1036 }
1037}
1038
1039/// `map` and `sorted`, the two operations on an `Array` and on a `Vector`
1040/// that still take their callback here, as the interpreter runs them: the
1041/// linear-memory backend lowers each of the two to its own loop instead —
1042/// see `crates/cove-ir/src/lower/walks.rs`, which calls this file's version
1043/// the oracle it has to agree with.
1044///
1045/// `filter` and `fold` used to be two more. They are ordinary calls into
1046/// `std.array` and `std.vector` now, resolved before either evaluator ever
1047/// reaches this function — see `cove_schema::builtins::standard_binding`.
1048///
1049/// `elements` is already the caller's own copy — the `Array`'s elements, or
1050/// the `Vector`'s taken out from under its `RefCell` before this was
1051/// called — which is what makes the walk a walk over a snapshot. A callback
1052/// that reaches the vector it was handed an element of may push onto it,
1053/// `freeze` it, or drop the last other handle to it, and neither changes
1054/// what is being walked or what comes back. The lowering makes the same
1055/// decision by reading a sequence's length once, with `Inst::Len`, before it
1056/// walks; this is that decision in the place where a closure rather than a
1057/// loop body is what could do the mutating.
1058///
1059/// Everything a callback costs is accounted where any other call is:
1060/// [`Callable::call_value`] is the evaluator re-entered, so fuel, the depth
1061/// limit, the host's `max_call_depth`, cancellation, and the trace are the
1062/// running task's exactly as they are outside a builtin. There is nothing
1063/// here that steps around a safepoint, because there is nothing here that
1064/// runs Cove code by any other route.
1065///
1066/// A callback that fails takes the whole call with it. The answer is built
1067/// to the side and returned only on success, so no half-built array and no
1068/// half-sorted sequence is ever reachable, and no receiver is written
1069/// through on any path.
1070///
1071/// # The argument list is `args`, once, for the whole walk
1072///
1073/// Each of the two takes its callback out of `args` first, which leaves that
1074/// vector empty with its capacity intact — so it is what every invocation of
1075/// the callback is handed, filled and drained again per element rather than
1076/// allocated per element. That is issue #193: `map` built a `vec![item]` for
1077/// each element it visited, `filter` a `vec![item.clone()]`, `fold` a
1078/// `vec![total, item]`, and `sorted` one per comparison, which for
1079/// `examples/life`'s `population()` is an allocation per creature per tick —
1080/// true of all four at the time #193 was fixed, even though two of them have
1081/// since moved out of this function entirely.
1082///
1083/// It costs nothing to arrange because `args` is already a vector the
1084/// caller lends. The predecessor pooled its own argument vectors the same
1085/// way starting at #184; #193 is that scheme reaching a path it could not
1086/// reach before, by being handed one level further down. A slice would not
1087/// do here for the same reason it would not do there — `map` moves its
1088/// element into the call, and the callback re-enters the evaluator and may
1089/// push onto the very stack a slice would point into.
1090fn walk_with(
1091 host: &mut dyn Callable,
1092 type_name: &str,
1093 elements: Vec<Value>,
1094 name: &str,
1095 args: &mut Vec<Value>,
1096 span: Span,
1097) -> Result<Value, RuntimeError> {
1098 let method = format!("{type_name}.{name}");
1099 match name {
1100 "map" => {
1101 let args = expect_args(&method, args, 1, span)?;
1102 let transform = args.remove(0);
1103 expect_callback(
1104 host,
1105 &method,
1106 "transform",
1107 "fn(T) -> R",
1108 1,
1109 &transform,
1110 span,
1111 )?;
1112 let mut mapped = Vec::with_capacity(elements.len());
1113 for item in elements {
1114 args.push(item);
1115 mapped.push(host.call_value(&transform, args, span)?);
1116 }
1117 Ok(Value(Repr::Array(mapped.into())))
1118 }
1119 "sorted" => {
1120 let args = expect_args(&method, args, 1, span)?;
1121 let by = args.remove(0);
1122 expect_callback(host, &method, "by", "fn(T, T) -> Bool", 2, &by, span)?;
1123 Ok(Value(Repr::Array(
1124 merge_sort(host, &method, elements, &by, args, span)?.into(),
1125 )))
1126 }
1127 // Only the two names above are routed here now; `filter` and `fold`
1128 // are resolved to a standard-library call before either evaluator
1129 // reaches this function. Answering the way an unknown method is
1130 // answered keeps that a fact rather than a `panic!` nobody can reach.
1131 _ => Err(no_method(type_name, name, span)),
1132 }
1133}
1134
1135/// A stable merge sort under a Cove callback.
1136///
1137/// Written out rather than handed to `slice::sort_by`, for two reasons
1138/// either of which would be enough on its own.
1139///
1140/// `by` can fail — it is a Cove closure, and a closure can raise or be
1141/// cancelled — and a `FnMut(&T, &T) -> Ordering` has nowhere to put a
1142/// failure. Smuggling one out through a cell and re-raising it afterwards
1143/// would mean the sort kept comparing after the run should have stopped.
1144///
1145/// And `by` can contradict itself. `slice::sort_by` panics when its
1146/// comparison function does not order the elements, and a panic in this
1147/// runtime means a broken invariant of the runtime; a program that wrote an
1148/// inconsistent comparison has broken nothing but its own ordering. A merge
1149/// answers some permutation instead, which is exactly what "no promise about
1150/// which" means, and it is the schema's stated behaviour rather than a
1151/// consequence of the algorithm that was to hand.
1152///
1153/// Bottom up: runs of one merged into runs of two, then four. The right
1154/// run's element is taken only when `by` says it comes *strictly* before the
1155/// left run's, which is what makes the sort stable — equal elements meet
1156/// with the earlier one on the left and the earlier one is kept.
1157fn merge_sort(
1158 host: &mut dyn Callable,
1159 method: &str,
1160 elements: Vec<Value>,
1161 by: &Value,
1162 args: &mut Vec<Value>,
1163 span: Span,
1164) -> Result<Vec<Value>, RuntimeError> {
1165 let len = elements.len();
1166 let mut source = elements;
1167 let mut merged: Vec<Value> = Vec::with_capacity(len);
1168 let mut width = 1usize;
1169 while width < len {
1170 merged.clear();
1171 let mut start = 0usize;
1172 while start < len {
1173 let middle = (start + width).min(len);
1174 let end = (start + width * 2).min(len);
1175 let (mut left, mut right) = (start, middle);
1176 while left < middle && right < end {
1177 args.push(source[right].clone());
1178 args.push(source[left].clone());
1179 let verdict = host.call_value(by, args, span)?;
1180 if callback_bool(method, "by", &verdict, span)? {
1181 merged.push(source[right].clone());
1182 right += 1;
1183 } else {
1184 merged.push(source[left].clone());
1185 left += 1;
1186 }
1187 }
1188 merged.extend_from_slice(&source[left..middle]);
1189 merged.extend_from_slice(&source[right..end]);
1190 start = end;
1191 }
1192 std::mem::swap(&mut source, &mut merged);
1193 width *= 2;
1194 }
1195 Ok(source)
1196}
1197
1198/// Holds a higher-order builtin's callback to the shape its signature
1199/// declares, before it is called rather than while it is being called.
1200///
1201/// The checker settles this for every program it accepts, so nothing a
1202/// checked program does reaches either failure. It is still asked here,
1203/// once for the whole walk, rather than left for
1204/// `Interpreter::call_value_slots` to discover on the first call: a walk of
1205/// zero elements never makes that call at all, so leaving the check there
1206/// would mean an empty `Array` misses a callback of the wrong arity that a
1207/// full one catches. Asking here also gives the failure the builtin's own
1208/// words — the declared shape, `fn(T) -> R`, and which parameter — rather
1209/// than a plain arity count. `map` and `sorted` are the interpreter's own
1210/// implementation of the two walks that remain here — `filter` and `fold`
1211/// moved to the standard library and never reach this function — and the
1212/// linear-memory backend lowers `map` and `sorted` on its own and never
1213/// reaches this function either.
1214fn expect_callback(
1215 host: &dyn Callable,
1216 method: &str,
1217 parameter: &str,
1218 expected: &str,
1219 parameters: usize,
1220 value: &Value,
1221 span: Span,
1222) -> Result<(), RuntimeError> {
1223 match host.arity(value) {
1224 Some(found) if found == parameters => Ok(()),
1225 Some(found) => Err(RuntimeError::new(format!(
1226 "`{method}` expects `{expected}` for `{parameter}`, but found a function of {found} parameter(s)"
1227 ))
1228 .at(span)),
1229 None => Err(type_error(method, parameter, expected, value, span)),
1230 }
1231}
1232
1233/// Reads a callback's answer as the `Bool` its signature declares.
1234///
1235/// Unreachable from a checked program for the same reason [`expect_callback`]
1236/// is, and stated for the same reason: the alternative is a `Bool` taken on
1237/// trust in the middle of a sort.
1238fn callback_bool(
1239 method: &str,
1240 parameter: &str,
1241 value: &Value,
1242 span: Span,
1243) -> Result<bool, RuntimeError> {
1244 match value {
1245 Value(Repr::Bool(answer)) => Ok(*answer),
1246 other => Err(RuntimeError::new(format!(
1247 "`{method}` expects `{parameter}` to answer a `Bool`, but found `{}`",
1248 other.type_name()
1249 ))
1250 .at(span)),
1251 }
1252}
1253
1254/// Consumes uniquely owned vector storage and returns its elements as an
1255/// `Array` in O(1).
1256///
1257/// Uniqueness is the runtime form of the Language Card's local uniqueness
1258/// check: the caller must hold the only handle to this storage.
1259pub fn freeze(storage: &Rc<VectorStorage>, span: Span) -> Result<Value, RuntimeError> {
1260 check_live(storage, "freeze", span)?;
1261 if Rc::strong_count(storage) != 1 {
1262 return Err(RuntimeError::new(
1263 "`freeze()` needs uniquely owned vector storage, but another alias observes this vector",
1264 )
1265 .at(span)
1266 .with_rule(
1267 "`freeze()` consumes a locally unique vector and returns an immutable array in O(1).",
1268 )
1269 .with_help(
1270 "call `toArray()` instead, which copies the elements in O(n), or drop the other alias before calling `freeze()`",
1271 ));
1272 }
1273 let elements = storage.elements.take();
1274 *storage.frozen.borrow_mut() = true;
1275 Ok(Value(Repr::Array(elements.into())))
1276}
1277
1278/// A vector consumed by `freeze()` is no longer usable.
1279pub fn check_live(
1280 storage: &Rc<VectorStorage>,
1281 method: &str,
1282 span: Span,
1283) -> Result<(), RuntimeError> {
1284 if *storage.frozen.borrow() {
1285 return Err(RuntimeError::new(format!(
1286 "`{method}` was called on a vector that `freeze()` already consumed"
1287 ))
1288 .at(span)
1289 .with_rule("`freeze()` consumes its vector; the source vector is no longer usable.")
1290 .with_help("use the `Array` that `freeze()` returned, or build a new vector"));
1291 }
1292 Ok(())
1293}
1294
1295/// `contains(element)` on a sequence: whether any element is `==` to it.
1296///
1297/// Equality is [`Value::eq_value`], the same one `==` is and the same one
1298/// `Map` and `Set` are keyed by, so a sequence answers membership exactly as
1299/// a comparison of the two values would. An empty receiver answers `false`,
1300/// and no argument can be refused: every value has an equality.
1301fn contains(
1302 method: &str,
1303 items: &[Value],
1304 args: &mut Vec<Value>,
1305 span: Span,
1306) -> Result<Value, RuntimeError> {
1307 let args = expect_args(method, args, 1, span)?;
1308 Ok(Value(Repr::Bool(
1309 items.iter().any(|item| item.eq_value(&args[0])),
1310 )))
1311}
1312
1313/// `indexOf(element)` on a sequence: the first position holding a value `==`
1314/// to it, or `None`.
1315///
1316/// The same equality [`contains`] uses, so the two cannot disagree about
1317/// whether an element is there. An empty receiver and an element that is not
1318/// in the sequence both answer `None`, which is what `String.indexOf` and
1319/// `Array.get` answer a question with no position to name.
1320fn index_of_element(
1321 method: &str,
1322 items: &[Value],
1323 args: &mut Vec<Value>,
1324 span: Span,
1325) -> Result<Value, RuntimeError> {
1326 let args = expect_args(method, args, 1, span)?;
1327 Ok(items
1328 .iter()
1329 .position(|item| item.eq_value(&args[0]))
1330 .map(|at| Value::some(Value(Repr::Int(at as i64))))
1331 .unwrap_or_else(Value::none))
1332}
1333
1334/// `slice(from, to)` on a sequence: the elements at `from..<to`.
1335///
1336/// Both bounds are clamped into `0..len` and a `to` at or below `from`
1337/// answers nothing, which is `String.slice`'s rule applied where the same
1338/// question arises rather than a second answer to it. So no argument can
1339/// stop the run: this refuses only a bound that is not an `Int` at all,
1340/// which is the receiver being called wrongly rather than an index being out
1341/// of range.
1342fn slice(
1343 method: &str,
1344 items: &[Value],
1345 args: &[Value],
1346 span: Span,
1347) -> Result<Rc<[Value]>, RuntimeError> {
1348 if args.len() != 2 {
1349 return Err(arity_error(method, 2, args.len(), span));
1350 }
1351 let bound = |at: usize, parameter: &str| match &args[at] {
1352 Value(Repr::Int(index)) => Ok((*index).clamp(0, items.len() as i64) as usize),
1353 other => Err(type_error(method, parameter, "Int", other, span)),
1354 };
1355 let from = bound(0, "from")?;
1356 let to = bound(1, "to")?;
1357 if to <= from {
1358 return Ok(Rc::from([]));
1359 }
1360 Ok(Rc::from(&items[from..to]))
1361}
1362
1363fn index_of(method: &str, args: &[Value], span: Span) -> Result<Option<usize>, RuntimeError> {
1364 if args.len() != 1 {
1365 return Err(arity_error(method, 1, args.len(), span));
1366 }
1367 match &args[0] {
1368 Value(Repr::Int(i)) if *i >= 0 => Ok(Some(*i as usize)),
1369 Value(Repr::Int(_)) => Ok(None),
1370 other => Err(type_error(method, "index", "Int", other, span)),
1371 }
1372}
1373
1374fn expect_args<'a>(
1375 method: &str,
1376 args: &'a mut Vec<Value>,
1377 count: usize,
1378 span: Span,
1379) -> Result<&'a mut Vec<Value>, RuntimeError> {
1380 if args.len() != count {
1381 return Err(arity_error(method, count, args.len(), span));
1382 }
1383 Ok(args)
1384}
1385
1386fn arity_error(method: &str, expected: usize, found: usize, span: Span) -> RuntimeError {
1387 RuntimeError::new(format!(
1388 "`{method}` takes {expected} argument(s), but {found} were given"
1389 ))
1390 .at(span)
1391}
1392
1393fn type_error(
1394 method: &str,
1395 parameter: &str,
1396 expected: &str,
1397 found: &Value,
1398 span: Span,
1399) -> RuntimeError {
1400 RuntimeError::new(format!(
1401 "`{method}` expects `{expected}` for `{parameter}`, but found `{}`",
1402 found.type_name()
1403 ))
1404 .at(span)
1405}
1406
1407fn no_method(type_name: &str, method: &str, span: Span) -> RuntimeError {
1408 RuntimeError::new(format!("`{type_name}` has no method `{method}`")).at(span)
1409}
1410
1411/// `Float.toInt`: truncates toward zero and names which of the three expected
1412/// failures stopped it. `NaN` is not a number, an infinity has no
1413/// truncation, and a magnitude at or past 2^63 does not fit in an `Int`.
1414fn float_to_int(x: f64) -> Value {
1415 if x.is_nan() {
1416 return Value::err(Value::error(
1417 "`Float.toInt` cannot convert `NaN`, which is not a number",
1418 ));
1419 }
1420 if x.is_infinite() {
1421 return Value::err(Value::error(format!(
1422 "`Float.toInt` cannot convert `{x}`, which has no truncation"
1423 )));
1424 }
1425 let truncated = x.trunc();
1426 if truncated < i64::MIN as f64 || truncated >= i64::MAX as f64 {
1427 return Value::err(Value::error(format!(
1428 "`Float.toInt` cannot convert `{x}`, which is outside Int's range"
1429 )));
1430 }
1431 Value::ok(Value(Repr::Int(truncated as i64)))
1432}
1433
1434/// `String.fromCodePoint`: the one-character `String` a code point names, and
1435/// otherwise which of the two ways the number names no character.
1436///
1437/// The surrogates get a sentence of their own because they are the failure a
1438/// caller is most likely to be able to do something about. A format that
1439/// writes a code point in sixteen bits — JSON's `\u`, and UTF-16 generally —
1440/// writes anything past `0xFFFF` as a pair of them, so a program that reached
1441/// here with a `0xD800` has half of a character rather than a bad one, and
1442/// what it needs to hear is that the other half is still to come. Combining
1443/// the pair is arithmetic the program does before it calls this: there is no
1444/// half-formed value to hand back, because a Cove `String` is UTF-8 and holds
1445/// no such thing.
1446fn from_code_point(code_point: i64) -> Value {
1447 if (0xD800..=0xDFFF).contains(&code_point) {
1448 return Value::err(Value::error(format!(
1449 "`{code_point}` is a surrogate half, which is not a character on its own"
1450 )));
1451 }
1452 match u32::try_from(code_point).ok().and_then(char::from_u32) {
1453 Some(character) => Value::ok(Value(Repr::Str(one_character(character)))),
1454 None => Value::err(Value::error(format!(
1455 "`{code_point}` is not a Unicode code point"
1456 ))),
1457 }
1458}
1459
1460/// `Int.parseRadix` refused a `radix` outside `2..=36`.
1461///
1462/// A radix of 1 has no place value and a radix of 0 has no digits, and past
1463/// 36 there are no more letters to spell one with. None of those is text the
1464/// data got wrong, so none of them is an `Err`: it is the call that is wrong,
1465/// and the run stops the way it stops for an empty `String.split` separator.
1466fn radix_error(radix: i64, span: Span) -> RuntimeError {
1467 RuntimeError::new(format!(
1468 "`Int.parseRadix` cannot read a number in radix `{radix}`"
1469 ))
1470 .at(span)
1471 .with_rule(
1472 "A radix is 2 through 36, which is as many digits as the ten numerals and the twenty-six letters afford.",
1473 )
1474 .with_help("pass a `radix` between 2 and 36, such as 16 for hexadecimal")
1475}
1476
1477/// `Float.format` refused a `digits` outside `0..=17`.
1478///
1479/// A `Float` carries at most 17 significant decimal digits, so a `digits`
1480/// past that asks for padding rather than precision, and a negative `digits`
1481/// names nothing.
1482fn format_digits_error(digits: i64, span: Span) -> RuntimeError {
1483 RuntimeError::new(format!("`Float.format` cannot use `{digits}` digits"))
1484 .at(span)
1485 .with_rule(
1486 "A Float carries at most 17 significant decimal digits, so `digits` must be between 0 and 17.",
1487 )
1488}
1489
1490/// The byte range `sliceBytes(from, to)` names, or what is wrong with it.
1491///
1492/// Four things can be, and they are checked in the order a reader would ask
1493/// them: is each end a byte offset into this string at all, do they run
1494/// forwards, and does each begin a character. The last is the one `slice` has
1495/// no equivalent of, and it is why this refuses where `slice` clamps — an
1496/// offset inside a character was never handed out by `codePointAtByte`, so
1497/// moving it to the nearest legal one would answer a question nobody asked.
1498fn byte_range(text: &str, from: i64, to: i64) -> Result<std::ops::Range<usize>, String> {
1499 let len = text.len();
1500 let offset = |name: &str, value: i64| -> Result<usize, String> {
1501 usize::try_from(value)
1502 .ok()
1503 .filter(|at| *at <= len)
1504 .ok_or_else(|| {
1505 format!("`{name}` is `{value}`, and a byte offset into this string is 0 to {len}")
1506 })
1507 };
1508 let start = offset("from", from)?;
1509 let end = offset("to", to)?;
1510 if start > end {
1511 return Err(format!(
1512 "`from` is `{from}` and `to` is `{to}`, so this range runs backwards"
1513 ));
1514 }
1515 for (name, at) in [("from", start), ("to", end)] {
1516 if !text.is_char_boundary(at) {
1517 return Err(format!(
1518 "`{name}` is `{at}`, which is inside a character rather than at the start of one"
1519 ));
1520 }
1521 }
1522 Ok(start..end)
1523}
1524
1525/// The one-character string `character` spells.
1526///
1527/// An ASCII character answers a string this thread already made, because
1528/// `chars()` is how a program takes text apart and a scanner asks for every
1529/// character of every line it reads. Allocating one string per character made
1530/// that the largest single source of allocation in `examples/cq`, and a
1531/// character's string is immutable and interchangeable, so there is no way for
1532/// a program to tell a shared one from a fresh one (issue #104).
1533///
1534/// The table is per thread rather than global because `Rc` is not shareable
1535/// across threads, which is the same reason `Value` uses `Rc` at all.
1536fn one_character(character: char) -> Rc<str> {
1537 thread_local! {
1538 static ASCII: [Rc<str>; 128] =
1539 std::array::from_fn(|byte| Rc::from((byte as u8 as char).to_string().as_str()));
1540 }
1541 if character.is_ascii() {
1542 return ASCII.with(|table| table[character as usize].clone());
1543 }
1544 character.to_string().into()
1545}
1546
1547/// Reads `value` as a `String`, or reports the type `method` declares for
1548/// `parameter` instead.
1549fn expect_str<'a>(
1550 method: &str,
1551 parameter: &str,
1552 value: &'a Value,
1553 span: Span,
1554) -> Result<&'a str, RuntimeError> {
1555 match value {
1556 Value(Repr::Str(text)) => Ok(text),
1557 other => Err(type_error(method, parameter, "String", other, span)),
1558 }
1559}
1560
1561/// `split` and `replace` both refuse an empty needle: matching against one
1562/// would match between every character rather than answer either method's
1563/// question.
1564///
1565/// The two are told different things afterwards, because the operation they
1566/// were reaching for is different. Splitting on nothing is a request for the
1567/// characters, which `chars()` answers; replacing nothing is not a request for
1568/// anything, so `replace` is told what it is missing rather than offered a
1569/// substitute.
1570fn empty_needle_error(method: &str, parameter: &str, help: &str, span: Span) -> RuntimeError {
1571 RuntimeError::new(format!("`{method}` cannot use an empty `{parameter}`"))
1572 .at(span)
1573 .with_rule(
1574 "An empty separator or search string would match between every character, rather than answer the question the method asks.",
1575 )
1576 .with_help(help)
1577}
1578
1579/// Converts `value` to a [`MapKey`], or reports why it cannot be a map key or
1580/// set element.
1581fn to_map_key(method: &str, role: &str, value: &Value, span: Span) -> Result<MapKey, RuntimeError> {
1582 MapKey::from_value(value).map_err(|invalid| invalid_key_error(method, role, &invalid, span))
1583}
1584
1585/// Names the specific offending part when the invalid value is nested, such
1586/// as `` a `Vector` inside `Point.tags` ``, rather than blaming the whole
1587/// struct: the Language Card promises errors that teach the rule they name.
1588fn invalid_key_error(method: &str, role: &str, invalid: &InvalidKey, span: Span) -> RuntimeError {
1589 let message = if invalid.path.is_empty() {
1590 format!(
1591 "`{method}` cannot use a `{}` as a {role}",
1592 invalid.type_name
1593 )
1594 } else {
1595 format!(
1596 "`{method}` cannot use a `{}` inside `{}` as a {role}",
1597 invalid.type_name, invalid.path
1598 )
1599 };
1600 RuntimeError::new(message)
1601 .at(span)
1602 .with_rule(invalid.rule())
1603 .with_help(invalid.help())
1604}
1605
1606/// `Map.of` and `Set.of` reject a duplicate key or element rather than
1607/// silently keeping one entry, because a literal with two identical keys is a
1608/// mistake, not an intent.
1609fn duplicate_key_error(method: &str, role: &str, key: &MapKey, span: Span) -> RuntimeError {
1610 RuntimeError::new(format!("`{method}` was given the {role} `{key}` more than once"))
1611 .at(span)
1612 .with_rule(
1613 "A literal with two identical keys is a mistake, not an intent; duplicate keys are rejected rather than silently resolved by keeping the last one.",
1614 )
1615 .with_help(format!("remove the duplicate, or give it a different {role}"))
1616}
1617
1618/// `Map.of` takes `MapEntry` values, built with `MapEntry(key:, value:)`.
1619fn expects_map_entry(found: &Value, span: Span) -> RuntimeError {
1620 RuntimeError::new(format!(
1621 "`Map.of` expects `MapEntry` values, but found `{}`",
1622 found.type_name()
1623 ))
1624 .at(span)
1625 .with_rule(
1626 "`Map.of(entries: MapEntry<K, V>...)` takes values built with `MapEntry(key:, value:)`.",
1627 )
1628}
1629
1630/// `count()` was removed in favour of a single spelling.
1631fn count_is_spelled_length(type_name: &str, span: Span) -> RuntimeError {
1632 RuntimeError::new(format!(
1633 "`{type_name}` has no method `count`; Cove spells the number of elements `length()`"
1634 ))
1635 .at(span)
1636 .with_rule("Every sequence reports its element count as `length()`; there is no `count()`.")
1637 .with_help("write `length()` instead of `count()`")
1638}