mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-22 08:17:40 +03:00
2216 lines
69 KiB
Rust
2216 lines
69 KiB
Rust
#[macro_use]
|
||
extern crate pretty_assertions;
|
||
#[macro_use]
|
||
extern crate indoc;
|
||
extern crate bumpalo;
|
||
extern crate roc;
|
||
|
||
extern crate quickcheck;
|
||
|
||
#[macro_use(quickcheck)]
|
||
extern crate quickcheck_macros;
|
||
|
||
mod helpers;
|
||
|
||
#[cfg(test)]
|
||
mod test_parse {
|
||
use crate::helpers::parse_with;
|
||
use bumpalo::collections::vec::Vec;
|
||
use bumpalo::{self, Bump};
|
||
use roc::module::header::ModuleName;
|
||
use roc::operator::BinOp::*;
|
||
use roc::operator::CalledVia;
|
||
use roc::operator::UnaryOp;
|
||
use roc::parse::ast::AssignedField::*;
|
||
use roc::parse::ast::CommentOrNewline::*;
|
||
use roc::parse::ast::Expr::{self, *};
|
||
use roc::parse::ast::Pattern::{self, *};
|
||
use roc::parse::ast::{
|
||
Attempting, Def, InterfaceHeader, Spaceable, Tag, TypeAnnotation, WhenBranch,
|
||
};
|
||
use roc::parse::module::{interface_header, module_defs};
|
||
use roc::parse::parser::{Fail, FailReason, Parser, State};
|
||
use roc::region::{Located, Region};
|
||
use std::{f64, i64};
|
||
|
||
fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) {
|
||
let arena = Bump::new();
|
||
let actual = parse_with(&arena, input);
|
||
|
||
assert_eq!(Ok(expected_expr), actual);
|
||
}
|
||
|
||
fn assert_parsing_fails<'a>(input: &'a str, reason: FailReason, attempting: Attempting) {
|
||
let arena = Bump::new();
|
||
let actual = parse_with(&arena, input);
|
||
let expected_fail = Fail { reason, attempting };
|
||
|
||
assert_eq!(Err(expected_fail), actual);
|
||
}
|
||
|
||
// STRING LITERALS
|
||
|
||
fn expect_parsed_str(input: &str, expected: &str) {
|
||
assert_parses_to(expected, Str(input.into()));
|
||
}
|
||
|
||
#[test]
|
||
fn empty_string() {
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"
|
||
""
|
||
"#
|
||
),
|
||
Str(""),
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn one_char_string() {
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"
|
||
"x"
|
||
"#
|
||
),
|
||
Str("x".into()),
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn multi_char_string() {
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"
|
||
"foo"
|
||
"#
|
||
),
|
||
Str("foo".into()),
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn string_without_escape() {
|
||
expect_parsed_str("a", r#""a""#);
|
||
expect_parsed_str("ab", r#""ab""#);
|
||
expect_parsed_str("abc", r#""abc""#);
|
||
expect_parsed_str("123", r#""123""#);
|
||
expect_parsed_str("abc123", r#""abc123""#);
|
||
expect_parsed_str("123abc", r#""123abc""#);
|
||
expect_parsed_str("123 abc 456 def", r#""123 abc 456 def""#);
|
||
}
|
||
|
||
#[test]
|
||
fn string_with_special_escapes() {
|
||
expect_parsed_str(r#"x\\x"#, r#""x\\x""#);
|
||
expect_parsed_str(r#"x\"x"#, r#""x\"x""#);
|
||
expect_parsed_str(r#"x\tx"#, r#""x\tx""#);
|
||
expect_parsed_str(r#"x\rx"#, r#""x\rx""#);
|
||
expect_parsed_str(r#"x\nx"#, r#""x\nx""#);
|
||
}
|
||
|
||
#[test]
|
||
fn string_with_single_quote() {
|
||
// This shoud NOT be escaped in a string.
|
||
expect_parsed_str("x'x", r#""x'x""#);
|
||
}
|
||
|
||
#[test]
|
||
fn empty_source_file() {
|
||
assert_parsing_fails("", FailReason::Eof(Region::zero()), Attempting::Module);
|
||
}
|
||
|
||
#[test]
|
||
fn first_line_too_long() {
|
||
let max_line_length = std::u16::MAX as usize;
|
||
|
||
// the string literal "ZZZZZZZZZ" but with way more Zs
|
||
let too_long_str_body: String = (1..max_line_length)
|
||
.into_iter()
|
||
.map(|_| "Z".to_string())
|
||
.collect();
|
||
let too_long_str = format!("\"{}\"", too_long_str_body);
|
||
|
||
// Make sure it's longer than our maximum line length
|
||
assert_eq!(too_long_str.len(), max_line_length + 1);
|
||
|
||
assert_parsing_fails(
|
||
&too_long_str,
|
||
FailReason::LineTooLong(0),
|
||
Attempting::Module,
|
||
);
|
||
}
|
||
|
||
// INT LITERALS
|
||
|
||
#[test]
|
||
fn zero_int() {
|
||
assert_parses_to("0", Int("0"));
|
||
}
|
||
|
||
#[test]
|
||
fn positive_int() {
|
||
assert_parses_to("1", Int("1"));
|
||
assert_parses_to("42", Int("42"));
|
||
}
|
||
|
||
#[test]
|
||
fn negative_int() {
|
||
assert_parses_to("-1", Int("-1"));
|
||
assert_parses_to("-42", Int("-42"));
|
||
}
|
||
|
||
#[test]
|
||
fn highest_int() {
|
||
assert_parses_to(
|
||
i64::MAX.to_string().as_str(),
|
||
Int(i64::MAX.to_string().as_str()),
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn lowest_int() {
|
||
assert_parses_to(
|
||
i64::MIN.to_string().as_str(),
|
||
Int(i64::MIN.to_string().as_str()),
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn int_with_underscore() {
|
||
assert_parses_to("1_2_34_567", Int("1_2_34_567"));
|
||
assert_parses_to("-1_2_34_567", Int("-1_2_34_567"));
|
||
// The following cases are silly. They aren't supported on purpose,
|
||
// but there would be a performance cost to explicitly disallowing them,
|
||
// which doesn't seem like it would benefit anyone.
|
||
assert_parses_to("1_", Int("1_"));
|
||
assert_parses_to("1__23", Int("1__23"));
|
||
}
|
||
|
||
#[quickcheck]
|
||
fn all_i64_values_parse(num: i64) {
|
||
assert_parses_to(num.to_string().as_str(), Int(num.to_string().as_str()));
|
||
}
|
||
|
||
// FLOAT LITERALS
|
||
|
||
#[test]
|
||
fn zero_float() {
|
||
assert_parses_to("0.0", Float("0.0"));
|
||
}
|
||
|
||
#[test]
|
||
fn positive_float() {
|
||
assert_parses_to("1.0", Float("1.0"));
|
||
assert_parses_to("1.1", Float("1.1"));
|
||
assert_parses_to("42.0", Float("42.0"));
|
||
assert_parses_to("42.9", Float("42.9"));
|
||
}
|
||
|
||
#[test]
|
||
fn negative_float() {
|
||
assert_parses_to("-1.0", Float("-1.0"));
|
||
assert_parses_to("-1.1", Float("-1.1"));
|
||
assert_parses_to("-42.0", Float("-42.0"));
|
||
assert_parses_to("-42.9", Float("-42.9"));
|
||
}
|
||
|
||
#[test]
|
||
fn float_with_underscores() {
|
||
assert_parses_to("1_23_456.0_1_23_456", Float("1_23_456.0_1_23_456"));
|
||
assert_parses_to("-1_23_456.0_1_23_456", Float("-1_23_456.0_1_23_456"));
|
||
}
|
||
|
||
#[test]
|
||
fn highest_float() {
|
||
let string = format!("{}.0", f64::MAX);
|
||
|
||
assert_parses_to(&string, Float(&string));
|
||
}
|
||
|
||
#[test]
|
||
fn lowest_float() {
|
||
let string = format!("{}.0", f64::MIN);
|
||
|
||
assert_parses_to(&string, Float(&string));
|
||
}
|
||
|
||
#[quickcheck]
|
||
fn all_f64_values_parse(num: f64) {
|
||
assert_parses_to(num.to_string().as_str(), Float(num.to_string().as_str()));
|
||
}
|
||
|
||
// RECORD LITERALS
|
||
|
||
#[test]
|
||
fn empty_record() {
|
||
let arena = Bump::new();
|
||
let expected = Record {
|
||
fields: Vec::new_in(&arena),
|
||
update: None,
|
||
};
|
||
let actual = parse_with(&arena, "{}");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn record_update() {
|
||
let arena = Bump::new();
|
||
let label1 = LabeledValue(
|
||
Located::new(0, 0, 16, 17, "x"),
|
||
&[],
|
||
arena.alloc(Located::new(0, 0, 19, 20, Int("5"))),
|
||
);
|
||
let label2 = LabeledValue(
|
||
Located::new(0, 0, 22, 23, "y"),
|
||
&[],
|
||
arena.alloc(Located::new(0, 0, 25, 26, Int("0"))),
|
||
);
|
||
let fields = bumpalo::vec![in &arena;
|
||
Located::new(0, 0, 16, 20, label1),
|
||
Located::new(0, 0, 22, 26, label2)
|
||
];
|
||
let var = Var {
|
||
module_name: "Foo.Bar",
|
||
ident: "baz",
|
||
};
|
||
let update_target = Located::new(0, 0, 2, 13, var);
|
||
let expected = Record {
|
||
update: Some(&*arena.alloc(update_target)),
|
||
fields,
|
||
};
|
||
|
||
let actual = parse_with(&arena, "{ Foo.Bar.baz & x: 5, y: 0 }");
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
// OPERATORS
|
||
|
||
#[test]
|
||
fn one_plus_two() {
|
||
let arena = Bump::new();
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, Int("1")),
|
||
Located::new(0, 0, 1, 2, Plus),
|
||
Located::new(0, 0, 2, 3, Int("2")),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "1+2");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn one_minus_two() {
|
||
let arena = Bump::new();
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, Int("1")),
|
||
Located::new(0, 0, 1, 2, Minus),
|
||
Located::new(0, 0, 2, 3, Int("2")),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "1-2");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn add_with_spaces() {
|
||
let arena = Bump::new();
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, Int("1")),
|
||
Located::new(0, 0, 3, 4, Plus),
|
||
Located::new(0, 0, 7, 8, Int("2")),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "1 + 2");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn sub_with_spaces() {
|
||
let arena = Bump::new();
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, Int("1")),
|
||
Located::new(0, 0, 3, 4, Minus),
|
||
Located::new(0, 0, 7, 8, Int("2")),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "1 - 2");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn add_var_with_spaces() {
|
||
// This is a regression test! It used to break with subtraction and work
|
||
// with other arithmetic operatos.
|
||
//
|
||
// Subtraction is special when it comes to parsing, because of unary negation.
|
||
|
||
let arena = Bump::new();
|
||
let var = Var {
|
||
module_name: "",
|
||
ident: "x",
|
||
};
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, var),
|
||
Located::new(0, 0, 2, 3, Plus),
|
||
Located::new(0, 0, 4, 5, Int("2")),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "x + 2");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn sub_var_with_spaces() {
|
||
// This is a regression test! It used to break with subtraction and work
|
||
// with other arithmetic operatos.
|
||
//
|
||
// Subtraction is special when it comes to parsing, because of unary negation.
|
||
let arena = Bump::new();
|
||
let var = Var {
|
||
module_name: "",
|
||
ident: "x",
|
||
};
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, var),
|
||
Located::new(0, 0, 2, 3, Minus),
|
||
Located::new(0, 0, 4, 5, Int("2")),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "x - 2");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn newline_before_add() {
|
||
let arena = Bump::new();
|
||
let spaced_int = Expr::SpaceAfter(
|
||
arena.alloc(Int("3")),
|
||
bumpalo::vec![in &arena; Newline].into_bump_slice(),
|
||
);
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, spaced_int),
|
||
Located::new(1, 1, 0, 1, Plus),
|
||
Located::new(1, 1, 2, 3, Int("4")),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "3 \n+ 4");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn newline_before_sub() {
|
||
let arena = Bump::new();
|
||
let spaced_int = Expr::SpaceAfter(
|
||
arena.alloc(Int("3")),
|
||
bumpalo::vec![in &arena; Newline].into_bump_slice(),
|
||
);
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, spaced_int),
|
||
Located::new(1, 1, 0, 1, Minus),
|
||
Located::new(1, 1, 2, 3, Int("4")),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "3 \n- 4");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn newline_after_mul() {
|
||
let arena = Bump::new();
|
||
let spaced_int = arena
|
||
.alloc(Int("4"))
|
||
.before(bumpalo::vec![in &arena; Newline].into_bump_slice());
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, Int("3")),
|
||
Located::new(0, 0, 3, 4, Star),
|
||
Located::new(1, 1, 2, 3, spaced_int),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "3 *\n 4");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn newline_after_sub() {
|
||
let arena = Bump::new();
|
||
let spaced_int = arena
|
||
.alloc(Int("4"))
|
||
.before(bumpalo::vec![in &arena; Newline].into_bump_slice());
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, Int("3")),
|
||
Located::new(0, 0, 3, 4, Minus),
|
||
Located::new(1, 1, 2, 3, spaced_int),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "3 -\n 4");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn comment_with_unicode() {
|
||
let arena = Bump::new();
|
||
let spaced_int = arena
|
||
.alloc(Int("3"))
|
||
.after(bumpalo::vec![in &arena; LineComment(" 2 × 2")].into_bump_slice());
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, spaced_int),
|
||
Located::new(1, 1, 0, 1, Plus),
|
||
Located::new(1, 1, 2, 3, Int("4")),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "3 # 2 × 2\n+ 4");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn comment_before_op() {
|
||
let arena = Bump::new();
|
||
let spaced_int = arena
|
||
.alloc(Int("3"))
|
||
.after(bumpalo::vec![in &arena; LineComment(" test!")].into_bump_slice());
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, spaced_int),
|
||
Located::new(1, 1, 0, 1, Plus),
|
||
Located::new(1, 1, 2, 3, Int("4")),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "3 # test!\n+ 4");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn comment_after_op() {
|
||
let arena = Bump::new();
|
||
let spaced_int = arena
|
||
.alloc(Int("92"))
|
||
.before(bumpalo::vec![in &arena; LineComment(" test!")].into_bump_slice());
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 2, Int("12")),
|
||
Located::new(0, 0, 4, 5, Star),
|
||
Located::new(1, 1, 1, 3, spaced_int),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "12 * # test!\n 92");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn ops_with_newlines() {
|
||
let arena = Bump::new();
|
||
let spaced_int1 = arena
|
||
.alloc(Int("3"))
|
||
.after(bumpalo::vec![in &arena; Newline].into_bump_slice());
|
||
let spaced_int2 = arena
|
||
.alloc(Int("4"))
|
||
.before(bumpalo::vec![in &arena; Newline, Newline].into_bump_slice());
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, spaced_int1),
|
||
Located::new(1, 1, 0, 1, Plus),
|
||
Located::new(3, 3, 2, 3, spaced_int2),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "3 \n+ \n\n 4");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn space_only_after_minus() {
|
||
// This is an edge case with minus because of unary negation.
|
||
// (x- y) should parse like subtraction (x - (y)), not function application (x (-y))
|
||
let arena = Bump::new();
|
||
let var1 = Var {
|
||
module_name: "",
|
||
ident: "x",
|
||
};
|
||
let var2 = Var {
|
||
module_name: "",
|
||
ident: "y",
|
||
};
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, var1),
|
||
Located::new(0, 0, 1, 2, Minus),
|
||
Located::new(0, 0, 3, 4, var2),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "x- y");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn minus_twelve_minus_five() {
|
||
let arena = Bump::new();
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 3, Int("-12")),
|
||
Located::new(0, 0, 3, 4, Minus),
|
||
Located::new(0, 0, 4, 5, Int("5")),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "-12-5");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn ten_times_eleven() {
|
||
let arena = Bump::new();
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 2, Int("10")),
|
||
Located::new(0, 0, 2, 3, Star),
|
||
Located::new(0, 0, 3, 5, Int("11")),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "10*11");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn multiple_operators() {
|
||
let arena = Bump::new();
|
||
let inner = arena.alloc((
|
||
Located::new(0, 0, 3, 5, Int("42")),
|
||
Located::new(0, 0, 5, 6, Plus),
|
||
Located::new(0, 0, 6, 9, Int("534")),
|
||
));
|
||
let outer = arena.alloc((
|
||
Located::new(0, 0, 0, 2, Int("31")),
|
||
Located::new(0, 0, 2, 3, Star),
|
||
Located::new(0, 0, 3, 9, BinOp(inner)),
|
||
));
|
||
let expected = BinOp(outer);
|
||
let actual = parse_with(&arena, "31*42+534");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn equals() {
|
||
let arena = Bump::new();
|
||
let var1 = Var {
|
||
module_name: "",
|
||
ident: "x",
|
||
};
|
||
let var2 = Var {
|
||
module_name: "",
|
||
ident: "y",
|
||
};
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, var1),
|
||
Located::new(0, 0, 1, 3, Equals),
|
||
Located::new(0, 0, 3, 4, var2),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "x==y");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn equals_with_spaces() {
|
||
let arena = Bump::new();
|
||
let var1 = Var {
|
||
module_name: "",
|
||
ident: "x",
|
||
};
|
||
let var2 = Var {
|
||
module_name: "",
|
||
ident: "y",
|
||
};
|
||
let tuple = arena.alloc((
|
||
Located::new(0, 0, 0, 1, var1),
|
||
Located::new(0, 0, 2, 4, Equals),
|
||
Located::new(0, 0, 5, 6, var2),
|
||
));
|
||
let expected = BinOp(tuple);
|
||
let actual = parse_with(&arena, "x == y");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
// VAR
|
||
|
||
#[test]
|
||
fn basic_var() {
|
||
let arena = Bump::new();
|
||
let expected = Var {
|
||
module_name: "",
|
||
ident: "whee",
|
||
};
|
||
let actual = parse_with(&arena, "whee");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn parenthetical_var() {
|
||
let arena = Bump::new();
|
||
let expected = ParensAround(arena.alloc(Var {
|
||
module_name: "",
|
||
ident: "whee",
|
||
}));
|
||
let actual = parse_with(&arena, "(whee)");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn qualified_var() {
|
||
let arena = Bump::new();
|
||
let expected = Var {
|
||
module_name: "One.Two",
|
||
ident: "whee",
|
||
};
|
||
let actual = parse_with(&arena, "One.Two.whee");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
// TAG
|
||
|
||
#[test]
|
||
fn basic_global_tag() {
|
||
let arena = Bump::new();
|
||
let expected = Expr::GlobalTag("Whee");
|
||
let actual = parse_with(&arena, "Whee");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn basic_private_tag() {
|
||
let arena = Bump::new();
|
||
let expected = Expr::PrivateTag("@Whee");
|
||
let actual = parse_with(&arena, "@Whee");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn apply_private_tag() {
|
||
let arena = Bump::new();
|
||
let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Int("12")));
|
||
let arg2 = arena.alloc(Located::new(0, 0, 9, 11, Int("34")));
|
||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||
let expected = Expr::Apply(
|
||
arena.alloc(Located::new(0, 0, 0, 5, Expr::PrivateTag("@Whee"))),
|
||
args,
|
||
CalledVia::Space,
|
||
);
|
||
let actual = parse_with(&arena, "@Whee 12 34");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn apply_global_tag() {
|
||
let arena = Bump::new();
|
||
let arg1 = arena.alloc(Located::new(0, 0, 5, 7, Int("12")));
|
||
let arg2 = arena.alloc(Located::new(0, 0, 8, 10, Int("34")));
|
||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||
let expected = Expr::Apply(
|
||
arena.alloc(Located::new(0, 0, 0, 4, Expr::GlobalTag("Whee"))),
|
||
args,
|
||
CalledVia::Space,
|
||
);
|
||
let actual = parse_with(&arena, "Whee 12 34");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn apply_parenthetical_global_tag_args() {
|
||
let arena = Bump::new();
|
||
let int1 = ParensAround(arena.alloc(Int("12")));
|
||
let int2 = ParensAround(arena.alloc(Int("34")));
|
||
let arg1 = arena.alloc(Located::new(0, 0, 6, 8, int1));
|
||
let arg2 = arena.alloc(Located::new(0, 0, 11, 13, int2));
|
||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||
let expected = Expr::Apply(
|
||
arena.alloc(Located::new(0, 0, 0, 4, Expr::GlobalTag("Whee"))),
|
||
args,
|
||
CalledVia::Space,
|
||
);
|
||
let actual = parse_with(&arena, "Whee (12) (34)");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn qualified_global_tag() {
|
||
let arena = Bump::new();
|
||
let expected = Expr::MalformedIdent("One.Two.Whee");
|
||
let actual = parse_with(&arena, "One.Two.Whee");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
// TODO restore this test - it fails, but is not worth fixing right now.
|
||
// #[test]
|
||
// fn qualified_private_tag() {
|
||
// let arena = Bump::new();
|
||
// let expected = Expr::MalformedIdent("One.Two.@Whee");
|
||
// let actual = parse_with(&arena, "One.Two.@Whee");
|
||
|
||
// assert_eq!(Ok(expected), actual);
|
||
// }
|
||
|
||
#[test]
|
||
fn tag_pattern() {
|
||
let arena = Bump::new();
|
||
let pattern = Located::new(0, 0, 1, 6, Pattern::GlobalTag("Thing"));
|
||
let patterns = bumpalo::vec![in &arena; pattern];
|
||
let expected = Closure(
|
||
arena.alloc(patterns),
|
||
arena.alloc(Located::new(0, 0, 10, 12, Int("42"))),
|
||
);
|
||
let actual = parse_with(&arena, "\\Thing -> 42");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn private_qualified_tag() {
|
||
let arena = Bump::new();
|
||
let expected = Expr::MalformedIdent("@One.Two.Whee");
|
||
let actual = parse_with(&arena, "@One.Two.Whee");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
// LISTS
|
||
|
||
#[test]
|
||
fn empty_list() {
|
||
let arena = Bump::new();
|
||
let elems = Vec::new_in(&arena);
|
||
let expected = List(elems);
|
||
let actual = parse_with(&arena, "[]");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn spaces_inside_empty_list() {
|
||
// This is a regression test!
|
||
let arena = Bump::new();
|
||
let elems = Vec::new_in(&arena);
|
||
let expected = List(elems);
|
||
let actual = parse_with(&arena, "[ ]");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn packed_singleton_list() {
|
||
let arena = Bump::new();
|
||
let elems = bumpalo::vec![in &arena; &*arena.alloc(Located::new(0, 0, 1, 2, Int("1")))];
|
||
let expected = List(elems);
|
||
let actual = parse_with(&arena, "[1]");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn spaced_singleton_list() {
|
||
let arena = Bump::new();
|
||
let elems = bumpalo::vec![in &arena; &*arena.alloc(Located::new(0, 0, 2, 3, Int("1")))];
|
||
let expected = List(elems);
|
||
let actual = parse_with(&arena, "[ 1 ]");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
// FIELD ACCESS
|
||
|
||
#[test]
|
||
fn basic_field() {
|
||
let arena = Bump::new();
|
||
let var = Var {
|
||
module_name: "",
|
||
ident: "rec",
|
||
};
|
||
let expected = Access(arena.alloc(var), "field");
|
||
let actual = parse_with(&arena, "rec.field");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn parenthetical_basic_field() {
|
||
let arena = Bump::new();
|
||
let paren_var = ParensAround(arena.alloc(Var {
|
||
module_name: "",
|
||
ident: "rec",
|
||
}));
|
||
let expected = Access(arena.alloc(paren_var), "field");
|
||
let actual = parse_with(&arena, "(rec).field");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn parenthetical_field_qualified_var() {
|
||
let arena = Bump::new();
|
||
let paren_var = ParensAround(arena.alloc(Var {
|
||
module_name: "One.Two",
|
||
ident: "rec",
|
||
}));
|
||
let expected = Access(arena.alloc(paren_var), "field");
|
||
let actual = parse_with(&arena, "(One.Two.rec).field");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn multiple_fields() {
|
||
let arena = Bump::new();
|
||
let var = Var {
|
||
module_name: "",
|
||
ident: "rec",
|
||
};
|
||
let expected = Access(
|
||
arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")),
|
||
"ghi",
|
||
);
|
||
let actual = parse_with(&arena, "rec.abc.def.ghi");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn qualified_field() {
|
||
let arena = Bump::new();
|
||
let var = Var {
|
||
module_name: "One.Two",
|
||
ident: "rec",
|
||
};
|
||
let expected = Access(
|
||
arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")),
|
||
"ghi",
|
||
);
|
||
let actual = parse_with(&arena, "One.Two.rec.abc.def.ghi");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
// APPLY
|
||
|
||
#[test]
|
||
fn basic_apply() {
|
||
let arena = Bump::new();
|
||
let arg = arena.alloc(Located::new(0, 0, 5, 6, Int("1")));
|
||
let args = bumpalo::vec![in &arena; &*arg];
|
||
let expected = Expr::Apply(
|
||
arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
0,
|
||
4,
|
||
Var {
|
||
module_name: "",
|
||
ident: "whee",
|
||
},
|
||
)),
|
||
args,
|
||
CalledVia::Space,
|
||
);
|
||
let actual = parse_with(&arena, "whee 1");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn apply_two_args() {
|
||
let arena = Bump::new();
|
||
let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Int("12")));
|
||
let arg2 = arena.alloc(Located::new(0, 0, 10, 12, Int("34")));
|
||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||
let expected = Expr::Apply(
|
||
arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
0,
|
||
4,
|
||
Var {
|
||
module_name: "",
|
||
ident: "whee",
|
||
},
|
||
)),
|
||
args,
|
||
CalledVia::Space,
|
||
);
|
||
let actual = parse_with(&arena, "whee 12 34");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn apply_three_args() {
|
||
let arena = Bump::new();
|
||
let arg1 = arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
2,
|
||
3,
|
||
Var {
|
||
module_name: "",
|
||
ident: "b",
|
||
},
|
||
));
|
||
let arg2 = arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
4,
|
||
5,
|
||
Var {
|
||
module_name: "",
|
||
ident: "c",
|
||
},
|
||
));
|
||
let arg3 = arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
6,
|
||
7,
|
||
Var {
|
||
module_name: "",
|
||
ident: "d",
|
||
},
|
||
));
|
||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2, &*arg3];
|
||
let expected = Expr::Apply(
|
||
arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
0,
|
||
1,
|
||
Var {
|
||
module_name: "",
|
||
ident: "a",
|
||
},
|
||
)),
|
||
args,
|
||
CalledVia::Space,
|
||
);
|
||
let actual = parse_with(&arena, "a b c d");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn parenthetical_apply() {
|
||
let arena = Bump::new();
|
||
let arg = arena.alloc(Located::new(0, 0, 7, 8, Int("1")));
|
||
let args = bumpalo::vec![in &arena; &*arg];
|
||
let parens_var = Expr::ParensAround(arena.alloc(Var {
|
||
module_name: "",
|
||
ident: "whee",
|
||
}));
|
||
let expected = Expr::Apply(
|
||
arena.alloc(Located::new(0, 0, 1, 5, parens_var)),
|
||
args,
|
||
CalledVia::Space,
|
||
);
|
||
let actual = parse_with(&arena, "(whee) 1");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
// UNARY OPERATORS
|
||
|
||
#[test]
|
||
fn unary_negation() {
|
||
let arena = Bump::new();
|
||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate);
|
||
let loc_arg1_expr = Located::new(
|
||
0,
|
||
0,
|
||
1,
|
||
4,
|
||
Var {
|
||
module_name: "",
|
||
ident: "foo",
|
||
},
|
||
);
|
||
let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op);
|
||
let actual = parse_with(&arena, "-foo");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn unary_not() {
|
||
let arena = Bump::new();
|
||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Not);
|
||
let loc_arg1_expr = Located::new(
|
||
0,
|
||
0,
|
||
1,
|
||
5,
|
||
Var {
|
||
module_name: "",
|
||
ident: "blah",
|
||
},
|
||
);
|
||
let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op);
|
||
let actual = parse_with(&arena, "!blah");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn apply_unary_negation() {
|
||
let arena = Bump::new();
|
||
let arg1 = arena.alloc(Located::new(0, 0, 7, 9, Int("12")));
|
||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate);
|
||
let arg2 = arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
10,
|
||
13,
|
||
Var {
|
||
module_name: "",
|
||
ident: "foo",
|
||
},
|
||
));
|
||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||
let apply_expr = Expr::Apply(
|
||
arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
1,
|
||
5,
|
||
Var {
|
||
module_name: "",
|
||
ident: "whee",
|
||
},
|
||
)),
|
||
args,
|
||
CalledVia::Space,
|
||
);
|
||
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op);
|
||
let actual = parse_with(&arena, "-whee 12 foo");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn apply_unary_not() {
|
||
let arena = Bump::new();
|
||
let arg1 = arena.alloc(Located::new(0, 0, 7, 9, Int("12")));
|
||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Not);
|
||
let arg2 = arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
10,
|
||
13,
|
||
Var {
|
||
module_name: "",
|
||
ident: "foo",
|
||
},
|
||
));
|
||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||
let apply_expr = Expr::Apply(
|
||
arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
1,
|
||
5,
|
||
Var {
|
||
module_name: "",
|
||
ident: "whee",
|
||
},
|
||
)),
|
||
args,
|
||
CalledVia::Space,
|
||
);
|
||
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op);
|
||
let actual = parse_with(&arena, "!whee 12 foo");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn unary_negation_with_parens() {
|
||
let arena = Bump::new();
|
||
let arg1 = arena.alloc(Located::new(0, 0, 8, 10, Int("12")));
|
||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate);
|
||
let arg2 = arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
11,
|
||
14,
|
||
Var {
|
||
module_name: "",
|
||
ident: "foo",
|
||
},
|
||
));
|
||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||
let apply_expr = Expr::ParensAround(arena.alloc(Expr::Apply(
|
||
arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
2,
|
||
6,
|
||
Var {
|
||
module_name: "",
|
||
ident: "whee",
|
||
},
|
||
)),
|
||
args,
|
||
CalledVia::Space,
|
||
)));
|
||
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op);
|
||
let actual = parse_with(&arena, "-(whee 12 foo)");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn unary_not_with_parens() {
|
||
let arena = Bump::new();
|
||
let arg1 = arena.alloc(Located::new(0, 0, 8, 10, Int("12")));
|
||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Not);
|
||
let arg2 = arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
11,
|
||
14,
|
||
Var {
|
||
module_name: "",
|
||
ident: "foo",
|
||
},
|
||
));
|
||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||
let apply_expr = Expr::ParensAround(arena.alloc(Expr::Apply(
|
||
arena.alloc(Located::new(
|
||
0,
|
||
0,
|
||
2,
|
||
6,
|
||
Var {
|
||
module_name: "",
|
||
ident: "whee",
|
||
},
|
||
)),
|
||
args,
|
||
CalledVia::Space,
|
||
)));
|
||
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op);
|
||
let actual = parse_with(&arena, "!(whee 12 foo)");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn unary_negation_arg() {
|
||
let arena = Bump::new();
|
||
let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Int("12")));
|
||
let loc_op = Located::new(0, 0, 9, 10, UnaryOp::Negate);
|
||
let var1 = Var {
|
||
module_name: "",
|
||
ident: "foo",
|
||
};
|
||
let loc_arg1_expr = Located::new(0, 0, 10, 13, var1);
|
||
let arg_op = UnaryOp(arena.alloc(loc_arg1_expr), loc_op);
|
||
let arg2 = arena.alloc(Located::new(0, 0, 9, 13, arg_op));
|
||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||
let var2 = Var {
|
||
module_name: "",
|
||
ident: "whee",
|
||
};
|
||
let expected = Expr::Apply(
|
||
arena.alloc(Located::new(0, 0, 0, 4, var2)),
|
||
args,
|
||
CalledVia::Space,
|
||
);
|
||
let actual = parse_with(&arena, "whee 12 -foo");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
// CLOSURE
|
||
|
||
#[test]
|
||
fn single_arg_closure() {
|
||
let arena = Bump::new();
|
||
let pattern = Located::new(0, 0, 1, 2, Identifier("a"));
|
||
let patterns = bumpalo::vec![in &arena; pattern];
|
||
let expected = Closure(
|
||
arena.alloc(patterns),
|
||
arena.alloc(Located::new(0, 0, 6, 8, Int("42"))),
|
||
);
|
||
let actual = parse_with(&arena, "\\a -> 42");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn single_underscore_closure() {
|
||
let arena = Bump::new();
|
||
let pattern = Located::new(0, 0, 1, 2, Underscore);
|
||
let patterns = bumpalo::vec![in &arena; pattern];
|
||
let expected = Closure(
|
||
arena.alloc(patterns),
|
||
arena.alloc(Located::new(0, 0, 6, 8, Int("42"))),
|
||
);
|
||
let actual = parse_with(&arena, "\\_ -> 42");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn malformed_ident_due_to_underscore() {
|
||
// This is a regression test against a bug where if you included an
|
||
// underscore in an argument name, it would parse as three arguments
|
||
// (and would ignore the underscore as if it had been blank space).
|
||
let arena = Bump::new();
|
||
let actual = parse_with(&arena, "\\the_answer -> 42");
|
||
|
||
assert_eq!(Ok(MalformedClosure), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn two_arg_closure() {
|
||
let arena = Bump::new();
|
||
let arg1 = Located::new(0, 0, 1, 2, Identifier("a"));
|
||
let arg2 = Located::new(0, 0, 4, 5, Identifier("b"));
|
||
let patterns = bumpalo::vec![in &arena; arg1, arg2];
|
||
let expected = Closure(
|
||
arena.alloc(patterns),
|
||
arena.alloc(Located::new(0, 0, 9, 11, Int("42"))),
|
||
);
|
||
let actual = parse_with(&arena, "\\a, b -> 42");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn three_arg_closure() {
|
||
let arena = Bump::new();
|
||
let arg1 = Located::new(0, 0, 1, 2, Identifier("a"));
|
||
let arg2 = Located::new(0, 0, 4, 5, Identifier("b"));
|
||
let arg3 = Located::new(0, 0, 7, 8, Identifier("c"));
|
||
let patterns = bumpalo::vec![in &arena; arg1, arg2, arg3];
|
||
let expected = Closure(
|
||
arena.alloc(patterns),
|
||
arena.alloc(Located::new(0, 0, 12, 14, Int("42"))),
|
||
);
|
||
let actual = parse_with(&arena, "\\a, b, c -> 42");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn closure_with_underscores() {
|
||
let arena = Bump::new();
|
||
let underscore1 = Located::new(0, 0, 1, 2, Underscore);
|
||
let underscore2 = Located::new(0, 0, 4, 5, Underscore);
|
||
let patterns = bumpalo::vec![in &arena; underscore1, underscore2];
|
||
let expected = Closure(
|
||
arena.alloc(patterns),
|
||
arena.alloc(Located::new(0, 0, 9, 11, Int("42"))),
|
||
);
|
||
let actual = parse_with(&arena, "\\_, _ -> 42");
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
// DEF
|
||
|
||
#[test]
|
||
fn one_def() {
|
||
let arena = Bump::new();
|
||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||
let def = Def::Body(
|
||
arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))),
|
||
arena.alloc(Located::new(1, 1, 2, 3, Int("5"))),
|
||
);
|
||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 1, def));
|
||
let defs = bumpalo::vec![in &arena; loc_def];
|
||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||
let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")];
|
||
let expected = Expr::SpaceBefore(
|
||
arena.alloc(Defs(defs, arena.alloc(loc_ret))),
|
||
reset_indentation.into_bump_slice(),
|
||
);
|
||
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"# leading comment
|
||
x=5
|
||
|
||
42
|
||
"#
|
||
),
|
||
expected,
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn one_spaced_def() {
|
||
let arena = Bump::new();
|
||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||
let def = Def::Body(
|
||
arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))),
|
||
arena.alloc(Located::new(1, 1, 4, 5, Int("5"))),
|
||
);
|
||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 1, def));
|
||
let defs = bumpalo::vec![in &arena; loc_def];
|
||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||
let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")];
|
||
let expected = Expr::SpaceBefore(
|
||
arena.alloc(Defs(defs, arena.alloc(loc_ret))),
|
||
reset_indentation.into_bump_slice(),
|
||
);
|
||
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"# leading comment
|
||
x = 5
|
||
|
||
42
|
||
"#
|
||
),
|
||
expected,
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn two_spaced_def() {
|
||
let arena = Bump::new();
|
||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||
let newline = bumpalo::vec![in &arena; Newline];
|
||
let def1 = Def::Body(
|
||
arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))),
|
||
arena.alloc(Located::new(1, 1, 4, 5, Int("5"))),
|
||
);
|
||
let loc_def1 = &*arena.alloc(Located::new(1, 1, 0, 1, def1));
|
||
let def2 = Def::SpaceBefore(
|
||
&*arena.alloc(Def::Body(
|
||
arena.alloc(Located::new(2, 2, 0, 1, Identifier("y"))),
|
||
arena.alloc(Located::new(2, 2, 4, 5, Int("6"))),
|
||
)),
|
||
newline.into_bump_slice(),
|
||
);
|
||
let loc_def2 = &*arena.alloc(Located::new(2, 2, 0, 5, def2));
|
||
// NOTE: The first def always gets reordered to the end (because it
|
||
// gets added by .push(), since that's more efficient and since
|
||
// canonicalization is going to re-sort these all anyway.)
|
||
let defs = bumpalo::vec![in &arena; loc_def2, loc_def1];
|
||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||
let loc_ret = Located::new(4, 4, 0, 2, ret);
|
||
let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")];
|
||
let expected = Expr::SpaceBefore(
|
||
arena.alloc(Defs(defs, arena.alloc(loc_ret))),
|
||
reset_indentation.into_bump_slice(),
|
||
);
|
||
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"# leading comment
|
||
x = 5
|
||
y = 6
|
||
|
||
42
|
||
"#
|
||
),
|
||
expected,
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn record_destructure_def() {
|
||
let arena = Bump::new();
|
||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||
let newline = bumpalo::vec![in &arena; Newline];
|
||
let fields = bumpalo::vec![in &arena;
|
||
Located::new(1, 1, 2, 3, Identifier("x")),
|
||
Located::new(1, 1, 5, 7, Identifier("y"))
|
||
];
|
||
let def1 = Def::Body(
|
||
arena.alloc(Located::new(
|
||
1,
|
||
1,
|
||
1,
|
||
8,
|
||
RecordDestructure(fields.into_bump_slice()),
|
||
)),
|
||
arena.alloc(Located::new(1, 1, 11, 12, Int("5"))),
|
||
);
|
||
let loc_def1 = &*arena.alloc(Located::new(1, 1, 1, 8, def1));
|
||
let def2 = Def::SpaceBefore(
|
||
&*arena.alloc(Def::Body(
|
||
arena.alloc(Located::new(2, 2, 0, 1, Identifier("y"))),
|
||
arena.alloc(Located::new(2, 2, 4, 5, Int("6"))),
|
||
)),
|
||
newline.into_bump_slice(),
|
||
);
|
||
let loc_def2 = &*arena.alloc(Located::new(2, 2, 0, 5, def2));
|
||
// NOTE: The first def always gets reordered to the end (because it
|
||
// gets added by .push(), since that's more efficient and since
|
||
// canonicalization is going to re-sort these all anyway.)
|
||
let defs = bumpalo::vec![in &arena; loc_def2, loc_def1];
|
||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||
let loc_ret = Located::new(4, 4, 0, 2, ret);
|
||
let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")];
|
||
let expected = Expr::SpaceBefore(
|
||
arena.alloc(Defs(defs, arena.alloc(loc_ret))),
|
||
reset_indentation.into_bump_slice(),
|
||
);
|
||
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"# leading comment
|
||
{ x, y } = 5
|
||
y = 6
|
||
|
||
42
|
||
"#
|
||
),
|
||
expected,
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn type_signature_def() {
|
||
let arena = Bump::new();
|
||
let newline = bumpalo::vec![in &arena; Newline];
|
||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||
let applied_ann = TypeAnnotation::Apply("", "Int", &[]);
|
||
let signature = Def::Annotation(
|
||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||
Located::new(0, 0, 6, 9, applied_ann),
|
||
);
|
||
let def = Def::Body(
|
||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||
arena.alloc(Located::new(1, 1, 6, 7, Int("4"))),
|
||
);
|
||
let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 7, spaced_def));
|
||
|
||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"
|
||
foo : Int
|
||
foo = 4
|
||
|
||
42
|
||
"#
|
||
),
|
||
expected,
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn parse_as_ann() {
|
||
let arena = Bump::new();
|
||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||
let loc_x = Located::new(0, 0, 18, 19, TypeAnnotation::BoundVariable("x"));
|
||
let loc_y = Located::new(0, 0, 20, 21, TypeAnnotation::BoundVariable("y"));
|
||
let loc_a = Located::new(0, 0, 30, 31, TypeAnnotation::BoundVariable("a"));
|
||
let loc_b = Located::new(0, 0, 32, 33, TypeAnnotation::BoundVariable("b"));
|
||
let applied_ann_args = bumpalo::vec![in &arena; loc_x, loc_y];
|
||
let applied_ann =
|
||
TypeAnnotation::Apply("Foo.Bar", "Baz", applied_ann_args.into_bump_slice());
|
||
let loc_applied_ann = &*arena.alloc(Located::new(0, 0, 6, 21, applied_ann));
|
||
let applied_as_args = bumpalo::vec![in &arena; loc_a, loc_b];
|
||
let applied_as = TypeAnnotation::Apply("", "Blah", applied_as_args.into_bump_slice());
|
||
let loc_applied_as = &*arena.alloc(Located::new(0, 0, 25, 33, applied_as));
|
||
let as_ann = TypeAnnotation::As(loc_applied_ann, &[], loc_applied_as);
|
||
let signature = Def::Annotation(
|
||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||
Located::new(0, 0, 6, 33, as_ann),
|
||
);
|
||
|
||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||
let defs = bumpalo::vec![in &arena; loc_ann];
|
||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||
let loc_ret = Located::new(2, 2, 0, 2, ret);
|
||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"
|
||
foo : Foo.Bar.Baz x y as Blah a b
|
||
|
||
42
|
||
"#
|
||
),
|
||
expected,
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn parse_alias() {
|
||
let arena = Bump::new();
|
||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||
let loc_x = Located::new(0, 0, 23, 24, TypeAnnotation::BoundVariable("x"));
|
||
let loc_y = Located::new(0, 0, 25, 26, TypeAnnotation::BoundVariable("y"));
|
||
let loc_a = Located::new(0, 0, 5, 6, Pattern::Identifier("a"));
|
||
let loc_b = Located::new(0, 0, 7, 8, Pattern::Identifier("b"));
|
||
let applied_ann_args = bumpalo::vec![in &arena; loc_a, loc_b];
|
||
let applied_alias_args = bumpalo::vec![in &arena; loc_x, loc_y];
|
||
let applied_alias =
|
||
TypeAnnotation::Apply("Foo.Bar", "Baz", applied_alias_args.into_bump_slice());
|
||
let signature = Def::Alias {
|
||
name: Located::new(0, 0, 0, 4, "Blah"),
|
||
vars: applied_ann_args.into_bump_slice(),
|
||
ann: Located::new(0, 0, 11, 26, applied_alias),
|
||
};
|
||
|
||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 4, signature));
|
||
let defs = bumpalo::vec![in &arena; loc_ann];
|
||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||
let loc_ret = Located::new(2, 2, 0, 2, ret);
|
||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"
|
||
Blah a b : Foo.Bar.Baz x y
|
||
|
||
42
|
||
"#
|
||
),
|
||
expected,
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn type_signature_function_def() {
|
||
use TypeAnnotation;
|
||
let arena = Bump::new();
|
||
let newline = bumpalo::vec![in &arena; Newline];
|
||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||
|
||
let int_type = TypeAnnotation::Apply("", "Int", &[]);
|
||
let float_type = TypeAnnotation::Apply("", "Float", &[]);
|
||
let bool_type = TypeAnnotation::Apply("", "Bool", &[]);
|
||
|
||
let arguments = bumpalo::vec![in &arena;
|
||
Located::new(0, 0, 6, 9, int_type),
|
||
Located::new(0, 0, 11, 16, float_type)
|
||
];
|
||
let return_type = Located::new(0, 0, 20, 24, bool_type);
|
||
let fn_ann = TypeAnnotation::Function(&arguments, &return_type);
|
||
let signature = Def::Annotation(
|
||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||
Located::new(0, 0, 20, 24, fn_ann),
|
||
);
|
||
|
||
let args = bumpalo::vec![in &arena;
|
||
Located::new(1,1,7,8, Identifier("x")),
|
||
Located::new(1,1,10,11, Underscore)
|
||
];
|
||
let body = Located::new(1, 1, 15, 17, Int("42"));
|
||
|
||
let closure = Expr::Closure(&args, &body);
|
||
|
||
let def = Def::Body(
|
||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||
arena.alloc(Located::new(1, 1, 6, 17, closure)),
|
||
);
|
||
let spaced = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 17, spaced));
|
||
|
||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"
|
||
foo : Int, Float -> Bool
|
||
foo = \x, _ -> 42
|
||
|
||
42
|
||
"#
|
||
),
|
||
expected,
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn ann_private_open_union() {
|
||
let arena = Bump::new();
|
||
let newline = bumpalo::vec![in &arena; Newline];
|
||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||
let tag1 = Tag::Private {
|
||
name: Located::new(0, 0, 8, 13, "@True"),
|
||
args: &[],
|
||
};
|
||
let tag2arg1 = Located::new(0, 0, 24, 27, TypeAnnotation::Apply("", "Two", &[]));
|
||
let tag2arg2 = Located::new(0, 0, 28, 34, TypeAnnotation::Apply("", "Things", &[]));
|
||
let tag2args = bumpalo::vec![in &arena; tag2arg1, tag2arg2];
|
||
let tag2 = Tag::Private {
|
||
name: Located::new(0, 0, 15, 23, "@Perhaps"),
|
||
args: tag2args.into_bump_slice(),
|
||
};
|
||
let tags = bumpalo::vec![in &arena;
|
||
Located::new(0, 0, 8, 13, tag1),
|
||
Located::new(0, 0, 15, 34, tag2)
|
||
];
|
||
let loc_wildcard = Located::new(0, 0, 36, 37, TypeAnnotation::Wildcard);
|
||
let applied_ann = TypeAnnotation::TagUnion {
|
||
tags: tags.into_bump_slice(),
|
||
ext: Some(arena.alloc(loc_wildcard)),
|
||
};
|
||
let signature = Def::Annotation(
|
||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||
Located::new(0, 0, 6, 37, applied_ann),
|
||
);
|
||
let def = Def::Body(
|
||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||
arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))),
|
||
);
|
||
let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def));
|
||
|
||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"
|
||
foo : [ @True, @Perhaps Two Things ]*
|
||
foo = True
|
||
|
||
42
|
||
"#
|
||
),
|
||
expected,
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn ann_private_closed_union() {
|
||
let arena = Bump::new();
|
||
let newline = bumpalo::vec![in &arena; Newline];
|
||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||
let tag1 = Tag::Private {
|
||
name: Located::new(0, 0, 8, 13, "@True"),
|
||
args: &[],
|
||
};
|
||
let tag2arg = Located::new(0, 0, 24, 29, TypeAnnotation::Apply("", "Thing", &[]));
|
||
let tag2args = bumpalo::vec![in &arena; tag2arg];
|
||
let tag2 = Tag::Private {
|
||
name: Located::new(0, 0, 15, 23, "@Perhaps"),
|
||
args: tag2args.into_bump_slice(),
|
||
};
|
||
let tags = bumpalo::vec![in &arena;
|
||
Located::new(0, 0, 8, 13, tag1),
|
||
Located::new(0, 0, 15, 29, tag2)
|
||
];
|
||
let applied_ann = TypeAnnotation::TagUnion {
|
||
tags: tags.into_bump_slice(),
|
||
ext: None,
|
||
};
|
||
let signature = Def::Annotation(
|
||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||
Located::new(0, 0, 6, 31, applied_ann),
|
||
);
|
||
let def = Def::Body(
|
||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||
arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))),
|
||
);
|
||
let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def));
|
||
|
||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"
|
||
foo : [ @True, @Perhaps Thing ]
|
||
foo = True
|
||
|
||
42
|
||
"#
|
||
),
|
||
expected,
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn ann_global_open_union() {
|
||
let arena = Bump::new();
|
||
let newline = bumpalo::vec![in &arena; Newline];
|
||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||
let tag1 = Tag::Global {
|
||
name: Located::new(0, 0, 8, 12, "True"),
|
||
args: &[],
|
||
};
|
||
let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply("", "Thing", &[]));
|
||
let tag2args = bumpalo::vec![in &arena; tag2arg];
|
||
let tag2 = Tag::Global {
|
||
name: Located::new(0, 0, 14, 21, "Perhaps"),
|
||
args: tag2args.into_bump_slice(),
|
||
};
|
||
let tags = bumpalo::vec![in &arena;
|
||
Located::new(0, 0, 8, 12, tag1),
|
||
Located::new(0, 0, 14, 27, tag2)
|
||
];
|
||
let loc_wildcard = Located::new(0, 0, 29, 30, TypeAnnotation::Wildcard);
|
||
let applied_ann = TypeAnnotation::TagUnion {
|
||
tags: tags.into_bump_slice(),
|
||
ext: Some(arena.alloc(loc_wildcard)),
|
||
};
|
||
let signature = Def::Annotation(
|
||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||
Located::new(0, 0, 6, 30, applied_ann),
|
||
);
|
||
let def = Def::Body(
|
||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||
arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))),
|
||
);
|
||
let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def));
|
||
|
||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"
|
||
foo : [ True, Perhaps Thing ]*
|
||
foo = True
|
||
|
||
42
|
||
"#
|
||
),
|
||
expected,
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn ann_global_closed_union() {
|
||
let arena = Bump::new();
|
||
let newline = bumpalo::vec![in &arena; Newline];
|
||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||
let tag1 = Tag::Global {
|
||
name: Located::new(0, 0, 8, 12, "True"),
|
||
args: &[],
|
||
};
|
||
let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply("", "Thing", &[]));
|
||
let tag2args = bumpalo::vec![in &arena; tag2arg];
|
||
let tag2 = Tag::Global {
|
||
name: Located::new(0, 0, 14, 21, "Perhaps"),
|
||
args: tag2args.into_bump_slice(),
|
||
};
|
||
let tags = bumpalo::vec![in &arena;
|
||
Located::new(0, 0, 8, 12, tag1),
|
||
Located::new(0, 0, 14, 27, tag2)
|
||
];
|
||
let applied_ann = TypeAnnotation::TagUnion {
|
||
tags: tags.into_bump_slice(),
|
||
ext: None,
|
||
};
|
||
let signature = Def::Annotation(
|
||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||
Located::new(0, 0, 6, 29, applied_ann),
|
||
);
|
||
let def = Def::Body(
|
||
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||
arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))),
|
||
);
|
||
let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice());
|
||
let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def));
|
||
|
||
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||
|
||
assert_parses_to(
|
||
indoc!(
|
||
r#"
|
||
foo : [ True, Perhaps Thing ]
|
||
foo = True
|
||
|
||
42
|
||
"#
|
||
),
|
||
expected,
|
||
);
|
||
}
|
||
|
||
// WHEN
|
||
|
||
#[test]
|
||
fn two_branch_when() {
|
||
let arena = Bump::new();
|
||
let newlines = bumpalo::vec![in &arena; Newline];
|
||
let pattern1 =
|
||
Pattern::SpaceBefore(arena.alloc(StrLiteral("blah")), newlines.into_bump_slice());
|
||
let loc_pattern1 = Located::new(1, 1, 1, 7, pattern1);
|
||
let expr1 = Int("1");
|
||
let loc_expr1 = Located::new(1, 1, 11, 12, expr1);
|
||
let branch1 = &*arena.alloc(WhenBranch {
|
||
patterns: bumpalo::vec![in &arena;loc_pattern1],
|
||
value: loc_expr1,
|
||
guard: None,
|
||
});
|
||
let newlines = bumpalo::vec![in &arena; Newline];
|
||
let pattern2 =
|
||
Pattern::SpaceBefore(arena.alloc(StrLiteral("mise")), newlines.into_bump_slice());
|
||
let loc_pattern2 = Located::new(2, 2, 1, 7, pattern2);
|
||
let expr2 = Int("2");
|
||
let loc_expr2 = Located::new(2, 2, 11, 12, expr2);
|
||
let branch2 = &*arena.alloc(WhenBranch {
|
||
patterns: bumpalo::vec![in &arena;loc_pattern2 ],
|
||
value: loc_expr2,
|
||
guard: None,
|
||
});
|
||
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
||
let var = Var {
|
||
module_name: "",
|
||
ident: "x",
|
||
};
|
||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||
let actual = parse_with(
|
||
&arena,
|
||
indoc!(
|
||
r#"
|
||
when x is
|
||
"blah" -> 1
|
||
"mise" -> 2
|
||
"#
|
||
),
|
||
);
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn when_with_numbers() {
|
||
let arena = Bump::new();
|
||
let newlines = bumpalo::vec![in &arena; Newline];
|
||
let pattern1 =
|
||
Pattern::SpaceBefore(arena.alloc(IntLiteral("1")), newlines.into_bump_slice());
|
||
let loc_pattern1 = Located::new(1, 1, 1, 2, pattern1);
|
||
let expr1 = Int("2");
|
||
let loc_expr1 = Located::new(1, 1, 6, 7, expr1);
|
||
let branch1 = &*arena.alloc(WhenBranch {
|
||
patterns: bumpalo::vec![in &arena;loc_pattern1],
|
||
value: loc_expr1,
|
||
guard: None,
|
||
});
|
||
let newlines = bumpalo::vec![in &arena; Newline];
|
||
let pattern2 =
|
||
Pattern::SpaceBefore(arena.alloc(IntLiteral("3")), newlines.into_bump_slice());
|
||
let loc_pattern2 = Located::new(2, 2, 1, 2, pattern2);
|
||
let expr2 = Int("4");
|
||
let loc_expr2 = Located::new(2, 2, 6, 7, expr2);
|
||
let branch2 = &*arena.alloc(WhenBranch {
|
||
patterns: bumpalo::vec![in &arena;loc_pattern2],
|
||
value: loc_expr2,
|
||
guard: None,
|
||
});
|
||
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
||
let var = Var {
|
||
module_name: "",
|
||
ident: "x",
|
||
};
|
||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||
let actual = parse_with(
|
||
&arena,
|
||
indoc!(
|
||
r#"
|
||
when x is
|
||
1 -> 2
|
||
3 -> 4
|
||
"#
|
||
),
|
||
);
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn when_with_records() {
|
||
let arena = Bump::new();
|
||
let newlines = bumpalo::vec![in &arena; Newline];
|
||
let identifiers1 = bumpalo::vec![in &arena; Located::new(1, 1, 3, 4, Identifier("y")) ];
|
||
let pattern1 = Pattern::SpaceBefore(
|
||
arena.alloc(RecordDestructure(identifiers1.into_bump_slice())),
|
||
newlines.into_bump_slice(),
|
||
);
|
||
let loc_pattern1 = Located::new(1, 1, 1, 6, pattern1);
|
||
let expr1 = Int("2");
|
||
let loc_expr1 = Located::new(1, 1, 10, 11, expr1);
|
||
let branch1 = &*arena.alloc(WhenBranch {
|
||
patterns: bumpalo::vec![in &arena;loc_pattern1 ],
|
||
value: loc_expr1,
|
||
guard: None,
|
||
});
|
||
let newlines = bumpalo::vec![in &arena; Newline];
|
||
let identifiers2 = bumpalo::vec![in &arena; Located::new(2, 2, 3, 4, Identifier("z")), Located::new(2, 2, 6, 7, Identifier("w")) ];
|
||
let pattern2 = Pattern::SpaceBefore(
|
||
arena.alloc(RecordDestructure(identifiers2.into_bump_slice())),
|
||
newlines.into_bump_slice(),
|
||
);
|
||
let loc_pattern2 = Located::new(2, 2, 1, 9, pattern2);
|
||
let expr2 = Int("4");
|
||
let loc_expr2 = Located::new(2, 2, 13, 14, expr2);
|
||
let branch2 = &*arena.alloc(WhenBranch {
|
||
patterns: bumpalo::vec![in &arena;loc_pattern2 ],
|
||
value: loc_expr2,
|
||
guard: None,
|
||
});
|
||
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
||
let var = Var {
|
||
module_name: "",
|
||
ident: "x",
|
||
};
|
||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||
let actual = parse_with(
|
||
&arena,
|
||
indoc!(
|
||
r#"
|
||
when x is
|
||
{ y } -> 2
|
||
{ z, w } -> 4
|
||
"#
|
||
),
|
||
);
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn when_with_alternative_patterns() {
|
||
let arena = Bump::new();
|
||
let newlines = bumpalo::vec![in &arena; Newline];
|
||
let pattern1 =
|
||
Pattern::SpaceBefore(arena.alloc(StrLiteral("blah")), newlines.into_bump_slice());
|
||
let pattern1_alt = StrLiteral("blop");
|
||
let loc_pattern1 = Located::new(1, 1, 1, 7, pattern1);
|
||
let loc_pattern1_alt = Located::new(1, 1, 10, 16, pattern1_alt);
|
||
let expr1 = Int("1");
|
||
let loc_expr1 = Located::new(1, 1, 20, 21, expr1);
|
||
let branch1 = &*arena.alloc(WhenBranch {
|
||
patterns: bumpalo::vec![in &arena;loc_pattern1, loc_pattern1_alt],
|
||
value: loc_expr1,
|
||
guard: None,
|
||
});
|
||
let newlines = bumpalo::vec![in &arena; Newline];
|
||
let pattern2 =
|
||
Pattern::SpaceBefore(arena.alloc(StrLiteral("foo")), newlines.into_bump_slice());
|
||
let newlines = bumpalo::vec![in &arena; Newline];
|
||
let pattern2_alt =
|
||
Pattern::SpaceBefore(arena.alloc(StrLiteral("bar")), newlines.into_bump_slice());
|
||
let loc_pattern2 = Located::new(2, 2, 1, 6, pattern2);
|
||
let loc_pattern2_alt = Located::new(3, 3, 1, 6, pattern2_alt);
|
||
let expr2 = Int("2");
|
||
let loc_expr2 = Located::new(3, 3, 10, 11, expr2);
|
||
let branch2 = &*arena.alloc(WhenBranch {
|
||
patterns: bumpalo::vec![in &arena;loc_pattern2, loc_pattern2_alt],
|
||
value: loc_expr2,
|
||
guard: None,
|
||
});
|
||
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
||
let var = Var {
|
||
module_name: "",
|
||
ident: "x",
|
||
};
|
||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||
let actual = parse_with(
|
||
&arena,
|
||
indoc!(
|
||
r#"
|
||
when x is
|
||
"blah" | "blop" -> 1
|
||
"foo" |
|
||
"bar" -> 2
|
||
"#
|
||
),
|
||
);
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
// MODULE
|
||
|
||
#[test]
|
||
fn empty_module() {
|
||
let arena = Bump::new();
|
||
let exposes = Vec::new_in(&arena);
|
||
let imports = Vec::new_in(&arena);
|
||
let module_name = ModuleName::new("Foo");
|
||
let expected = InterfaceHeader {
|
||
name: Located::new(0, 0, 10, 13, module_name),
|
||
exposes,
|
||
imports,
|
||
|
||
after_interface: &[],
|
||
before_exposes: &[],
|
||
after_exposes: &[],
|
||
before_imports: &[],
|
||
after_imports: &[],
|
||
};
|
||
let src = indoc!(
|
||
r#"
|
||
interface Foo exposes [] imports []
|
||
"#
|
||
);
|
||
let actual = interface_header()
|
||
.parse(&arena, State::new(&src, Attempting::Module))
|
||
.map(|tuple| tuple.0);
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn nested_module() {
|
||
let arena = Bump::new();
|
||
let exposes = Vec::new_in(&arena);
|
||
let imports = Vec::new_in(&arena);
|
||
let module_name = ModuleName::new("Foo.Bar.Baz");
|
||
let expected = InterfaceHeader {
|
||
name: Located::new(0, 0, 10, 21, module_name),
|
||
exposes,
|
||
imports,
|
||
|
||
after_interface: &[],
|
||
before_exposes: &[],
|
||
after_exposes: &[],
|
||
before_imports: &[],
|
||
after_imports: &[],
|
||
};
|
||
let src = indoc!(
|
||
r#"
|
||
interface Foo.Bar.Baz exposes [] imports []
|
||
"#
|
||
);
|
||
let actual = interface_header()
|
||
.parse(&arena, State::new(&src, Attempting::Module))
|
||
.map(|tuple| tuple.0);
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
#[test]
|
||
fn standalone_module_defs() {
|
||
use roc::parse::ast::Def::*;
|
||
|
||
let arena = Bump::new();
|
||
let newlines1 = bumpalo::vec![in &arena; Newline, Newline];
|
||
let newlines2 = bumpalo::vec![in &arena; Newline];
|
||
let newlines3 = bumpalo::vec![in &arena; Newline];
|
||
let pattern1 = Identifier("foo");
|
||
let pattern2 = Identifier("bar");
|
||
let pattern3 = Identifier("baz");
|
||
let def1 = SpaceAfter(
|
||
arena.alloc(Body(
|
||
arena.alloc(Located::new(0, 0, 0, 3, pattern1)),
|
||
arena.alloc(Located::new(0, 0, 6, 7, Int("1"))),
|
||
)),
|
||
newlines1.into_bump_slice(),
|
||
);
|
||
let def2 = SpaceAfter(
|
||
arena.alloc(Body(
|
||
arena.alloc(Located::new(2, 2, 0, 3, pattern2)),
|
||
arena.alloc(Located::new(2, 2, 6, 10, Str("hi"))),
|
||
)),
|
||
newlines2.into_bump_slice(),
|
||
);
|
||
let def3 = SpaceAfter(
|
||
arena.alloc(Body(
|
||
arena.alloc(Located::new(3, 3, 0, 3, pattern3)),
|
||
arena.alloc(Located::new(3, 3, 6, 13, Str("stuff"))),
|
||
)),
|
||
newlines3.into_bump_slice(),
|
||
);
|
||
|
||
let expected = bumpalo::vec![in &arena;
|
||
Located::new(0, 0, 0, 7, def1),
|
||
Located::new(2, 2, 0, 10, def2),
|
||
Located::new(3, 3, 0, 13, def3)
|
||
];
|
||
let src = indoc!(
|
||
r#"
|
||
foo = 1
|
||
|
||
bar = "hi"
|
||
baz = "stuff"
|
||
"#
|
||
);
|
||
let actual = module_defs()
|
||
.parse(&arena, State::new(&src, Attempting::Module))
|
||
.map(|tuple| tuple.0);
|
||
|
||
assert_eq!(Ok(expected), actual);
|
||
}
|
||
|
||
// PARSE ERROR
|
||
|
||
// TODO this should be parse error, but isn't!
|
||
// fn trailing_paren() {
|
||
// assert_parses_to(
|
||
// indoc!(
|
||
// r#"
|
||
// r = "foo"
|
||
// s = { left : "foo" }
|
||
|
||
// when 0 is
|
||
// 1 -> { x: s.left, y: s.left }
|
||
// 0 -> { x: s.left, y: r }
|
||
// )
|
||
// "#
|
||
// ),
|
||
// Str(""),
|
||
// );
|
||
// }
|
||
|
||
// TODO test for \t \r and \n in string literals *outside* unicode escape sequence!
|
||
//
|
||
// TODO test for non-ASCII variables
|
||
//
|
||
// TODO verify that when a string literal contains a newline before the
|
||
// closing " it correctly updates both the line *and* column in the State.
|
||
}
|