1//! Evaluator semantics tests (§4).
4use j::domain::MemBackend;
6use j::parse::parse_expr;
7use j::value::{value_eq, Env, Value};
10const CONFIG: &str = include_str!("../config.j");
12fn make_interp() -> (Interp, config::Config) {
13 let cfg = config::load_config(CONFIG).expect("reference config must load");
14 let backend = Rc::new(MemBackend::new());
15 let mut interp = Interp::new(backend, cfg.shapes.clone(), Env::empty());
16 config::eval_config(&mut interp, &cfg).expect("reference config must evaluate");
20fn ev(interp: &mut Interp, cfg: &config::Config, src: &str) -> Result<Value, String> {
21 let outer = Rc::new(cfg.global_names.clone());
22 let e = parse_expr(src, outer).map_err(|p| format!("parse: {}", p.msg))?;
23 let e = config::resolve_ids(&e, interp).map_err(|_| "id resolution".to_string())?;
24 let env = interp.global_env();
26 .eval(&Rc::new(e), &env)
27 .map_err(|c| format!("crash: {}", c.msg))
30fn ok(interp: &mut Interp, cfg: &config::Config, src: &str) -> Value {
31 ev(interp, cfg, src).unwrap_or_else(|e| panic!("{} => {}", src, e))
34fn crash(interp: &mut Interp, cfg: &config::Config, src: &str) -> String {
35 match ev(interp, cfg, src) {
36 Ok(v) => panic!("{} unexpectedly succeeded: {}", src, j::show::show(interp, &v).unwrap()),
42 ($i:expr, $cfg:expr, $src:expr, $want:expr) => {{
43 let got = ok(&mut $i, &$cfg, $src);
45 value_eq(&got, &$want).unwrap_or(false),
48 j::show::show(&$i, &got).unwrap(),
49 j::show::show(&$i, &$want).unwrap()
55fn or_non_function_lhs_skips_rhs() {
56 let (mut i, cfg) = make_interp();
57 // rhs would crash; not evaluated
58 check!(i, cfg, "1 or head []", Value::int(1));
59 check!(i, cfg, "\"x\" or crash \"no\"", Value::text("x"));
60 // lhs crashes: rhs returned
61 check!(i, cfg, "head [] or 9", Value::int(9));
65fn or_function_lifting() {
66 let (mut i, cfg) = make_interp();
67 // (f or g) x = f x or g x
71 "(head or (\\_ -> 5)) []",
77 "(head or (\\_ -> 5)) [7]",
80 // same rule applies again to the result
84 "((\\x -> head x or 0) or (\\_ -> 9)) []",
87 // ... including lifting again when `f x` is a function, so the fallback
88 // still guards the later arguments of a curried lhs
92 "((\\a b -> head b) or (\\a b -> 5)) 1 []",
98 "((\\a b c -> head c) or (\\a b c -> 7)) 1 2 []",
104 "((\\a b -> a) or (\\a b -> 5)) 1 []",
107 // `f x` a function and `g x` not: `f x` alone, no longer guarded
108 let m = crash(&mut i, &cfg, "((\\a b -> head b) or (\\a -> 5)) 1 []");
109 assert!(m.contains("head: empty list"), "{}", m);
110 // `g x` is evaluated unguarded, as the rhs of a written `or` is
111 let m = crash(&mut i, &cfg, "((\\a b -> b) or (\\a -> crash \"rhs\")) 1 2");
112 assert!(m.contains("rhs"), "{}", m);
113 // lhs function, rhs not a function: lhs returned
114 let v = ok(&mut i, &cfg, "head or 3");
115 assert!(matches!(v, Value::Fun(_)), "expected a function");
119fn short_circuit_booleans() {
120 let (mut i, cfg) = make_interp();
121 check!(i, cfg, "false && crash \"x\"", Value::Bool(false));
122 check!(i, cfg, "true || crash \"x\"", Value::Bool(true));
123 check!(i, cfg, "true && true", Value::Bool(true));
124 check!(i, cfg, "false || false", Value::Bool(false));
125 assert!(crash(&mut i, &cfg, "1 && true").contains("Bool"));
126 // rhs of || is not evaluated when lhs is true; non-Bool rhs crashes when reached
127 assert!(crash(&mut i, &cfg, "false || 1").contains("Bool"));
131fn if_requires_bool() {
132 let (mut i, cfg) = make_interp();
133 check!(i, cfg, "if 1 == 1 then \"a\" else \"b\"", Value::text("a"));
134 let m = crash(&mut i, &cfg, "if [1] then 1 else 2");
135 assert!(m.contains("Bool"), "{}", m);
139fn let_is_recursive() {
140 let (mut i, cfg) = make_interp();
141 // mutual recursion within one block
145 "let even = \\n -> if n == 0 then true else odd (n - 1); odd = \\n -> if n == 0 then false else even (n - 1) in even 10",
148 // later binding sees earlier
149 check!(i, cfg, "let x = 1; y = x + 1 in y", Value::int(2));
153fn let_bindings_evaluate_in_dependency_order() {
154 let (mut i, cfg) = make_interp();
155 // §4.1: a binding is evaluated after every binding it refers to outside
156 // a lambda, whatever order the block lists them in
157 check!(i, cfg, "let y = x + 1; x = 1 in y", Value::int(2));
158 check!(i, cfg, "let a = b; b = 5 in a", Value::int(5));
159 check!(i, cfg, "let a = b + 1; b = c + 1; c = 10 in a", Value::int(12));
160 check!(i, cfg, "let z = f 2; f = \\x -> x * 3 in z", Value::int(6));
161 check!(i, cfg, "let\n total = base * 2\n base = 21\nin total", Value::int(42));
162 check!(i, cfg, "(\\n -> let y = x + n; x = 1 in y) 5", Value::int(6));
163 // source order made `b` unbound here, a crash `or` caught: 0, not 1
164 check!(i, cfg, "let a = b or 0; b = 1 in a", Value::int(1));
165 // a function applied in a binding finds the bindings its body refers to,
166 // as one applied at load does (§4.1)
167 check!(i, cfg, "let f = \\x -> x * k; z = f 2; k = 3 in z", Value::int(6));
171 "let ev = \\n -> if n == 0 then k else od (n - 1); r = ev 4; od = \\n -> if n == 0 then 0 else ev (n - 1); k = 1 in r",
174 // mutually referring bindings keep the order that references outside
175 // lambdas give them: `b` applies `a`, whose body needs `y`, and `y`
176 // refers back to `b`; `b` waits for `z`, so `y` is bound by then
177 check!(i, cfg, "let a = \\_ -> y; b = a z; y = \\_ -> b; z = 1 in z", Value::int(1));
178 // among them, one that refers to none of the others outside a lambda and
179 // only stores its lambdas that do goes first: `env` is bound before
180 // `out` applies `fmt`, whose body needs it, wherever the block lists it
184 "let fmt = \\x -> env.lanes; out = fmt 1; env = { lanes = 3, again = \\_ -> out } in out",
190 "let fmt = \\x -> env.lanes; out = fmt 1; env = let n = 3 in { lanes = n, again = [(\\_ -> out)] } in out",
193 // a binding that applies such a lambda keeps its place in the first
194 // order: `bm` must still follow `ax`
198 "let aa = \\_ -> bm; ax = { f = aa, n = 1 }; bm = (\\f -> f 0) (\\_ -> ax) in bm.n",
204fn let_cycle_is_a_crash() {
205 let (mut i, cfg) = make_interp();
206 for src in ["let a = b; b = a in a", "let a = a in a", "let a = [b]; b = { x = a } in 1"] {
207 let m = crash(&mut i, &cfg, src);
208 assert!(m.contains("cycle"), "{} => {}", src, m);
210 // an ordinary crash, which `or` catches, and only when the block is
212 check!(i, cfg, "(let a = b + 1; b = a in a) or 7", Value::int(7));
213 check!(i, cfg, "if false then (let a = a in a) else 1", Value::int(1));
214 // references inside lambdas never form a cycle
215 check!(i, cfg, "let f = \\n -> if n == 0 then 0 else f (n - 1) in f 3", Value::int(0));
219fn lexical_scoping() {
220 let (mut i, cfg) = make_interp();
221 // closures capture their environment: `f` sees the `x` it was defined
222 // under, not the `x` bound where it is called (§4.2 forbids shadowing, so
223 // the two `x`s are in disjoint scopes)
227 "let f = (let x = 1 in \\y -> x + y); g = \\x -> f x in g 100",
230 // and keep it after the scope that bound it has returned
234 "let add = \\x -> \\y -> x + y in let inc = add 1 in inc 41",
240fn nonrecursive_local_helpers_release_their_captures() {
241 let (mut i, cfg) = make_interp();
244 "foldl (\\a n -> let step = \\x -> if sentinel == sentinel then x + n else 0 in step a) 0 (range 0 100)",
248 "(let step = \\_ -> sentinel; fail = crash (step 0) in fail) or true",
252 // No global can hold this later-added value. A let cell that owns a
253 // closure capturing itself used to keep the entire environment alive,
254 // even after its scalar result (or caught crash) was discarded.
255 let tracked = Rc::new("tracked environment".to_string());
256 let weak = Rc::downgrade(&tracked);
257 let env = i.global_env().extend(vec![("sentinel".into(), Value::Text(tracked))]);
258 let expr = Rc::new(parse_expr(src, Rc::new(cfg.global_names.clone())).unwrap());
259 let got = i.eval(&expr, &env).unwrap();
260 assert!(value_eq(&got, &want).unwrap());
263 assert!(weak.upgrade().is_none(), "local helper retained its environment: {}", src);
268fn escaping_helpers_keep_their_captures_until_they_are_dropped() {
269 let (mut i, cfg) = make_interp();
270 let tracked = Rc::new("captured".to_string());
271 let weak = Rc::downgrade(&tracked);
272 let env = i.global_env().extend(vec![("sentinel".into(), Value::Text(tracked))]);
273 // The source's forward reference is put after its dependency; the
274 // returned function must still capture that earlier helper.
275 let src = "let second = \\x -> first x; first = \\x -> sentinel in second";
276 let expr = Rc::new(parse_expr(src, Rc::new(cfg.global_names.clone())).unwrap());
277 let f = i.eval(&expr, &env).unwrap();
279 assert!(weak.upgrade().is_some());
280 let got = i.apply(f.clone(), Value::int(0)).unwrap();
281 assert_eq!(got.as_text().unwrap(), "captured");
284 assert!(weak.upgrade().is_none());
286 // A genuinely recursive group still needs its frame after the block's
287 // body returns; clearing its cell at that point would break this call.
291 "let even = \\n -> if n == 0 then true else odd (n - 1); odd = \\n -> if n == 0 then false else even (n - 1) in even",
293 assert!(value_eq(&i.apply(f, Value::int(11)).unwrap(), &Value::Bool(false)).unwrap());
297fn shared_literal_buffers_preserve_caught_crashes_and_evaluation_order() {
298 let (mut i, cfg) = make_interp();
299 check!(i, cfg, "[1 (head [] or 2) 3]", Value::list(vec![Value::int(1), Value::int(2), Value::int(3)]));
300 check!(i, cfg, "[1 (crash \"late\") 3] or [9]", Value::list(vec![Value::int(9)]));
301 let record = Value::record(&[("a", Value::int(1)), ("b", Value::int(2)), ("c", Value::int(3))]);
302 check!(i, cfg, "{ a = 1, b = head [] or 2, c = 3 }", record.clone());
303 check!(i, cfg, "({ a = 1, b = 0, c = 0 }) { b = head [] or 2, c = 3 }", record);
304 check!(i, cfg, "({ a = 1, b = crash \"late\" }) or 9", Value::int(9));
305 check!(i, cfg, "(({ a = 0, b = 0 }) { a = 1, b = crash \"late\" }) or 9", Value::int(9));
307 "[(crash \"first\") (crash \"second\")]",
308 "{ a = crash \"first\", b = crash \"second\" }",
309 "({ a = 0, b = 0 }) { a = crash \"first\", b = crash \"second\" }",
311 assert!(crash(&mut i, &cfg, src).ends_with("first"));
316fn a_large_flat_let_releases_its_frames_without_native_recursion() {
319 let bindings = (0..30_000)
320 .map(|n| (format!("binding{}", n), Rc::new(Expr::Bool(true))))
322 let expr = Rc::new(Expr::Let(bindings, Rc::new(Expr::Bool(true))));
323 let got = Interp::dummy().eval(&expr, &Env::empty()).unwrap();
324 assert!(value_eq(&got, &Value::Bool(true)).unwrap());
329fn unbound_names_crash() {
330 let (mut i, cfg) = make_interp();
331 let m = crash(&mut i, &cfg, "nosuchname");
332 assert!(m.contains("unbound"), "{}", m);
336fn applying_non_function_crashes() {
337 let (mut i, cfg) = make_interp();
338 assert!(crash(&mut i, &cfg, "1 2").contains("apply"));
339 assert!(crash(&mut i, &cfg, "\"x\" 1").contains("apply"));
340 assert!(crash(&mut i, &cfg, "[1] 2").contains("apply"));
344fn record_selection_and_update() {
345 let (mut i, cfg) = make_interp();
346 check!(i, cfg, "{ a = 1 }.a", Value::int(1));
347 let m = crash(&mut i, &cfg, "{ a = 1 }.b");
348 assert!(m.contains("no field"), "{}", m);
349 let m = crash(&mut i, &cfg, "1 .a");
350 assert!(m.contains("select"), "{}", m);
351 // update never adds fields
352 let m = crash(&mut i, &cfg, "({ a = 1 }) { b = 2 }");
353 assert!(m.contains("no field"), "{}", m);
354 // selector function (record argument parenthesised)
355 check!(i, cfg, "(.a) ({ a = 42 })", Value::int(42));
360 let (mut i, cfg) = make_interp();
361 check!(i, cfg, "[1 [2]] == [1 [2]]", Value::Bool(true));
362 check!(i, cfg, "{ a = 1 } == { a = 1 }", Value::Bool(true));
363 check!(i, cfg, "{ a = 1 } == { a = 2 }", Value::Bool(false));
364 check!(i, cfg, "{ a = 1 } == { b = 1 }", Value::Bool(false));
365 check!(i, cfg, "1 /= 2", Value::Bool(true));
366 check!(i, cfg, "blob \"x\" == blob \"x\"", Value::Bool(true));
367 check!(i, cfg, "blob \"x\" == blob \"y\"", Value::Bool(false));
368 // comparing functions crashes
369 let m = crash(&mut i, &cfg, "map == map");
370 assert!(m.contains("function"), "{}", m);
371 // cross-kind is just false
372 check!(i, cfg, "1 == \"1\"", Value::Bool(false));
373 // ordering on non-Ints crashes
374 assert!(crash(&mut i, &cfg, "\"a\" < \"b\"").contains("Int"));
378fn shapes_as_values() {
379 let (mut i, cfg) = make_interp();
380 let v = ok(&mut i, &cfg, "Commit");
381 assert!(matches!(v, Value::Shape(_)));
382 // alias of a record shape works
383 let v = ok(&mut i, &cfg, "Repo");
384 assert!(matches!(v, Value::Shape(_)));
385 // function-shaped typedecls crash when used
386 let m = crash(&mut i, &cfg, "Edit");
387 assert!(m.contains("shape"), "{}", m);
388 let m = crash(&mut i, &cfg, "Path");
389 assert!(m.contains("shape"), "{}", m);
390 let m = crash(&mut i, &cfg, "Undeclared");
391 assert!(m.contains("shape"), "{}", m);
394/// The reference config with `extra` appended, loaded and evaluated.
395fn make_interp_with(extra: &str) -> (Interp, config::Config) {
396 let src = format!("{}\n{}", CONFIG, extra);
397 let cfg = config::load_config(&src).expect("config must load");
398 let mut interp = Interp::new(Rc::new(MemBackend::new()), cfg.shapes.clone(), Env::empty());
399 config::eval_config(&mut interp, &cfg).expect("config must evaluate");
404fn cyclic_alias_is_not_a_usable_shape() {
405 // §4.12, §4.7: an alias that never reaches a type is a catchable crash
406 // when used as a shape. Resolving it recursed until the native stack
407 // overflowed (an abort `or` cannot catch), and so did displaying any
408 // record, since display asks every typedecl for its shape.
409 let (mut i, cfg) = make_interp_with("Self = Self\nA = B\nB = A\n");
410 for name in ["Self", "A", "B"] {
411 let m = crash(&mut i, &cfg, name);
412 assert!(m.contains(&format!("`{}` does not name a usable shape", name)), "{}", m);
414 check!(i, cfg, "Self or 1", Value::int(1));
415 check!(i, cfg, "A or 7", Value::int(7));
416 let m = crash(&mut i, &cfg, "extract Self [1]");
417 assert!(m.contains("usable shape"), "{}", m);
418 let rec = ok(&mut i, &cfg, "{ a = 1 }");
419 assert_eq!(j::render::display(&mut i, &rec, false).unwrap(), "a 1\n");
423fn cyclic_alias_in_a_signature_loads() {
424 // §4.13: an alias of a function type is unfolded into the contract.
425 // Unfolding a cyclic alias never stopped, so every run hung at load; a
426 // self-referring function alias is unfolded once, and its result is
427 // checked as the alias itself.
428 let (mut i, cfg) = make_interp_with(
429 "A = B\nB = A\n\nf : A\nf = 1\n\nStream = Int -> Stream\n\ng : Stream\ng = \\x -> g\n",
431 check!(i, cfg, "f", Value::int(1));
432 let v = ok(&mut i, &cfg, "g 1 2 3");
433 assert!(matches!(v, Value::Fun(_)));
434 let m = crash(&mut i, &cfg, "g 1 \"x\"");
435 assert!(m.contains("g expected Int as argument 1, got Text"), "{}", m);
439fn alias_of_an_undeclared_type_is_not_a_usable_shape() {
440 // §4.12: an alias takes its type's shape, and an undeclared type's shape
441 // is a crash when used. It used to be a shape nothing matched, so
442 // `extract Foo xs` quietly returned [].
443 let (mut i, cfg) = make_interp_with("Foo = Bar\n");
444 let m = crash(&mut i, &cfg, "Foo");
445 assert!(m.contains("`Foo` does not name a usable shape"), "{}", m);
446 let m = crash(&mut i, &cfg, "extract Foo [1 \"a\"]");
447 assert!(m.contains("usable shape"), "{}", m);
450/// Signed definitions whose lambda takes fewer parameters than the signature,
451/// so the body's value is a function the rest of the signature checks (§4.13).
452const DEFERRED: &str = r#"
454failIf = \n -> if n > 0 then id else crash "negative"
456choose : Int -> Int -> Int
457choose = \n -> if n > 0 then (\x -> x) else crash "negative"
459bad : Int -> Int -> Int
460bad = \a -> \b -> "oops"
462bad3 : Int -> Int -> Int -> Int
463bad3 = \a -> \b c -> "oops"
465add3 : Int -> Int -> Int -> Int
466add3 = \a -> \b c -> a + b + c
469const REPO: &str = r#"{ root = { files = [], message = "kept", labels = [], id = @ }, children = [], context = [] }"#;
472fn deferred_body_crash_is_catchable_and_keeps_its_message() {
473 // §4.6, §4.7: a crash in a deferred body is a crash like any other. It
474 // was replaced by "<name>: body crashed when applied" and raised past
475 // every enclosing `or`, so `failIf 0 or id` aborted the whole command.
476 let (mut i, cfg) = make_interp_with(DEFERRED);
477 check!(i, cfg, "choose 0 5 or 7", Value::int(7));
478 check!(i, cfg, "(choose 0 or (\\x -> x + 1)) 5", Value::int(6));
479 check!(i, cfg, "choose 1 5", Value::int(5));
480 let m = crash(&mut i, &cfg, "choose 0 5");
481 assert_eq!(m, "crash: negative");
482 let src = format!("((failIf 0 or id) ({})).root.message", REPO);
483 check!(i, cfg, &src, Value::text("kept"));
484 let src = format!("(\\r -> failIf 0 r or r) ({})", REPO);
485 let v = ok(&mut i, &cfg, &src);
486 assert!(matches!(v, Value::Record(_)));
487 let m = crash(&mut i, &cfg, &format!("failIf 0 ({})", REPO));
488 assert_eq!(m, "crash: negative");
492fn deferred_body_is_contract_checked() {
493 // §4.13: every argument of a signed definition is checked as it is
494 // supplied, and the result once the application is complete. Those that
495 // arrived after the body was deferred went unchecked, and so did the
496 // result: `bad 1 "x"` was "oops".
497 let (mut i, cfg) = make_interp_with(DEFERRED);
498 let m = crash(&mut i, &cfg, "bad 1 \"x\"");
499 assert!(m.contains("contract: bad expected Int as argument 2, got Text"), "{}", m);
500 let m = crash(&mut i, &cfg, "bad 1 2");
501 assert!(m.contains("contract: bad: result expected Int, got Text"), "{}", m);
502 // and a violation is catchable like any other crash
503 check!(i, cfg, "bad 1 2 or 9", Value::int(9));
504 check!(i, cfg, "(bad 1 or (\\_ -> 9)) 2", Value::int(9));
505 let m = crash(&mut i, &cfg, "failIf 1 3");
506 assert!(m.contains("contract: failIf expected Repo"), "{}", m);
507 // with two parameters left once the body is deferred, each of them and
508 // the result are checked too
509 let m = crash(&mut i, &cfg, "bad3 1 \"x\" 3");
510 assert!(m.contains("contract: bad3 expected Int as argument 2, got Text"), "{}", m);
511 let m = crash(&mut i, &cfg, "bad3 1 2 \"y\"");
512 assert!(m.contains("contract: bad3 expected Int as argument 3, got Text"), "{}", m);
513 let m = crash(&mut i, &cfg, "bad3 1 2 3");
514 assert!(m.contains("contract: bad3: result expected Int, got Text"), "{}", m);
515 check!(i, cfg, "add3 1 2 3", Value::int(6));
516 check!(i, cfg, "let f = add3 1 2 in f 3 + f 4", Value::int(13));
517 // the partial application stays a named value (§5.2)
518 check!(i, cfg, "show (add3 1 2)", Value::text("add3 1 2"));
519 // the reference config's own deferred definitions blame themselves
520 let m = crash(&mut i, &cfg, "describe \"x\" 3");
521 assert!(m.contains("contract: describe expected Repo"), "{}", m);
522 assert!(m.contains("argument 2"), "{}", m);
526fn signature_does_not_change_when_a_body_runs() {
527 // §4.1: evaluation is strict and a lambda body is evaluated on each
528 // application; a signature only adds checks (§4.13). A signed definition
529 // whose lambda took fewer parameters than its signature deferred its
530 // body until the signature was used up, so `failIf 0` did not crash,
531 // `failIf 0 or 7` crashed past the `or` (§4.6), and the body ran again
532 // at every later application.
533 let (mut i, cfg) = make_interp_with(&format!(
537failIfU = \n -> if n > 0 then id else crash "negative"
539pick2 : Int -> Int -> Int -> Int
540pick2 = \n -> if n > 0 then (\a b -> a + b) else crash "negative"
545once : Int -> Int -> Id
546once = \n -> let i = @ in \m -> i
548onceU = \n -> let i = @ in \m -> i
551 // the body crashes at the application that binds the lambda's last
552 // parameter, signed or not, and `or` catches it there
553 for f in ["failIf", "failIfU"] {
554 check!(i, cfg, &format!("{} 0 or 7", f), Value::int(7));
555 check!(i, cfg, &format!("show ({} 0 or \"fallback\")", f), Value::text("\"fallback\""));
556 let m = crash(&mut i, &cfg, &format!("let e = {} 0 in 5", f));
557 assert_eq!(m, "crash: negative");
559 check!(i, cfg, "choose 0 or 7", Value::int(7));
560 check!(i, cfg, "pick2 0 or 5", Value::int(5));
561 check!(i, cfg, "pick2 0 1 or 5", Value::int(5));
562 let m = crash(&mut i, &cfg, "let e = pick2 0 1 in 5");
563 assert_eq!(m, "crash: negative");
564 let (m, def) = crash_in(&mut i, &cfg, "failIf 0");
565 assert_eq!((m.as_str(), def.as_deref()), ("negative", Some("failIf")));
566 // it runs once, not again at every later argument
567 for f in ["once", "onceU"] {
568 check!(i, cfg, &format!("let g = {} 1 in g 1 == g 2", f), Value::Bool(true));
570 // the body's value must be a function while the signature lists more
571 let (m, def) = crash_in(&mut i, &cfg, "notFn 1");
572 assert_eq!(m, "contract: notFn expected a function, got Int");
573 assert_eq!(def.as_deref(), Some("notFn"));
574 check!(i, cfg, "notFn 1 or 7", Value::int(7));
575 // the application still renders by name (§5.2), and the arguments the
576 // signature lists past the lambda's, and the result, are still checked
577 check!(i, cfg, "show (pick2 1 2)", Value::text("pick2 1 2"));
578 check!(i, cfg, "show (failIf 1)", Value::text("failIf 1"));
579 check!(i, cfg, "show (describe \"wip\")", Value::text("describe \"wip\""));
583 "show (at (parents) (describe \"x\"))",
584 Value::text("at (parents) (describe \"x\")")
586 check!(i, cfg, "pick2 1 2 3", Value::int(5));
587 check!(i, cfg, "let f = pick2 1 in f 2 3 + f 4 5", Value::int(14));
588 let m = crash(&mut i, &cfg, "pick2 1 \"x\"");
589 assert!(m.contains("contract: pick2 expected Int as argument 2, got Text"), "{}", m);
590 let m = crash(&mut i, &cfg, "pick2 1 2 \"y\"");
591 assert!(m.contains("contract: pick2 expected Int as argument 3, got Text"), "{}", m);
592 let m = crash(&mut i, &cfg, "once 1 \"x\"");
593 assert!(m.contains("contract: once expected Int as argument 2, got Text"), "{}", m);
596/// Definitions that crash at different depths, for the crash trace (§1.4).
597const NESTED: &str = r#"
599inner = \n -> if n > 0 then n else crash "not positive"
602outer = \n -> inner n + 1
605after = \n -> inner n + head []
608caught = \n -> (inner 0 or n) + head []
610viaMap : [Int] -> [Int]
611viaMap = \xs -> map (\x -> x + head []) xs
613mapInner : [Int] -> [Int]
614mapInner = \xs -> map inner xs
619mapped : [Int] -> [Int]
620mapped = map (\x -> x + head [])
622counted : [Int] -> Int
625plain = \n -> if n > 0 then n else crash "not positive"
627plainOuter = \n -> plain n + 1
629plainAfter = \n -> plain n + head []
631plainTail = \n -> if n > 0 then plainTail (n - 1) else plain n
633plainBack = \n -> if n > 0 then plainBack (n - 1) else head []
635signedCallsPlain : Int -> Int
636signedCallsPlain = \n -> plain n
638plainCallsSigned = \n -> inner n
640sumPos : Int -> Int -> Int
641sumPos = \a b -> if b > 0 then a + b else crash "not positive"
646sameAsInner : Int -> Int
650/// The message of the crash `src` raises and the definition it is in.
651fn crash_in(interp: &mut Interp, cfg: &config::Config, src: &str) -> (String, Option<String>) {
652 let outer = Rc::new(cfg.global_names.clone());
653 let e = parse_expr(src, outer).unwrap_or_else(|p| panic!("{}: {}", src, p.msg));
654 let e = config::resolve_ids(&e, interp).unwrap_or_else(|_| panic!("{}: ids", src));
655 let env = interp.global_env();
656 match interp.eval(&Rc::new(e), &env) {
657 Ok(v) => panic!("{} unexpectedly succeeded: {}", src, j::show::show(interp, &v).unwrap()),
658 Err(c) => (c.msg, c.def),
663fn crash_names_the_innermost_definition_executing() {
664 // §1.4, §5.1: a crash reports the innermost definition that was
665 // executing. Nothing recorded one once loading was over, so every crash
666 // at run time reported none and the CLI printed only `from EXPR`.
667 let (mut i, cfg) = make_interp_with(&format!("{}{}", DEFERRED, NESTED));
668 let goto = format!("goto (\\_ -> []) ({})", REPO);
669 let prev = format!("prev ({})", REPO);
670 let next = format!("next ({})", REPO);
672 ("inner 0", Some("inner")),
673 // §5.1's example, on a repository literal
674 (goto.as_str(), Some("goto")),
675 // the definition running, not the one that called it
676 ("outer 0", Some("inner")),
677 // once `inner` has returned it is no longer executing
678 ("after 1", Some("after")),
679 // a crash `or` caught in `inner` does not name the next one
680 ("caught 1", Some("caught")),
681 // a lambda a builtin runs is in the definition that made it; a
682 // definition a builtin runs is itself the innermost
683 ("viaMap [1]", Some("viaMap")),
684 ("mapInner [0]", Some("inner")),
685 // a body whose lambda is shorter than its signature runs in its
686 // definition too (§4.13)
687 ("choose 0 5", Some("choose")),
688 // a contract violation is in the definition checked (§4.13),
689 // whether an argument, one past the lambda's, or the result
690 ("inner \"x\"", Some("inner")),
691 ("outer \"x\"", Some("outer")),
692 ("bad 1 \"x\"", Some("bad")),
693 ("bad 1 2", Some("bad")),
694 ("describe 3", Some("describe")),
695 // a definition whose value is a builtin runs under its own name
696 ("first []", Some("first")),
697 ("first \"x\"", Some("first")),
698 ("squash 3", Some("squash")),
699 // and so do the functions it applies, and its result's check
700 ("mapped [1]", Some("mapped")),
701 ("counted [1]", Some("counted")),
702 // a definition without a signature is executing while its body runs,
703 // as one with a signature is: nothing named one, so `plain 0` and
704 // `plainOuter 0` named none, and `signedCallsPlain 0` named the
705 // signed definition that called `plain`
706 ("plain 0", Some("plain")),
707 ("plainOuter 0", Some("plain")),
708 ("plainAfter 1", Some("plainAfter")),
709 ("plainTail 3", Some("plain")),
710 ("plainBack 3", Some("plainBack")),
711 ("signedCallsPlain 0", Some("plain")),
712 ("signedCallsPlain \"x\"", Some("signedCallsPlain")),
713 ("plainCallsSigned 0", Some("inner")),
714 // a definition whose value is another's function runs that one's
715 // body, the innermost (`prev = goto parents`), where the outer
716 // name was reported; its own signature still checks its arguments
717 ("addOne 0", Some("sumPos")),
718 ("addOne \"x\"", Some("addOne")),
719 ("sameAsInner 0", Some("inner")),
720 ("sameAsInner \"x\"", Some("sameAsInner")),
721 (prev.as_str(), Some("goto")),
722 (next.as_str(), Some("goto")),
723 // a builtin is no definition: outside every definition, none
725 ("crash \"top\"", None),
726 ("(\\x -> x + head []) 1", None),
728 let (msg, def) = crash_in(&mut i, &cfg, src);
729 assert_eq!(def.as_deref(), want, "{} crashed with {}", src, msg);
734fn unsigned_tail_recursion_keeps_its_continuation_constant() {
735 // §1.4, §4.1: a definition's body runs under a frame naming it, and a
736 // call in tail position of that body replaces its frame. A frame kept
737 // per call grew the continuation with every step of an unsigned tail
738 // recursion, which only signed definitions' checks did before, and a
739 // crash at the end dropped the whole chain at once on the native stack.
741 let (mut i, cfg) = make_interp_with(NESTED);
742 let (msg, def) = crash_in(&mut i, &cfg, "plainTail 50000");
743 assert_eq!(def.as_deref(), Some("plain"), "{}", msg);
744 let (msg, def) = crash_in(&mut i, &cfg, "plainBack 50000");
745 assert_eq!(def.as_deref(), Some("plainBack"), "{}", msg);
749/// Signed recursions whose lambdas take fewer parameters than their
750/// signatures, or as many, for the space they take (§4.1, §4.13).
751const SIGNED_TAIL: &str = r#"
752shortS : Int -> Int -> Int
753shortS = \n -> if n == 0 then (\a -> a) else shortS (n - 1)
756countS = \n -> if n == 0 then id else countS (n - 1)
759endS = \n -> if n == 0 then crash "end" else endS (n - 1)
761endShortS : Int -> Int -> Int
762endShortS = \n -> if n == 0 then crash "end" else endShortS (n - 1)
764letS : Int -> Int -> Int
765letS = \n -> if n == 0 then (\a -> a) else (let r = letS (n - 1) in r)
767evenS : Int -> Int -> Int
768evenS = \n -> if n == 0 then (\a -> a) else oddS (n - 1)
770oddS : Int -> Int -> Int
771oddS = \n -> if n == 0 then (\a -> a) else evenS (n - 1)
773endEvenS : Int -> Int -> Int
774endEvenS = \n -> if n == 0 then crash "end" else endOddS (n - 1)
776endOddS : Int -> Int -> Int
777endOddS = \n -> if n == 0 then crash "end" else endEvenS (n - 1)
781fn signed_tail_recursion_keeps_its_continuation_constant() {
782 // §4.1, §4.13: a signature only adds checks, so a signed tail recursion
783 // takes the space of its unsigned twin. Every call of `shortS` pushed
784 // its own check and naming under the caller's, and the value came back
785 // as `\x -> f x` wrapped once per call: holding it took a gigabyte at a
786 // million calls, and dropping it recursed once per wrapper on the
787 // native stack and aborted the process. A crash at the end of such a
788 // chain dropped the continuation at once, the same way. A recursion
789 // that is not a tail call, or goes through another signed definition,
790 // still returns through every call, but its value drops as any other,
791 // and so does its continuation when a crash abandons it.
793 let (mut i, cfg) = make_interp_with(SIGNED_TAIL);
795 check!(i, cfg, &format!("let f = shortS {n} in 1"), Value::int(1));
796 check!(i, cfg, &format!("let e = countS {n} in 1"), Value::int(1));
797 check!(i, cfg, &format!("show (shortS {n})"), Value::text(format!("shortS {n}")));
798 check!(i, cfg, &format!("shortS {n} 7"), Value::int(7));
799 for f in ["endS", "endShortS"] {
800 let (msg, def) = crash_in(&mut i, &cfg, &format!("{f} {n}"));
801 assert_eq!((msg.as_str(), def.as_deref()), ("end", Some(f)));
803 for f in ["letS", "evenS"] {
804 check!(i, cfg, &format!("let g = {f} {n} in 1"), Value::int(1));
805 check!(i, cfg, &format!("{f} {n} 7"), Value::int(7));
807 let (msg, def) = crash_in(&mut i, &cfg, &format!("endEvenS {n}"));
808 assert_eq!((msg.as_str(), def.as_deref()), ("end", Some("endEvenS")));
809 check!(i, cfg, &format!("endEvenS {n} or 5"), Value::int(5));
810 // the checks the frames stood for still hold
811 let m = crash(&mut i, &cfg, &format!("shortS {n} \"x\""));
812 assert!(m.contains("contract: shortS expected Int as argument 2, got Text"), "{}", m);
813 let m = crash(&mut i, &cfg, &format!("countS {n} 5"));
814 assert!(m.contains("contract: countS expected Repo (record) as argument 2"), "{}", m);
819fn new_id_mints_distinct_ids() {
820 let (mut i, cfg) = make_interp();
821 check!(i, cfg, "@ /= @", Value::Bool(true));
822 // in a lambda body, each application mints
826 "let f = \\_ -> @ in f 1 /= f 1",
829 // two mints in one expression are distinct
830 check!(i, cfg, "@ == @", Value::Bool(false));
834fn label_literals_are_revsets() {
835 let (mut i, cfg) = make_interp();
836 // %name is a function
837 let v = ok(&mut i, &cfg, "%main");
838 assert!(matches!(v, Value::Fun(_)));
839 // show renders it back
840 check!(i, cfg, "show %main", Value::text("%main"));
844fn single_quoted_text() {
845 let (mut i, cfg) = make_interp();
846 check!(i, cfg, "'hello'", Value::text("hello"));
847 check!(i, cfg, "'say \"hi\"'", Value::text("say \"hi\""));
848 check!(i, cfg, "\"don't\"", Value::text("don't"));
849 check!(i, cfg, "'a' ++ 'b'", Value::text("ab"));
850 check!(i, cfg, "'x' == \"x\"", Value::Bool(true));
854fn path_literals_are_text_lists() { let (mut i, cfg) = make_interp();
859 Value::list(vec![Value::text("src"), Value::text("lexer.rs")])
861 check!(i, cfg, "./", Value::list(vec![]));
862 check!(i, cfg, "./a/b/ == ./a/b", Value::Bool(true));
866fn crash_messages_propagate() {
867 let (mut i, cfg) = make_interp();
868 let m = crash(&mut i, &cfg, "crash \"custom message\"");
869 assert!(m.contains("custom message"), "{}", m);
873fn contract_result_checked() {
874 let (mut i, cfg) = make_interp();
875 // head : [a] -> a — no constraint, fine
876 check!(i, cfg, "head [1 2]", Value::int(1));
877 // length's result is Int
878 check!(i, cfg, "length \"x\" or 0", Value::int(0)); // length on text crashes (list only) -> or
882fn goto_error_message() {
883 let (mut i, cfg) = make_interp();
884 // goto on an empty revset against a trivial repo
888 "goto (labelled \"nope\")",
890 // the function is returned; applying it to a repo crashes — test via at
891 assert!(m.is_ok(), "goto itself is a value");
895fn wildcard_patterns() {
896 let (mut i, cfg) = make_interp();
897 check!(i, cfg, "(\\_ -> 42) \"anything\"", Value::int(42));
898 check!(i, cfg, "(\\_ _ -> 1) 2 3", Value::int(1));
902fn deeply_nested_or() {
903 let (mut i, cfg) = make_interp();
907 "head [] or head [] or head [] or 4",
913fn very_deep_recursion() {
914 let (mut i, cfg) = make_interp();
918 "let go = \\n -> if n == 0 then 0 else 1 + go (n - 1) in go 100000",
920 assert!(value_eq(&v.unwrap(), &Value::int(100000)).unwrap());
924fn very_deep_or_recursion() {
925 // `or`-recursive walks (the shape of `top`/`tip` over history) must not
926 // consume native stack: crash-catching is part of the heap machine
927 let (mut i, cfg) = make_interp();
931 "let go = \\n -> (if n <= 0 then crash \"bottom\" else go (n - 1)) or n in go 50000",
933 assert!(value_eq(&v.unwrap(), &Value::int(0)).unwrap());
937fn very_deep_recursion_through_builtins_that_apply_functions() {
938 // §4.1: map, filter, foldl and a composition applied the functions they
939 // were given in a nested run on the native stack, so a recursion through
940 // one of them overflowed the stack and aborted the process at depths
941 // that plain recursion (very_deep_recursion) reaches on this thread
942 let (mut i, cfg) = make_interp();
944 "let f = \\n -> if n == 0 then 0 else head (map f [(n - 1)]) in f 30000",
945 "let f = \\n -> if n == 0 then 0 else length (filter (\\x -> f (n - 1) == 0) [1]) - 1 in f 30000",
946 "let f = \\n -> if n == 0 then 0 else foldl (\\a x -> f (n - 1)) 0 [1] in f 30000",
947 "let f = \\n -> if n == 0 then 0 else ((\\x -> x) . f) (n - 1) in f 30000",
948 "let f = \\n -> if n == 0 then 0 else (head . map f) [(n - 1)] in f 30000",
950 check!(i, cfg, src, Value::int(0));
954/// Run `f` on a thread with a stack of `mb` megabytes.
955fn on_stack(mb: usize, f: impl FnOnce() + Send + 'static) {
956 std::thread::Builder::new()
957 .stack_size(mb * 1024 * 1024)
965fn deep_linear_history_takes_no_native_stack_per_commit() {
966 // `commits` recursed through `map` once per level of history, and
967 // `labelled`, `trunk` and `immutable` walk the history with it. The
968 // binary evaluates `immutable` on every run (§7.2), so a long linear
969 // history made every command overflow the stack and abort (§4.1). The
970 // chain is built in the language, its deepest commit labelled `main`,
971 // and seen from the top; the stack is a small fraction of the binary's,
972 // so native stack spent per commit shows at a depth a test can afford.
974 let (mut i, cfg) = make_interp();
977 "let leaf = {{ root = {{ files = [], message = \"\", labels = [\"main\"], id = @ }}, children = [] }}; \
978 chain = foldl (\\t k -> {{ root = {{ files = [], message = \"\", labels = [], id = @ }}, children = [t] }}) leaf (range 1 {n}); \
979 repo = {{ root = chain.root, children = chain.children, context = [] }} \
980 in [(length (commits repo)) (length (trunk repo)) (length (immutable repo))]"
986 Value::list(vec![Value::int(n), Value::int(1), Value::int(n)])
992fn history_walks_take_time_linear_in_the_length_of_history() {
993 // `commits` was `\t -> t.root :: (concat (map commits t.children) or [])`
994 // and `ancestors` consed the focus onto the ancestors of `up repo`. `::`
995 // and `concat` build a new list, so every level copied the whole list
996 // below it and both were quadratic in the depth of the history. The
997 // binary evaluates `immutable` on every run (§7.2), which walks the
998 // history with both through `labelled`, `trunk` and `ancestorsOf`: `j 1`
999 // took a minute on a linear history of 32000 commits. Here the history
1000 // is seen from the top, its deepest commit labelled `main`, so
1001 // `immutable` walks it with both. In linear time that takes seconds in a
1002 // debug build; in quadratic time, many minutes. The stack is the
1003 // binary's, since `by` and dropping the chain take native stack per
1006 let (tx, rx) = std::sync::mpsc::channel();
1007 std::thread::Builder::new()
1008 .stack_size(512 * 1024 * 1024)
1010 let (mut i, cfg) = make_interp();
1012 "let commit = \\l -> {{ files = [], message = \"\", labels = l, id = @ }}; \
1013 chain = foldl (\\t k -> {{ root = commit [], children = [t] }}) \
1014 ({{ root = commit [\"main\"], children = [] }}) (range 0 {n}); \
1015 repo = {{ root = chain.root, children = chain.children, context = [] }} \
1016 in [(length (commits repo)) (length (immutable repo))]"
1018 let got = ev(&mut i, &cfg, &src).map(|v| j::show::show(&i, &v).unwrap());
1019 let _ = tx.send(got);
1022 let limit = std::time::Duration::from_secs(60);
1023 match rx.recv_timeout(limit) {
1024 Ok(got) => assert_eq!(got, Ok(format!("[{m} {m}]", m = n + 1))),
1025 Err(_) => panic!("walking {} commits took more than {:?}", n, limit),
1030fn commits_gives_what_its_recursive_definition_gave_on_any_tree() {
1031 // `commits` is the builtin `subtreeCommits`, which must give what the
1032 // recursive definition it replaced gives (§4.9), on malformed trees too.
1033 // That definition's `or []` catches a crash anywhere under a node: only
1034 // a top without `root` crashes, and a node whose `children` is not a
1035 // list of values with a `root` contributes its own root alone.
1036 let (mut i, cfg) = make_interp();
1037 let old = "let old = \\t -> t.root :: (concat (map old t.children) or []) in";
1038 let ints = |xs: &[i64]| Value::list(xs.iter().map(|&x| Value::int(x)).collect());
1040 ("({ root = 1, children = [] })", ints(&[1])),
1042 "({ root = 1, children = [({ root = 2, children = [({ root = 3, children = [] })] }) \
1043 ({ root = 4, children = [] })] })",
1044 ints(&[1, 2, 3, 4]),
1046 ("({ root = 1, children = [], context = [] })", ints(&[1])),
1047 ("({ root = 1, children = 5 })", ints(&[1])),
1048 ("({ root = 1 })", ints(&[1])),
1049 ("({ root = 1, children = [({ root = 2, children = [] }) 7] })", ints(&[1])),
1051 "({ root = 1, children = [({ root = 2, children = [({ root = 3 })] }) \
1052 ({ root = 4, children = [5] })] })",
1053 ints(&[1, 2, 3, 4]),
1056 "({ root = 1, children = [({ root = 2, children = [({ children = [] })] }) \
1057 ({ root = 4, children = [({ root = 5, children = \"x\" })] })] })",
1058 ints(&[1, 2, 4, 5]),
1061 check!(i, cfg, &format!("commits {}", t), want);
1062 check!(i, cfg, &format!("{} old {}", old, t), want);
1065 ("5", "crash: cannot select field `root` from a Int"),
1066 ("({ children = [] })", "crash: record has no field `root`"),
1068 assert_eq!(crash(&mut i, &cfg, &format!("commits {}", t)), msg);
1069 assert_eq!(crash(&mut i, &cfg, &format!("{} old {}", old, t)), msg);
1074fn builtins_that_apply_functions_keep_their_semantics() {
1075 let (mut i, cfg) = make_interp();
1076 let ints = |xs: &[i64]| Value::list(xs.iter().map(|&x| Value::int(x)).collect());
1077 // elements are visited left to right, so the first crash is the first
1078 // element's (§4: evaluation is left to right)
1080 ("map (\\x -> crash (show x)) [1 2]", "1"),
1081 ("filter (\\x -> crash (show x)) [1 2]", "1"),
1082 ("foldl (\\a x -> crash (show x)) 0 [1 2]", "1"),
1083 ("filter (\\x -> x) [true 1]", "filter: predicate returned a Int"),
1084 ("(not . head) []", "head: empty list"),
1086 let m = crash(&mut i, &cfg, src);
1087 assert!(m.ends_with(msg), "{} => {}", src, m);
1089 // a crash in a function they apply unwinds to the nearest `or`, outside
1090 // them or inside the function
1091 check!(i, cfg, "map (\\x -> if x == 2 then crash \"two\" else x) [1 2 3] or [0]", ints(&[0]));
1092 check!(i, cfg, "map (\\x -> (if x == 2 then crash \"two\" else x) or 0) [1 2 3]", ints(&[1, 0, 3]));
1093 check!(i, cfg, "filter (\\x -> x > 1 or false) [1 2 3]", ints(&[2, 3]));
1094 check!(i, cfg, "foldl (\\a x -> (a + head []) or x) 0 [1 2 3]", Value::int(3));
1095 check!(i, cfg, "((\\x -> x + 1) . (\\x -> head [] or x)) 1", Value::int(2));
1096 // nested, empty, and partially applied
1097 check!(i, cfg, "map (map ((+) 1)) [[1] [] [2 3]]", Value::list(vec![ints(&[2]), ints(&[]), ints(&[3, 4])]));
1098 check!(i, cfg, "map id []", ints(&[]));
1099 check!(i, cfg, "filter (\\x -> true) []", ints(&[]));
1100 check!(i, cfg, "foldl (\\a x -> crash \"no\") 5 []", Value::int(5));
1101 check!(i, cfg, "foldl (\\a x -> a * 10 + x) 0 [1 2 3]", Value::int(123));
1102 check!(i, cfg, "(map (\\x -> x * 2) . filter (\\x -> x > 1)) [1 2 3]", ints(&[4, 6]));
1103 // the body of the function is evaluated on each application (§4.10)
1104 check!(i, cfg, "let ids = map (\\x -> @) [1 2 3] in nth 0 ids /= nth 2 ids", Value::Bool(true));
1108fn or_caught_crash_yields_rhs_id() {
1109 // a crash on the lhs, after minting an id, is caught and the rhs value is
1110 // used (the mint on the failing branch is rewound)
1111 let (mut i, cfg) = make_interp();
1115 "((\\_ -> let discard = @ in crash \"boom\") 0) or @",
1117 assert!(matches!(v, Value::Id(_)), "got {}", j::show::show(&i, &v).unwrap());
1121fn partial_application_values() {
1122 let (mut i, cfg) = make_interp();
1123 check!(i, cfg, "(take 2) [1 2 3 4]", Value::list(vec![Value::int(1), Value::int(2)]));
1124 check!(i, cfg, "map ((+) 1) [1 2]", Value::list(vec![Value::int(2), Value::int(3)]));
1125 check!(i, cfg, "foldl (.) id [not not] true", Value::Bool(true));
1129fn operator_as_function() {
1130 let (mut i, cfg) = make_interp();
1131 check!(i, cfg, "(+) 1 2", Value::int(3));
1132 check!(i, cfg, "foldl (++) \"\" [\"a\" \"b\"]", Value::text("ab"));
1133 check!(i, cfg, "(.) not not true", Value::Bool(true));
1134 check!(i, cfg, "(::) 1 [2]", Value::list(vec![Value::int(1), Value::int(2)]));
1137/// The examples of a markdown file that document a result: the line each
1138/// starts on, the expression, and the text after its `=>`. An example starts
1139/// at column 0 in a fenced block (a `> ` quote stripped) and continues on the
1140/// indented lines below it, and on a line starting `in`; its result follows
1141/// `=>`, on its first line or an indented one, and continues on indented
1142/// lines. A `--` comment outside a text literal is dropped.
1143fn documented_results(md: &str) -> Vec<(usize, String, String)> {
1144 fn split(line: &str) -> (String, Option<String>) {
1145 let (mut in_text, mut escaped) = (false, false);
1146 for (at, c) in line.char_indices() {
1149 _ if escaped => escaped = false,
1150 '\\' => escaped = true,
1151 '"' => in_text = false,
1154 } else if c == '"' {
1156 } else if line[at..].starts_with("--") {
1157 return (line[..at].trim_end().to_string(), None);
1158 } else if line[at..].starts_with("=>") {
1159 let (rest, _) = split(&line[at + 2..]);
1160 return (line[..at].trim_end().to_string(), Some(rest.trim().to_string()));
1163 (line.trim_end().to_string(), None)
1165 let mut out = Vec::new();
1166 // the example being read: its line, expression, and result so far
1167 let mut cur: Option<(usize, String, Option<String>)> = None;
1168 let mut in_block = false;
1169 for (n, raw) in md.lines().enumerate() {
1170 let line = raw.strip_prefix("> ").unwrap_or(if raw == ">" { "" } else { raw });
1171 let fence = line.trim_start().starts_with("```");
1172 let empty = line.trim().is_empty();
1173 let comment = line.trim_start().starts_with("--");
1174 let continues = line.starts_with(' ') || line.starts_with("in ");
1176 in_block = !in_block;
1178 // an example ends at a blank line, at the next one, and with its block
1179 if !in_block || empty || !(comment || continues) {
1180 if let Some((at, e, Some(r))) = cur.take() {
1181 out.push((at, e, r));
1184 if !in_block || fence || empty || comment {
1187 let (text, result) = split(line);
1189 None if !continues => cur = Some((n + 1, text, result)),
1191 Some((_, _, Some(r))) => {
1193 r.push_str(text.trim());
1195 Some((_, e, r)) => {
1196 if !text.trim().is_empty() {
1207/// The documented examples whose result is prose, not an expression, by
1208/// file and expression. They are not checked; every other example is.
1209const PROSE_RESULTS: &[(&str, &str)] = &[
1210 ("docs/language.md", "(\\x -> x.missing) or (\\x -> 0)"),
1211 ("docs/base.md", "[({ path = ./a.txt, content = blob \"hello\" })]"),
1215fn documented_examples_evaluate_to_their_results() {
1216 // docs/language.md listed two records side by side, `[{ id = 1 } { id = 2 }]`,
1217 // as a list of two; it is one record updated by the other (§3.4), and
1218 // `map (.id)` of it gave `[2]`, not the `[1 2]` documented. Each example
1219 // that documents a value must evaluate to it. One whose result is prose
1220 // (PROSE_RESULTS) or that names a commit by its id is not checked here;
1221 // any other that crashes, or whose result does not evaluate or compare,
1222 // is wrong, so a misspelled result or a crash documented as a result
1224 let (mut i, cfg) = make_interp();
1225 let mut checked = 0;
1226 let mut prose = Vec::new();
1227 let mut wrong = Vec::new();
1229 ("docs/language.md", include_str!("../docs/language.md")),
1230 ("docs/base.md", include_str!("../docs/base.md")),
1232 for (line, expr, result) in documented_results(md) {
1233 if PROSE_RESULTS.contains(&(name, expr.as_str())) {
1234 prose.push((name, expr));
1237 let at = format!("{}:{}: {}", name, line, expr);
1238 match (ev(&mut i, &cfg, &expr), ev(&mut i, &cfg, &result)) {
1239 // an id names no commit here
1240 (Err(e), _) if e == "id resolution" => {}
1241 (Err(e), _) => wrong.push(format!("{} => {}: {}", at, result, e)),
1242 (Ok(_), Err(e)) => wrong.push(format!("{} => {}: the result: {}", at, result, e)),
1243 (Ok(got), Ok(want)) => match value_eq(&got, &want) {
1244 Ok(true) => checked += 1,
1245 Ok(false) => wrong.push(format!(
1248 j::show::show(&i, &got).unwrap(),
1251 // a function does not compare: say what it does in
1252 // prose, and list it in PROSE_RESULTS
1253 Err(e) => wrong.push(format!("{} => {}: {}", at, result, e.msg)),
1258 assert!(wrong.is_empty(), "{}", wrong.join("\n"));
1259 // each prose example is still where the list says
1260 for (name, expr) in PROSE_RESULTS {
1262 prose.iter().any(|(n, e)| n == name && e == expr),
1263 "{}: no example `{}`",
1268 // the extraction still finds the examples
1269 assert!(checked >= 150, "only {} examples checked", checked);
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list