1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum IntrinsicOp {
6 Add,
7 Subtract,
8 Multiply,
9 Divide,
10 Remainder,
11 Modulo,
12 Equal,
13 Less,
14 LessOrEqual,
15 Greater,
16 GreaterOrEqual,
17}
18
19impl IntrinsicOp {
20 #[cfg(test)]
21 pub(crate) const ALL: &[IntrinsicOp] = &[
22 IntrinsicOp::Add,
23 IntrinsicOp::Subtract,
24 IntrinsicOp::Multiply,
25 IntrinsicOp::Divide,
26 IntrinsicOp::Remainder,
27 IntrinsicOp::Modulo,
28 IntrinsicOp::Equal,
29 IntrinsicOp::Less,
30 IntrinsicOp::LessOrEqual,
31 IntrinsicOp::Greater,
32 IntrinsicOp::GreaterOrEqual,
33 ];
34
35 pub fn from_symbol(symbol: &str) -> Option<IntrinsicOp> {
36 Some(match symbol {
37 "+" => IntrinsicOp::Add,
38 "-" => IntrinsicOp::Subtract,
39 "*" => IntrinsicOp::Multiply,
40 "/" => IntrinsicOp::Divide,
41 "mod" => IntrinsicOp::Modulo,
42 "=" => IntrinsicOp::Equal,
43 "<" => IntrinsicOp::Less,
44 "<=" => IntrinsicOp::LessOrEqual,
45 ">" => IntrinsicOp::Greater,
46 ">=" => IntrinsicOp::GreaterOrEqual,
47 _ => return None,
48 })
49 }
50
51 pub fn operator(self) -> &'static str {
53 match self {
54 IntrinsicOp::Add => "+",
55 IntrinsicOp::Subtract => "-",
56 IntrinsicOp::Multiply => "*",
57 IntrinsicOp::Divide => "/",
58 IntrinsicOp::Remainder => "rem",
59 IntrinsicOp::Modulo => "mod",
60 IntrinsicOp::Equal => "=",
61 IntrinsicOp::Less => "<",
62 IntrinsicOp::LessOrEqual => "<=",
63 IntrinsicOp::Greater => ">",
64 IntrinsicOp::GreaterOrEqual => ">=",
65 }
66 }
67}
68
69pub(crate) fn apply_intrinsic(
73 primitive: IntrinsicOp,
74 arguments: &[Value],
75) -> Result<Value, String> {
76 let op = primitive.operator();
77 if let [left, right] = arguments {
78 return apply_binary_intrinsic(primitive, left, right);
79 }
80 match primitive {
81 IntrinsicOp::Add
82 | IntrinsicOp::Subtract
83 | IntrinsicOp::Multiply
84 | IntrinsicOp::Divide
85 | IntrinsicOp::Remainder
86 | IntrinsicOp::Modulo => {
87 if arguments.is_empty() {
88 return Err(format!("{op} expects arguments"));
89 }
90 if matches!(primitive, IntrinsicOp::Remainder | IntrinsicOp::Modulo)
91 && arguments.len() != 2
92 {
93 return Err(format!("{op} expects two numbers"));
94 }
95 if arguments.len() == 1 {
96 if primitive == IntrinsicOp::Subtract {
97 return numeric::numeric_negate(&arguments[0]);
98 }
99 if primitive == IntrinsicOp::Divide {
100 return apply_binary_intrinsic(
101 IntrinsicOp::Divide,
102 &Value::Number(1),
103 &arguments[0],
104 );
105 }
106 if !numeric::is_numeric_value(&arguments[0]) {
107 return Err(format!("{op} expects numbers"));
108 }
109 return Ok(arguments[0].clone());
110 }
111 let mut result = arguments[0].clone();
112 for argument in &arguments[1..] {
113 result = apply_binary_intrinsic(primitive, &result, argument)?;
114 }
115 Ok(result)
116 }
117 IntrinsicOp::Equal => {
118 if arguments.len() < 2 {
119 return Err("= expects at least 2 arguments".into());
120 }
121 let first = &arguments[0];
122 Ok(Value::Bool(
123 arguments[1..].iter().all(|value| value == first),
124 ))
125 }
126 IntrinsicOp::Less
127 | IntrinsicOp::LessOrEqual
128 | IntrinsicOp::Greater
129 | IntrinsicOp::GreaterOrEqual => {
130 if arguments.len() < 2 {
131 return Err(format!("{op} expects at least two arguments"));
132 }
133 for pair in arguments.windows(2) {
134 let Some(ordering) = numeric::numeric_compare(&pair[0], &pair[1])? else {
135 return Err(format!("{op} expects numbers"));
136 };
137 let matches = match primitive {
138 IntrinsicOp::Less => ordering == std::cmp::Ordering::Less,
139 IntrinsicOp::LessOrEqual => ordering != std::cmp::Ordering::Greater,
140 IntrinsicOp::Greater => ordering == std::cmp::Ordering::Greater,
141 IntrinsicOp::GreaterOrEqual => ordering != std::cmp::Ordering::Less,
142 _ => unreachable!(),
143 };
144 if !matches {
145 return Ok(Value::Bool(false));
146 }
147 }
148 Ok(Value::Bool(true))
149 }
150 }
151}
152
153pub(crate) fn apply_intrinsic_name(name: &str, arguments: &[Value]) -> Result<Value, String> {
154 if let Some(primitive) = IntrinsicOp::from_symbol(name) {
155 return apply_intrinsic(primitive, arguments);
156 }
157 let native = name
158 .strip_prefix("std.native.")
159 .ok_or_else(|| format!("unknown runtime intrinsic: {name}"))?;
160 let (native_type, method) = native
161 .split_once('/')
162 .ok_or_else(|| format!("invalid native intrinsic target: {name}"))?;
163 let callable = native_type_function_value(native_type, method)?;
164 call_value(callable, arguments.to_vec())
165}
166
167pub(crate) fn apply_binary_intrinsic(
171 primitive: IntrinsicOp,
172 left: &Value,
173 right: &Value,
174) -> Result<Value, String> {
175 let op = primitive.operator();
176 if let (Value::Number(left), Value::Number(right)) = (left, right) {
177 return apply_binary_numbers(primitive, *left, *right);
178 }
179 match primitive {
180 IntrinsicOp::Equal => return Ok(Value::Bool(left == right)),
181 IntrinsicOp::Less
182 | IntrinsicOp::LessOrEqual
183 | IntrinsicOp::Greater
184 | IntrinsicOp::GreaterOrEqual => {
185 let Some(ordering) = numeric::numeric_compare(left, right)? else {
186 return Err(format!("{op} expects numbers"));
187 };
188 return Ok(Value::Bool(match primitive {
189 IntrinsicOp::Less => ordering == std::cmp::Ordering::Less,
190 IntrinsicOp::LessOrEqual => ordering != std::cmp::Ordering::Greater,
191 IntrinsicOp::Greater => ordering == std::cmp::Ordering::Greater,
192 IntrinsicOp::GreaterOrEqual => ordering != std::cmp::Ordering::Less,
193 _ => unreachable!(),
194 }));
195 }
196 IntrinsicOp::Add
197 | IntrinsicOp::Subtract
198 | IntrinsicOp::Multiply
199 | IntrinsicOp::Divide
200 | IntrinsicOp::Remainder
201 | IntrinsicOp::Modulo => {
202 let operation = match primitive {
203 IntrinsicOp::Add => ArithmeticOp::Add,
204 IntrinsicOp::Subtract => ArithmeticOp::Subtract,
205 IntrinsicOp::Multiply => ArithmeticOp::Multiply,
206 IntrinsicOp::Divide => ArithmeticOp::Divide,
207 IntrinsicOp::Remainder => ArithmeticOp::Remainder,
208 IntrinsicOp::Modulo => ArithmeticOp::Modulo,
209 _ => unreachable!(),
210 };
211 return numeric::numeric_binary(operation, left, right).map_err(|error| {
212 if error == "expected numeric values" {
213 format!("{op} expects numbers")
214 } else {
215 error
216 }
217 });
218 }
219 }
220}
221
222pub(crate) fn apply_binary_numbers(
223 primitive: IntrinsicOp,
224 left: i64,
225 right: i64,
226) -> Result<Value, String> {
227 apply_binary_numbers_promoting(primitive, left, right)
228}
229
230fn apply_binary_numbers_promoting(
231 primitive: IntrinsicOp,
232 left: i64,
233 right: i64,
234) -> Result<Value, String> {
235 let result = match primitive {
236 IntrinsicOp::Add => match left.checked_add(right) {
237 Some(value) => Value::Number(value),
238 None => {
239 return numeric::numeric_binary(
240 ArithmeticOp::Add,
241 &Value::Number(left),
242 &Value::Number(right),
243 )
244 }
245 },
246 IntrinsicOp::Subtract => match left.checked_sub(right) {
247 Some(value) => Value::Number(value),
248 None => {
249 return numeric::numeric_binary(
250 ArithmeticOp::Subtract,
251 &Value::Number(left),
252 &Value::Number(right),
253 )
254 }
255 },
256 IntrinsicOp::Multiply => match left.checked_mul(right) {
257 Some(value) => Value::Number(value),
258 None => {
259 return numeric::numeric_binary(
260 ArithmeticOp::Multiply,
261 &Value::Number(left),
262 &Value::Number(right),
263 )
264 }
265 },
266 IntrinsicOp::Divide | IntrinsicOp::Remainder | IntrinsicOp::Modulo if right == 0 => {
267 return Err("division by zero".into())
268 }
269 IntrinsicOp::Divide => match left.checked_div(right) {
270 Some(value) => Value::Number(value),
271 None => {
272 return numeric::numeric_binary(
273 ArithmeticOp::Divide,
274 &Value::Number(left),
275 &Value::Number(right),
276 )
277 }
278 },
279 IntrinsicOp::Remainder | IntrinsicOp::Modulo => {
280 if left == i64::MIN && right == -1 {
281 Value::Number(0)
282 } else {
283 Value::Number(
284 left.checked_rem(right)
285 .expect("remainder overflow handled above"),
286 )
287 }
288 }
289 IntrinsicOp::Equal => Value::Bool(left == right),
290 IntrinsicOp::Less
291 | IntrinsicOp::LessOrEqual
292 | IntrinsicOp::Greater
293 | IntrinsicOp::GreaterOrEqual => {
294 let ordering = left.cmp(&right);
295 Value::Bool(match primitive {
296 IntrinsicOp::Less => ordering == std::cmp::Ordering::Less,
297 IntrinsicOp::LessOrEqual => ordering != std::cmp::Ordering::Greater,
298 IntrinsicOp::Greater => ordering == std::cmp::Ordering::Greater,
299 IntrinsicOp::GreaterOrEqual => ordering != std::cmp::Ordering::Less,
300 _ => unreachable!(),
301 })
302 }
303 };
304 Ok(result)
305}
306
307#[cfg(test)]
308mod primitive_tests {
309 use super::{apply_binary_intrinsic, apply_intrinsic_name, IntrinsicOp};
310 use crate::core::Value;
311
312 #[test]
313 fn compiler_aliases_keep_modulo_named_and_percent_unbound() {
314 assert_eq!(IntrinsicOp::from_symbol("%"), None);
315 assert_eq!(IntrinsicOp::from_symbol("mod"), Some(IntrinsicOp::Modulo));
316 assert_eq!(IntrinsicOp::from_symbol("+"), Some(IntrinsicOp::Add));
317 assert_eq!(IntrinsicOp::from_symbol("-"), Some(IntrinsicOp::Subtract));
318 }
319
320 #[test]
321 fn mod_and_remainder_keep_the_dividend_sign() {
322 assert_eq!(
323 apply_intrinsic_name("mod", &[Value::Number(-7), Value::Number(3)]).unwrap(),
324 Value::Number(-1)
325 );
326 assert_eq!(
327 apply_intrinsic_name("mod", &[Value::Number(7), Value::Number(-3)]).unwrap(),
328 Value::Number(1)
329 );
330 assert_eq!(
331 apply_binary_intrinsic(
332 IntrinsicOp::Remainder,
333 &Value::Number(-7),
334 &Value::Number(3),
335 )
336 .unwrap(),
337 Value::Number(-1)
338 );
339 }
340}
341
342fn bit_values(op: &str, values: &[Value]) -> Result<Value, String> {
343 let op = match op.strip_prefix("std.native.Bits/").unwrap_or(op) {
344 "and" => "bit-and",
345 "or" => "bit-or",
346 "xor" => "bit-xor",
347 "not" => "bit-not",
348 "shift-left" => "bit-shift-left",
349 "shift-right" => "bit-shift-right",
350 operation => operation,
351 };
352 match op {
353 "bit-not" => {
354 if values.len() != 1 {
355 return Err("bit-not expects one integer".into());
356 }
357 numeric::bit_not(&values[0]).map_err(|_| "bit-not expects one integer".to_string())
358 }
359 "bit-and" | "bit-or" | "bit-xor" => {
360 if values.len() != 2 {
361 return Err(format!("{op} expects two integers"));
362 }
363 numeric::bit_binary(op, &values[0], &values[1]).map_err(|error| {
364 if error == "expected an integer" {
365 format!("{op} expects integers")
366 } else {
367 error
368 }
369 })
370 }
371 "bit-shift-left" | "bit-shift-right" => {
372 if values.len() != 2 {
373 return Err(format!("{op} expects an integer and distance"));
374 }
375 numeric::bit_shift(op == "bit-shift-left", &values[0], &values[1])
376 }
377 _ => Err(format!("unknown bit operation: {op}")),
378 }
379}
380
381pub(crate) fn number_conversion_value(operation: &str, value: Value) -> Result<Value, String> {
382 let operation = operation
383 .strip_prefix("std.native.Num/")
384 .unwrap_or(operation);
385 match operation {
386 "long" => Ok(Value::Number(
387 numeric::to_i64_truncating(&value).map_err(|error| format!("long: {error}"))?,
388 )),
389 "double" => Ok(Value::Float(
390 numeric::to_f64_explicit(&value).map_err(|error| format!("double: {error}"))?,
391 )),
392 "parse-long" => match value {
393 Value::String(value) if !value.is_empty() && value.trim() == value => Ok(value
394 .parse::<i64>()
395 .map(Value::Number)
396 .unwrap_or(Value::Nil)),
397 Value::String(_) => Ok(Value::Nil),
398 _ => Err("parse-long expects a string".into()),
399 },
400 "parse-double" => match value {
401 Value::String(value) if !value.is_empty() && value.trim() == value => {
402 if matches!(
403 value.as_str(),
404 "NaN" | "Infinity" | "+Infinity" | "-Infinity"
405 ) {
406 return Err("non-finite number".into());
407 }
408 if !decimal_double_text(&value) {
409 return Ok(Value::Nil);
410 }
411 let parsed = value.parse::<f64>().map_err(|_| "non-finite number")?;
412 Ok(Value::Float(numeric::finite_float(parsed)?))
413 }
414 Value::String(_) => Ok(Value::Nil),
415 _ => Err("parse-double expects a string".into()),
416 },
417 _ => Err(format!("unknown number conversion: {operation}")),
418 }
419}
420
421fn decimal_double_text(value: &str) -> bool {
422 let bytes = value.as_bytes();
423 let mut index = usize::from(matches!(bytes.first(), Some(b'+') | Some(b'-')));
424 let mut digits = 0usize;
425 while matches!(bytes.get(index), Some(b'0'..=b'9')) {
426 digits += 1;
427 index += 1;
428 }
429 if bytes.get(index) == Some(&b'.') {
430 index += 1;
431 while matches!(bytes.get(index), Some(b'0'..=b'9')) {
432 digits += 1;
433 index += 1;
434 }
435 }
436 if digits == 0 {
437 return false;
438 }
439 if matches!(bytes.get(index), Some(b'e') | Some(b'E')) {
440 index += 1;
441 if matches!(bytes.get(index), Some(b'+') | Some(b'-')) {
442 index += 1;
443 }
444 let exponent_start = index;
445 while matches!(bytes.get(index), Some(b'0'..=b'9')) {
446 index += 1;
447 }
448 if index == exponent_start {
449 return false;
450 }
451 }
452 index == bytes.len()
453}
454
455fn numeric_to_f64(value: &Value, operation: &str) -> Result<f64, String> {
456 numeric::to_f64_explicit(value).map_err(|error| format!("{operation}: {error}"))
457}
458
459fn numeric_abs(value: Value) -> Result<Value, String> {
460 numeric::numeric_abs(&value).map_err(|_| "abs expects a numeric value".to_string())
461}
462
463fn math_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
464 let operation = operation
465 .strip_prefix("std.native.Maths/")
466 .unwrap_or(operation);
467 let expected = if matches!(operation, "atan2" | "pow") {
468 2
469 } else {
470 1
471 };
472 if values.len() != expected {
473 return Err(format!(
474 "{operation} expects {} numeric {}",
475 if expected == 1 { "one" } else { "two" },
476 if expected == 1 { "value" } else { "values" }
477 ));
478 }
479 if operation == "abs" {
480 return numeric_abs(values.into_iter().next().unwrap());
481 }
482 let first = numeric_to_f64(&values[0], operation)?;
483 let result = match operation {
484 "acos" => first.acos(),
485 "acosh" => first.acosh(),
486 "asin" => first.asin(),
487 "asinh" => first.asinh(),
488 "atan" => first.atan(),
489 "atan2" => first.atan2(numeric_to_f64(&values[1], operation)?),
490 "atanh" => first.atanh(),
491 "ceil" => first.ceil(),
492 "cos" => first.cos(),
493 "cosh" => first.cosh(),
494 "exp" => first.exp(),
495 "floor" => first.floor(),
496 "pow" => first.powf(numeric_to_f64(&values[1], operation)?),
497 "sin" => first.sin(),
498 "sinh" => first.sinh(),
499 "sqrt" => first.sqrt(),
500 "tan" => first.tan(),
501 "tanh" => first.tanh(),
502 _ => return Err(format!("unknown math operation: {operation}")),
503 };
504 Ok(Value::Float(numeric::finite_float(result)?))
505}
506
507#[derive(Clone, Debug)]
508enum DocumentOp {
509 Text(String, usize),
510 Pass(String),
511 Escaped(String),
512 Line(String, String),
513 Break,
514 Begin(usize),
515 End,
516 Nest(i64),
517 Align(i64),
518 Outdent,
519}
520
521#[derive(Clone)]
522enum DocumentTask {
523 Visit(Value),
524 Emit(DocumentOp),
525}
526
527fn document_tag(name: &str, children: Vec<Value>) -> Result<Value, String> {
528 let mut values = Vec::with_capacity(children.len() + 1);
529 values.push(Value::Keyword(Keyword::parse(name)?));
530 values.extend(children);
531 Ok(Value::Vector(values.into()))
532}
533
534fn document_values(value: &Value) -> Option<Vec<Value>> {
535 match value {
536 Value::Vector(values) => Some(values.iter().cloned().collect()),
537 Value::Tuple(values) => Some(values.iter().cloned().collect()),
538 Value::List(values) => Some(values.iter().cloned().collect()),
539 Value::Cons(values) => Some(values.iter().collect()),
540 _ => None,
541 }
542}
543
544fn document_text(values: &[Value], operation: &str) -> Result<String, String> {
545 let mut output = String::new();
546 for value in values {
547 match value {
548 Value::String(text) => output.push_str(text),
549 Value::Character(character) => output.push(*character),
550 _ => {
551 return Err(format!(
552 "std.native.Document/{operation} expects text values"
553 ))
554 }
555 }
556 }
557 Ok(output)
558}
559
560fn document_offset(values: &[Value], fallback: i64) -> (i64, &[Value]) {
561 match values.first() {
562 Some(Value::Number(offset)) => (*offset, &values[1..]),
563 _ => (fallback, values),
564 }
565}
566
567fn push_document_children(stack: &mut Vec<DocumentTask>, values: &[Value]) {
568 for child in values.iter().rev() {
569 stack.push(DocumentTask::Visit(child.clone()));
570 }
571}
572
573fn serialize_document(document: &Value) -> Result<Vec<DocumentOp>, String> {
574 let mut stack = vec![DocumentTask::Visit(document.clone())];
575 let mut operations = Vec::new();
576 while let Some(task) = stack.pop() {
577 match task {
578 DocumentTask::Emit(operation) => operations.push(operation),
579 DocumentTask::Visit(Value::Nil) => {}
580 DocumentTask::Visit(Value::String(text)) => {
581 let width = text.chars().count();
582 operations.push(DocumentOp::Text(text, width));
583 }
584 DocumentTask::Visit(Value::Keyword(tag))
585 if matches!(tag.as_str(), "line" | "document/line") =>
586 {
587 operations.push(DocumentOp::Line(" ".into(), "".into()));
588 }
589 DocumentTask::Visit(value) => {
590 let values = document_values(&value)
591 .ok_or_else(|| "Document expects strings or element vectors".to_string())?;
592 if values.is_empty() {
593 continue;
594 }
595 let tag = match &values[0] {
596 Value::Keyword(tag) => tag.as_str(),
597 _ => {
598 push_document_children(&mut stack, &values);
599 continue;
600 }
601 };
602 let body = &values[1..];
603 match tag {
604 "text" | "document/text" => {
605 let text = document_text(body, "text")?;
606 let width = text.chars().count();
607 operations.push(DocumentOp::Text(text, width));
608 }
609 "pass" | "document/pass" => {
610 operations.push(DocumentOp::Pass(document_text(body, "pass")?));
611 }
612 "escaped" | "document/escaped" => {
613 if body.len() != 1 {
614 return Err("std.native.Document/escaped expects one string".into());
615 }
616 operations.push(DocumentOp::Escaped(document_text(body, "escaped")?));
617 }
618 "span" | "document/span" | "document/fragment" => {
619 push_document_children(&mut stack, body);
620 }
621 "annotate" | "document/annotate" => {
622 if body.is_empty() {
623 return Err("std.native.Document/annotate expects an annotation".into());
624 }
625 push_document_children(&mut stack, &body[1..]);
626 }
627 "line" | "document/line" => {
628 if body.len() > 2 {
629 return Err(
630 "std.native.Document/line expects optional inline and terminate text"
631 .into(),
632 );
633 }
634 let inline = if body.is_empty() {
635 " ".into()
636 } else {
637 document_text(&body[..1], "line")?
638 };
639 let terminate = if body.len() < 2 {
640 "".into()
641 } else {
642 document_text(&body[1..2], "line")?
643 };
644 operations.push(DocumentOp::Line(inline, terminate));
645 }
646 "break" | "document/break" => {
647 if !body.is_empty() {
648 return Err("std.native.Document/break expects no arguments".into());
649 }
650 operations.push(DocumentOp::Break);
651 }
652 "group" | "document/group" => {
653 stack.push(DocumentTask::Emit(DocumentOp::End));
654 push_document_children(&mut stack, body);
655 stack.push(DocumentTask::Emit(DocumentOp::Begin(0)));
656 }
657 "nest" | "document/nest" => {
658 let (offset, children) = document_offset(body, 2);
659 stack.push(DocumentTask::Emit(DocumentOp::Outdent));
660 push_document_children(&mut stack, children);
661 stack.push(DocumentTask::Emit(DocumentOp::Nest(offset)));
662 }
663 "align" | "document/align" => {
664 let (offset, children) = document_offset(body, 0);
665 stack.push(DocumentTask::Emit(DocumentOp::Outdent));
666 push_document_children(&mut stack, children);
667 stack.push(DocumentTask::Emit(DocumentOp::Align(offset)));
668 }
669 _ => {
670 return Err(format!(
671 "Document text renderer does not support element tag :{tag}"
672 ))
673 }
674 }
675 }
676 }
677 }
678 Ok(operations)
679}
680
681fn annotate_document_groups(operations: &mut [DocumentOp]) -> Result<Vec<usize>, String> {
682 let mut right = 0usize;
683 let mut rights = Vec::with_capacity(operations.len());
684 let mut groups = Vec::new();
685 for index in 0..operations.len() {
686 let operation = operations[index].clone();
687 match operation {
688 DocumentOp::Text(_, width) => right = right.saturating_add(width),
689 DocumentOp::Escaped(_) => right = right.saturating_add(1),
690 DocumentOp::Line(inline, _) => right = right.saturating_add(inline.chars().count()),
691 DocumentOp::Begin(_) => groups.push(index),
692 DocumentOp::End => {
693 let begin = groups
694 .pop()
695 .ok_or_else(|| "Document contains an unmatched group end".to_string())?;
696 operations[begin] = DocumentOp::Begin(right);
697 }
698 _ => {}
699 }
700 rights.push(right);
701 }
702 if !groups.is_empty() {
703 return Err("Document contains an unmatched group begin".into());
704 }
705 Ok(rights)
706}
707
708fn render_document_text(document: &Value, width: usize) -> Result<String, String> {
709 let mut operations = serialize_document(document)?;
710 let rights = annotate_document_groups(&mut operations)?;
711 let mut output = String::new();
712 let mut fits = 0usize;
713 let mut length = width;
714 let mut tabs = vec![0i64];
715 let mut column = 0i64;
716 for (index, operation) in operations.into_iter().enumerate() {
717 let indent = *tabs.last().unwrap_or(&0);
718 match operation {
719 DocumentOp::Text(text, visible) => {
720 if column == 0 && indent > 0 {
721 output.push_str(&" ".repeat(indent as usize));
722 column += indent;
723 }
724 output.push_str(&text);
725 column += visible as i64;
726 }
727 DocumentOp::Escaped(text) => {
728 if column == 0 && indent > 0 {
729 output.push_str(&" ".repeat(indent as usize));
730 column += indent;
731 }
732 output.push_str(&text);
733 column += 1;
734 }
735 DocumentOp::Pass(text) => output.push_str(&text),
736 DocumentOp::Line(inline, terminate) => {
737 if fits == 0 {
738 output.push_str(&terminate);
739 output.push('\n');
740 column = 0;
741 length = rights[index]
742 .saturating_add(width)
743 .saturating_sub(indent.max(0) as usize);
744 } else {
745 column += inline.chars().count() as i64;
746 output.push_str(&inline);
747 }
748 }
749 DocumentOp::Break => {
750 output.push('\n');
751 column = 0;
752 length = rights[index]
753 .saturating_add(width)
754 .saturating_sub(indent.max(0) as usize);
755 }
756 DocumentOp::Nest(offset) => tabs.push(indent + offset),
757 DocumentOp::Align(offset) => tabs.push(column + offset),
758 DocumentOp::Outdent => {
759 if tabs.len() == 1 {
760 return Err("Document contains an unmatched outdent".into());
761 }
762 tabs.pop();
763 }
764 DocumentOp::Begin(end) => {
765 fits = if fits > 0 {
766 fits + 1
767 } else if end <= length {
768 1
769 } else {
770 0
771 };
772 }
773 DocumentOp::End => fits = fits.saturating_sub(1),
774 }
775 }
776 if tabs.len() != 1 {
777 return Err("Document contains an unmatched indentation scope".into());
778 }
779 Ok(output)
780}
781
782fn document_map_option(options: &Value, name: &str) -> Option<Value> {
783 let key = Value::Keyword(Keyword::parse(name).ok()?);
784 map_entries(options)?
785 .into_iter()
786 .find_map(|(candidate, value)| (candidate == key).then_some(value))
787}
788
789fn document_operation(operation: &str, values: Vec<Value>) -> Result<Value, String> {
790 let operation = operation
791 .strip_prefix("std.native.Document/")
792 .unwrap_or(operation);
793 match operation {
794 "element" => {
795 if values.is_empty() || !matches!(values[0], Value::Keyword(_)) {
796 return Err("std.native.Document/element expects a keyword tag".into());
797 }
798 Ok(Value::Vector(values.into()))
799 }
800 "text" => Ok(Value::String(document_text(&values, "text")?)),
801 "fragment" | "group" | "pass" => document_tag(&format!("document/{operation}"), values),
802 "annotate" => {
803 if values.is_empty() {
804 return Err("std.native.Document/annotate expects an annotation".into());
805 }
806 document_tag("document/annotate", values)
807 }
808 "escaped" => {
809 if values.len() != 1 || !matches!(values[0], Value::String(_)) {
810 return Err("std.native.Document/escaped expects one string".into());
811 }
812 document_tag("document/escaped", values)
813 }
814 "line" => {
815 if values.len() > 2
816 || values
817 .iter()
818 .any(|value| !matches!(value, Value::String(_)))
819 {
820 return Err(
821 "std.native.Document/line expects optional inline and terminate strings".into(),
822 );
823 }
824 document_tag("document/line", values)
825 }
826 "break" => {
827 if !values.is_empty() {
828 return Err("std.native.Document/break expects no arguments".into());
829 }
830 document_tag("document/break", values)
831 }
832 "nest" | "align" => document_tag(&format!("document/{operation}"), values),
833 "normalize" => {
834 if values.len() != 1 {
835 return Err("std.native.Document/normalize expects one document".into());
836 }
837 serialize_document(&values[0])?;
838 Ok(values[0].clone())
839 }
840 "valid?" => {
841 if values.len() != 1 {
842 return Err("std.native.Document/valid? expects one value".into());
843 }
844 Ok(Value::Bool(serialize_document(&values[0]).is_ok()))
845 }
846 "render" => {
847 if !(1..=2).contains(&values.len()) {
848 return Err(
849 "std.native.Document/render expects a document and optional options map".into(),
850 );
851 }
852 let default_options = Value::Map(PMap::new());
853 let options = values.get(1).unwrap_or(&default_options);
854 if map_entries(options).is_none() {
855 return Err("std.native.Document/render expects an options map".into());
856 }
857 match document_map_option(options, "format") {
858 None => {}
859 Some(Value::Keyword(value)) if value.as_str() == "text" => {}
860 _ => return Err("std.native.Document/render currently supports only :text".into()),
861 }
862 let width = match document_map_option(options, "width") {
863 None => 80usize,
864 Some(Value::Number(value)) if value >= 0 => value as usize,
865 Some(_) => {
866 return Err(
867 "std.native.Document/render width must be a non-negative integer".into(),
868 )
869 }
870 };
871 Ok(Value::String(render_document_text(&values[0], width)?))
872 }
873 _ => Err(format!("unknown Document operation: {operation}")),
874 }
875}
876
877fn result_context(value: Option<Value>) -> Result<Value, String> {
878 let context = value.unwrap_or_else(|| Value::Map(PMap::new()));
879 map_entries(&context)
880 .is_some()
881 .then_some(context)
882 .ok_or_else(|| "Result context must be a map".into())
883}
884
885fn result_synchronize_options(options: Option<Value>) -> Result<(Option<u64>, Value), String> {
886 let Some(options) = options else {
887 return Ok((None, Value::Map(PMap::new())));
888 };
889 if map_entries(&options).is_none() {
890 return Err("std.native.Result/synchronize expects an options map".into());
891 }
892 let timeout_key = Value::Keyword(Keyword::from("timeout"));
893 let context_key = Value::Keyword(Keyword::from("context"));
894 let timeout = match map_value(&options, &timeout_key) {
895 None | Some(Value::Nil) => None,
896 Some(value) => Some(
897 value_u64_integer(value, "std.native.Result/synchronize").map_err(|_| {
898 "std.native.Result/synchronize timeout must be a non-negative integer".to_string()
899 })?,
900 ),
901 };
902 let context = result_context(map_value(&options, &context_key).cloned())?;
903 Ok((timeout, context))
904}
905
906fn native_result_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
907 let operation = operation
908 .strip_prefix("std.native.Result/")
909 .unwrap_or(operation);
910 match operation {
911 "create" => {
912 if !(2..=3).contains(&values.len()) {
913 return Err(
914 "std.native.Result/create expects status, value, and optional context".into(),
915 );
916 }
917 let status = values[0].clone();
918 let value = values[1].clone();
919 let context = result_context(values.get(2).cloned())?;
920 match status {
921 Value::Keyword(status) if status.as_str() == "success" => Ok(Value::Result(
922 Rc::new(ResultValue::success(value, context)?),
923 )),
924 Value::Keyword(status) if status.as_str() == "error" => {
925 Ok(Value::Result(Rc::new(ResultValue::error(value, context)?)))
926 }
927 _ => Err("std.native.Result/create status must be :success or :error".into()),
928 }
929 }
930 "synchronize" => {
931 if !(1..=2).contains(&values.len()) {
932 return Err(
933 "std.native.Result/synchronize expects a value and optional options map".into(),
934 );
935 }
936 let value = values[0].clone();
937 let options = values.get(1).cloned();
938 let (timeout, context) = result_synchronize_options(options)?;
939 native_result::synchronize_value(value, timeout, context)
940 }
941 "success?" | "error?" | "status" | "data" | "error-value" | "context" => {
942 if values.len() != 1 {
943 return Err(format!("std.native.Result/{operation} expects one value"));
944 }
945 let value = values[0].clone();
946 let Value::Result(result) = value else {
947 if matches!(operation, "success?" | "error?") {
948 return Ok(Value::Bool(false));
949 }
950 return Err(format!("std.native.Result/{operation} expects a Result"));
951 };
952 Ok(match operation {
953 "success?" => Value::Bool(result.is_success()),
954 "error?" => Value::Bool(result.is_error()),
955 "status" => result.status_value(),
956 "data" => result.data.clone(),
957 "error-value" => result.error_value(),
958 "context" => {
959 if map_entries(&result.context).is_some_and(|entries| entries.is_empty()) {
960 Value::Nil
961 } else {
962 result.context.clone()
963 }
964 }
965 _ => unreachable!(),
966 })
967 }
968 "with-context" => {
969 if values.len() != 2 {
970 return Err("std.native.Result/with-context expects a Result and context".into());
971 }
972 let value = values[0].clone();
973 let Value::Result(result) = value else {
974 return Err("std.native.Result/with-context expects a Result".into());
975 };
976 let context = values[1].clone();
977 Ok(Value::Result(Rc::new(result.with_context(context)?)))
978 }
979 _ => Err(format!("unknown std.native.Result operation: {operation}")),
980 }
981}
982
983fn native_exception_values(operation: &str, values: Vec<Value>) -> Result<Value, String> {
984 let operation = operation
985 .strip_prefix("std.native.Exception/")
986 .unwrap_or(operation);
987 match operation {
988 "new" => {
989 if !(2..=3).contains(&values.len()) {
990 return Err(
991 "std.native.Exception/new expects a message, data map, and optional cause"
992 .into(),
993 );
994 }
995 let Value::String(message) = &values[0] else {
996 return Err("std.native.Exception/new expects a string message".into());
997 };
998 if map_entries(&values[1]).is_none() {
999 return Err("std.native.Exception/new expects a data map".into());
1000 }
1001 Ok(Value::ExceptionInfo(Rc::new(ExceptionInfo {
1002 message: message.clone(),
1003 data: Box::new(values[1].clone()),
1004 cause: values.get(2).cloned().map(Box::new),
1005 provenance: Rc::new(RefCell::new(Default::default())),
1006 })))
1007 }
1008 "message" => {
1009 if values.len() != 1 {
1010 return Err("std.native.Exception/message expects one value".into());
1011 }
1012 Ok(match &values[0] {
1013 Value::ExceptionInfo(value) => Value::String(value.message.clone()),
1014 Value::String(value) => Value::String(value.clone()),
1015 value => Value::String(value.display()),
1016 })
1017 }
1018 "class" => {
1019 if values.len() != 1 {
1020 return Err("std.native.Exception/class expects one value".into());
1021 }
1022 Ok(Value::String(portable_type_name(&values[0]).into()))
1023 }
1024 _ => Err(format!("unknown native exception operation: {operation}")),
1025 }
1026}
1027
1028fn value_index(value: &Value) -> Result<usize, String> {
1029 numeric::to_usize_exact(value)
1030 .map_err(|_| "index must be a non-negative host-sized integer".into())
1031}
1032
1033fn value_u64_integer(value: &Value, operation: &str) -> Result<u64, String> {
1034 numeric::to_u64_exact(value)
1035 .map_err(|_| format!("{operation} expects a non-negative 64-bit integer"))
1036}
1037
1038fn value_u16_integer(value: &Value, operation: &str, allow_zero: bool) -> Result<u16, String> {
1039 let value =
1040 numeric::to_u16_exact(value).map_err(|_| format!("{operation} expects a valid port"))?;
1041 if !allow_zero && value == 0 {
1042 return Err(format!("{operation} expects a valid port"));
1043 }
1044 Ok(value)
1045}