libunseemly/
end_to_end__tests.rs

1use crate::{
2    ast::Ast,
3    core_forms, eval_unseemly_program_top, expand, grammar,
4    name::{n, Name},
5    runtime::{core_values, eval, eval::Value},
6    ty, type_unseemly_program_top,
7    util::assoc::Assoc,
8};
9use std::cell::RefCell;
10
11// HACK: the non-test code in here is copied from `cli.rs`.
12
13thread_local! {
14    pub static ty_env : RefCell<Assoc<Name, Ast>> = RefCell::new(core_values::core_types());
15    pub static val_env : RefCell<Assoc<Name, Value>> = RefCell::new(core_values::core_values());
16}
17
18fn type_unseemly_program(program: &str) -> Result<Ast, String> {
19    let ast = grammar::parse(
20        &core_forms::outermost_form(),
21        core_forms::outermost__parse_context(),
22        program,
23    )
24    .map_err(|e| e.msg)?;
25
26    ty_env.with(|tys| ty::synth_type(&ast, tys.borrow().clone()).map_err(|e| format!("{}", e)))
27}
28
29fn eval_unseemly_program(program: &str) -> Result<Value, String> {
30    let ast: Ast = grammar::parse(
31        &core_forms::outermost_form(),
32        core_forms::outermost__parse_context(),
33        program,
34    )
35    .map_err(|e| e.msg)?;
36
37    let _type = ty_env
38        .with(|tys| ty::synth_type(&ast, tys.borrow().clone()).map_err(|e| format!("{}", e)))?;
39
40    let core_ast = expand::expand(&ast).map_err(|_| "error".to_owned())?;
41
42    val_env.with(|vals| eval::eval(&core_ast, vals.borrow().clone()).map_err(|_| "???".to_string()))
43}
44
45fn assign_variable(name: &str, expr: &str) -> Result<Value, String> {
46    let res = eval_unseemly_program(expr);
47
48    if let Ok(ref v) = res {
49        let ty = type_unseemly_program(expr).unwrap();
50        ty_env.with(|tys| {
51            val_env.with(|vals| {
52                let new_tys = tys.borrow().set(n(name), ty);
53                let new_vals = vals.borrow().set(n(name), v.clone());
54                *tys.borrow_mut() = new_tys;
55                *vals.borrow_mut() = new_vals;
56            })
57        })
58    }
59    res
60}
61
62fn assign_t_var(name: &str, t: &str) -> Result<Ast, String> {
63    let ast = grammar::parse(
64        &grammar::FormPat::Call(n("Type")),
65        core_forms::outermost__parse_context(),
66        t,
67    )
68    .map_err(|e| e.msg)?;
69
70    let res =
71        ty_env.with(|tys| ty::synth_type(&ast, tys.borrow().clone()).map_err(|e| format!("{}", e)));
72
73    if let Ok(ref t) = res {
74        ty_env.with(|tys| {
75            let new_tys = tys.borrow().set(n(name), t.clone());
76            *tys.borrow_mut() = new_tys;
77        })
78    }
79
80    res
81}
82
83fn ignore__this_function() {
84    // Suppress unused variable warnings for functions only used in tests.
85    let _ = eval_unseemly_program_top;
86    let _ = type_unseemly_program_top;
87}
88
89// Many of these tests should be converted to `u!`-based tests.
90// In a lot of cases, the fact htat `u!` doesn't support syntax quotation is an obstacle.
91// TODO: cut the knot and bake syntax {,un}quotation support to `u!`.
92
93#[test]
94fn end_to_end_int_list_tools() {
95    assert_m!(assign_t_var("IntList", "mu_type IntList . { +[Nil]+ +[Cons Int IntList]+ }"), Ok(_));
96
97    assert_m!(assign_t_var("IntListUF", "{ +[Nil]+ +[Cons Int IntList]+ }"), Ok(_));
98
99    assert_m!(
100        assign_variable("mt_ilist", "fold +[Nil]+ : { +[Nil]+ +[Cons Int IntList]+ } : IntList"),
101        Ok(_)
102    );
103
104    assert_m!(
105        assign_variable("ilist_3", "fold +[Cons three mt_ilist]+ : IntListUF : IntList"),
106        Ok(_)
107    );
108
109    assert_m!(
110        assign_variable("ilist_23", "fold +[Cons two ilist_3]+ : IntListUF : IntList"),
111        Ok(_)
112    );
113
114    assert_m!(
115        assign_variable("ilist_123", "fold +[Cons one ilist_23]+ : IntListUF : IntList"),
116        Ok(_)
117    );
118
119    assert_m!(
120        assign_variable(
121            "sum_int_list",
122            "(fix .[again : [-> [IntList -> Int]] .
123             .[ lst : IntList .
124                 match unfold lst {
125                     +[Nil]+ => zero +[Cons hd tl]+ => (plus hd ((again) tl))} ]. ]. )"
126        ),
127        Ok(_)
128    );
129
130    assert_eq!(eval_unseemly_program("(sum_int_list ilist_123)"), Ok(val!(i 6)));
131
132    assert_m!(
133        assign_variable(
134            "int_list_len",
135            "(fix .[again : [-> [IntList -> Int]] .
136             .[ lst : IntList .
137                 match unfold lst {
138                     +[Nil]+ => zero +[Cons hd tl]+ => (plus one ((again) tl))} ]. ].)"
139        ),
140        Ok(_)
141    );
142
143    assert_eq!(eval_unseemly_program("(int_list_len ilist_123)"), Ok(val!(i 3)));
144}
145
146#[test]
147fn end_to_end_list_tools() {
148    assert_m!(
149        assign_t_var("List", "forall T . mu_type List . { +[Nil]+ +[Cons T List<T> ]+ }"),
150        Ok(_)
151    );
152
153    assert_m!(assign_t_var("ListUF", "forall T . { +[Nil]+ +[Cons T List<T> ]+ }"), Ok(_));
154
155    assert_m!(
156        assign_variable(
157            "mt_list",
158            "fold +[Nil]+ : { +[Nil]+ +[Cons Int List<Int> ]+ } : List < Int > "
159        ),
160        Ok(_)
161    );
162
163    assert_m!(
164        assign_variable("list_3", "fold +[Cons three mt_list]+ : ListUF<Int> : List<Int>"),
165        Ok(_)
166    );
167
168    assert_m!(
169        assign_variable("list_23", "fold +[Cons two list_3]+ : ListUF<Int> : List<Int>"),
170        Ok(_)
171    );
172
173    assert_m!(
174        assign_variable("list_123", "fold +[Cons one list_23]+ : ListUF<Int> : List<Int>"),
175        Ok(_)
176    );
177
178    assert_m!(
179        assign_variable(
180            "list_len",
181            "forall S . (fix .[again : [-> [List<S> -> Int]] .
182            .[ lst : List<S> .
183                match unfold lst {
184                    +[Nil]+ => zero
185                    +[Cons hd tl]+ => (plus one ((again) tl))} ]. ].)"
186        ),
187        Ok(_)
188    );
189
190    assert_eq!(eval_unseemly_program("(list_len list_123)"), Ok(val!(i 3)));
191
192    assert_m!(
193        assign_variable(
194            "map",
195            "forall T S . (fix  .[again : [-> [List<T>  [T -> S] -> List<S> ]] .
196            .[ lst : List<T>   f : [T -> S] .
197                match unfold lst {
198                    +[Nil]+ => fold +[Nil]+ : ListUF<S> : List<S>
199                    +[Cons hd tl]+ =>
200                      fold +[Cons (f hd) ((again) tl f)]+ : ListUF<S> : List<S> } ]. ].)"
201        ),
202        Ok(_)
203    );
204    // TODO: what should even happen if you have `forall` not on the "outside"?
205    // It should probably be an error to have a value typed with an underdetermined type.
206
207    // TODO: it's way too much of a pain to define each different expected result list.
208    assert_m!(eval_unseemly_program("(map list_123 .[x : Int . (plus x one)]. )"), Ok(_));
209
210    assert_m!(eval_unseemly_program("(map list_123 .[x : Int . (equal? x two)]. )"), Ok(_));
211}
212
213#[test]
214fn subtyping_direction() {
215    // Let's check to make sure that "supertype" and "subtype" never got mixed up:
216
217    assert_m!(assign_variable("ident", "forall T . .[ a : T . a ]."), Ok(_));
218
219    assert_eq!(eval_unseemly_program("(ident five)"), Ok(val!(i 5)));
220
221    assert_m!(eval_unseemly_program("( .[ a : [Int -> Int] . a]. ident)"), Ok(_));
222
223    assert_m!(eval_unseemly_program("( .[ a : forall T . [T -> T] . a]. .[a : Int . a].)"), Err(_));
224
225    assert_m!(eval_unseemly_program(".[ a : *[]* . a]."), Ok(_));
226
227    assert_m!(
228        eval_unseemly_program("( .[ a : *[normal : Int extra : Int]* . a]. *[normal : one]*)"),
229        Err(_)
230    );
231
232    assert_m!(
233        eval_unseemly_program("( .[ a : *[normal : Int]* . a]. *[normal : one extra : five]*)"),
234        Ok(_)
235    );
236}
237
238#[test]
239fn end_to_end_quotation_advanced() {
240    assert_eq!(
241        eval_unseemly_program(
242            "(.[five_e : Expr < Int >.
243                '[Expr | (plus five ,[five_e],) ]' ].
244                '[Expr | five]')"
245        ),
246        eval_unseemly_program("'[Expr | (plus five five) ]'")
247    );
248
249    // Pass the wrong type (not really a test of quotation)
250    assert_m!(
251        type_unseemly_program_top(
252            "(.[five_e : Expr<Int> .
253                '[Expr | (plus five ,[five_e],) ]' ].
254                '[Expr | true]')"
255        ),
256        Err(_)
257    );
258
259    // Interpolate the wrong type
260    assert_m!(
261        type_unseemly_program_top(
262            "(.[five_e : Expr<Bool> .
263                '[Expr | (plus five ,[five_e],) ]' ].
264                '[Expr | true]')"
265        ),
266        Err(_)
267    );
268
269    // Interpolate the wrong type (no application needed to find the error)
270    assert_m!(
271        type_unseemly_program_top(".[five_e : Expr<Bool> . '[Expr | (plus five ,[five_e],) ]' ]."),
272        Err(_)
273    );
274
275    assert_m!(
276        eval_unseemly_program(
277            "forall T . .[type : Type<T>   rhs : Expr<T>
278                . '[Expr | (.[x : ,[Type<T> | type], . eight].  ,[rhs], )]' ]."
279        ),
280        Ok(_)
281    );
282
283    assert_m!(eval_unseemly_program("'[Pat<Nat> | x]'"), Ok(_));
284
285    // Actually import a pattern of quoted syntax:
286    assert_eq!(
287        eval_unseemly_program(
288            "match '[Expr | (plus one two) ]' {
289                 '[Expr<Int> | (plus ,[Expr<Int> | e], two) ]' => e }"
290        ),
291        eval_unseemly_program("'[Expr| one]'")
292    );
293
294    // Thanks to `prefab_type`, we can do implicitly-typed `let`
295    //  expanding to explicitly-typed lambda!
296    // See `trad_let.unseemly` for details.
297    assert_m!(
298        assign_variable(
299            "let",
300            "forall T S . .[binder : Pat<T>
301                        type : Type<T>
302                        rhs : Expr<T>
303                        body : Expr<S> .
304             '[ Expr | (.[x : ,[type],
305                     . match x { ,[Pat<T> | binder], => ,[body], } ].
306                 ,[rhs],)]' ]."
307        ),
308        Ok(_)
309    );
310
311    without_freshening! {
312        assert_eq!(
313            eval_unseemly_program(
314                "(let  '[Pat<Int> | y]'
315                       '[Type<Int> | Int]'
316                       '[Expr<Int> | eight]'
317                       '[Expr<Int> | five]')"),
318            eval_unseemly_program("'[Expr<Int> | (.[x : Int . match x {y => five}].  eight)]'"));
319    }
320
321    //  // We need tuple literals before we can test this:
322    //  assert_m!(assign_variable("let-multi",
323    //      "forall T . .[ binder : **[ :::[T >> Ident<T> ]::: ]**
324    //                     type : **[ :::[T >> Type<T> ]::: ]**
325    //                     rhs : **[ :::[T >> Expr<T> ]::: ]**
326    //                     body : Expr<S> .
327    //          '[Expr | (.[ ...[, binder , >> ,[Ident | binder],]...
328    //                       : ...[, type , >> ,[Type | type], ]... .
329    //                    ,[body], ].
330    //                      ...[, Expr , | ,[rhs], ]... ) ]'
331    //                       "),
332    //       Ok(_));
333
334    //  without_freshening! {
335    //      assert_eq!(
336    //          eval_unseemly_program(
337    //              "(let-multi  '[Ident<Int> | y]'
338    //                     '[Type<Int> | Int]'
339    //                     '[Expr<Int> | eight]'
340    //                     '[Expr<Int> | five]')"),
341    //          eval_unseemly_program("'[Expr<Int> | (.[x : Int . match x {y => five}].  eight)]'"));
342    //  }
343}
344
345#[test]
346fn simple_end_to_end_eval() {
347    assert_eq!(eval_unseemly_program_top("(zero? zero)"), Ok(val!(b true)));
348
349    assert_eq!(eval_unseemly_program_top("(plus one one)"), Ok(val!(i 2)));
350
351    assert_eq!(
352        eval_unseemly_program_top("(.[x : Int  y : Int . (plus x y)]. one one)"),
353        Ok(val!(i 2))
354    );
355
356    assert_eq!(
357        eval_unseemly_program_top(
358            "((fix .[ again : [ -> [ Int -> Int ]] .
359            .[ n : Int .
360                match (zero? n) {
361                    +[True]+ => one
362                    +[False]+ => (times n ((again) (minus n one))) } ]. ].) five)"
363        ),
364        Ok(val!(i 120))
365    );
366}
367
368#[test]
369fn end_to_end_quotation_basic() {
370    assert_m!(eval_unseemly_program_top("'[Expr | .[ x : Int . x ]. ]'"), Ok(_));
371
372    assert_m!(eval_unseemly_program_top("'[Expr | (plus five five) ]'"), Ok(_));
373
374    assert_m!(eval_unseemly_program_top("'[Expr | '[Expr | (plus five five) ]' ]'"), Ok(_));
375
376    //≫ .[s : Expr<Int> . '[Expr | ( ,[Expr | s], '[Expr | ,[Expr | s], ]')]' ].
377}
378
379#[test]
380fn language_building() {
381    assert_eq!(
382        eval_unseemly_program_top(
383            r"extend_syntax
384                DefaultSeparator ::= /((?:\s|#[^\n]*)*)/ ;
385            in
386                # Now we have comments! (just not after the last token)
387            five"
388        ),
389        Ok(val!(i 5))
390    );
391
392    let bound_wrong_prog = "extend_syntax
393            Expr ::=also forall T S . '{
394                [
395                    lit ,{ DefaultToken }, = 'let'
396                    [
397                        pat := ( ,{ Pat<S> }, )
398                        lit ,{ DefaultToken }, = '='
399                        value := ( ,{ Expr<S> }, )
400                        lit ,{ DefaultToken }, = ';'
401                    ] *
402                    lit ,{ DefaultToken }, = 'in'
403                    body := ( ,{ Expr<T> }, <-- ...[pat = value]... )
404                ]
405            }' let_macro -> .{
406                '[Expr |
407                    match ...[,value, >> ,[value], ]...
408                        { ...[,pat, >> ,[pat],]... => ,[body], } ]'
409            }. ;
410        in
411        let x = eight ;
412            y = times ;
413        in (plus x y)";
414    let bound_wrong_ast = grammar::parse(
415        &core_forms::outermost_form(),
416        core_forms::outermost__parse_context(),
417        bound_wrong_prog,
418    )
419    .unwrap();
420
421    assert_m!(
422        ty::synth_type(&bound_wrong_ast, crate::runtime::core_values::core_types()),
423        ty_err_p!(Mismatch(x, y)) => {
424            assert_eq!(x, uty!({Int :}));
425            assert_eq!(y, uty!({fn : [{Int :}; {Int :}] {Int :}}));
426        }
427    );
428
429    let inner_expr_wrong_prog = "extend_syntax
430            Expr ::=also forall T S . '{
431                [
432                    lit ,{ DefaultToken }, = 'let'
433                    [
434                        pat := ( ,{ Pat<S> }, )
435                        lit ,{ DefaultToken }, = '='
436                        value := ( ,{ Expr<S> }, )
437                        lit ,{ DefaultToken }, = ';'
438                    ] *
439                    lit ,{ DefaultToken }, = 'in'
440                    body := ( ,{ Expr< T > }, <-- ...[pat = value]... )
441                ]
442            }' let_macro -> .{
443                '[Expr |
444                    match ...[,value, >> ,[value], ]...
445                        { ...[,pat, >> ,[pat],]... => ,[body], } ]'
446            }. ;
447        in
448        let x = eight ;
449            y = four ;
450        in (plus x times)";
451    let inner_expr_wrong_ast = grammar::parse(
452        &core_forms::outermost_form(),
453        core_forms::outermost__parse_context(),
454        inner_expr_wrong_prog,
455    )
456    .unwrap();
457
458    assert_m!(
459        ty::synth_type(&inner_expr_wrong_ast, crate::runtime::core_values::core_types()),
460        ty_err_p!(Mismatch(x, times)) => {
461            assert_eq!(x, uty!({Int :}));
462            assert_eq!(times, uty!({fn : [{Int :}; {Int :}] {Int :}}));
463        }
464    );
465
466    // TODO: leaving out the `**[ ]**` results in an ICP; it should be a static error.
467
468    let let_macro_prog = "extend_syntax
469            Expr ::=also forall T S . '{
470                [
471                    lit ,{ DefaultToken }, = 'let'
472                    [
473                        pat := ( ,{ Pat<S> }, )
474                        lit ,{ DefaultToken }, = '='
475                        value := ( ,{ Expr<S> }, )
476                        lit ,{ DefaultToken }, = ';'
477                    ] *
478                    lit ,{ DefaultToken }, = 'in'
479                    body := ( ,{ Expr<T> }, <-- ...[pat = value]... )
480                ]
481            }' let_macro -> .{
482                '[Expr |
483                    match **[...[,value, >> ,[value], ]... ]**
484                        { **[...[,pat, >> ,[pat],]... ]** => ,[body], } ]'
485            }. ;
486        in
487        let x = eight ;
488            y = four ;
489        in (plus y (plus x y))";
490    assert_eq!(eval_unseemly_program_top(let_macro_prog), Ok(val!(i 16)));
491}