mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 07:14:46 +00:00
402 lines
11 KiB
Rust
402 lines
11 KiB
Rust
#[macro_use]
|
|
extern crate pretty_assertions;
|
|
#[macro_use]
|
|
extern crate indoc;
|
|
extern crate bumpalo;
|
|
extern crate combine; // OBSOLETE
|
|
extern crate roc;
|
|
|
|
extern crate quickcheck;
|
|
|
|
#[macro_use(quickcheck)]
|
|
extern crate quickcheck_macros;
|
|
|
|
mod helpers;
|
|
|
|
#[cfg(test)]
|
|
mod test_parser {
|
|
use bumpalo::Bump;
|
|
use helpers::located;
|
|
use roc::parse;
|
|
use roc::parse::ast::Attempting;
|
|
use roc::parse::ast::Expr::{self, *};
|
|
use roc::parse::parser::{Fail, FailReason, Parser, State};
|
|
use roc::parse::problems::Problem;
|
|
use roc::region::{Located, Region};
|
|
use std::{f64, i64};
|
|
|
|
fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) {
|
|
let state = State::new(&input, Attempting::Module);
|
|
let arena = Bump::new();
|
|
let parser = parse::expr();
|
|
let answer = parser.parse(&arena, state);
|
|
let actual = answer.map(|(expr, _)| expr);
|
|
|
|
assert_eq!(Ok(expected_expr), actual);
|
|
}
|
|
|
|
fn assert_parsing_fails<'a>(input: &'a str, reason: FailReason, attempting: Attempting) {
|
|
let state = State::new(&input, Attempting::Module);
|
|
let arena = Bump::new();
|
|
let parser = parse::expr();
|
|
let answer = parser.parse(&arena, state);
|
|
let actual = answer.map_err(|(fail, _)| fail);
|
|
let expected_fail = Fail { reason, attempting };
|
|
|
|
assert_eq!(Err(expected_fail), actual);
|
|
}
|
|
|
|
fn assert_malformed_str<'a>(input: &'a str, expected_probs: Vec<Located<Problem>>) {
|
|
let state = State::new(&input, Attempting::Expression);
|
|
let arena = Bump::new();
|
|
let parser = parse::expr();
|
|
let answer = parser.parse(&arena, state);
|
|
let actual = answer.map(|(expr, _)| expr);
|
|
|
|
assert_eq!(
|
|
Ok(Expr::MalformedStr(expected_probs.into_boxed_slice())),
|
|
actual
|
|
);
|
|
}
|
|
|
|
/* fn raw(string: &str) -> Ident { */
|
|
/* Ident::Unqualified(string.to_string()) */
|
|
/* } */
|
|
|
|
// 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#"
|
|
""
|
|
"#
|
|
),
|
|
EmptyStr,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn one_char_list() {
|
|
assert_parses_to(
|
|
indoc!(
|
|
r#"
|
|
"x"
|
|
"#
|
|
),
|
|
Str("x".into()),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn multi_char_list() {
|
|
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("x\tx", r#""x\tx""#);
|
|
expect_parsed_str("x\rx", r#""x\rx""#);
|
|
expect_parsed_str("x\nx", r#""x\nx""#);
|
|
}
|
|
|
|
#[test]
|
|
fn string_with_escaped_interpolation() {
|
|
assert_parses_to(
|
|
// This should NOT be string interpolation, because of the \\
|
|
indoc!(
|
|
r#"
|
|
"abcd\\(efg)hij"
|
|
"#
|
|
),
|
|
Str(r#"abcd\(efg)hij"#.into()),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn string_with_single_quote() {
|
|
// This shoud NOT be escaped in a string.
|
|
expect_parsed_str("x'x", r#""x'x""#);
|
|
}
|
|
|
|
#[test]
|
|
fn string_with_valid_unicode_escapes() {
|
|
expect_parsed_str("x\u{00A0}x", r#""x\u{00A0}x""#);
|
|
expect_parsed_str("x\u{101010}x", r#""x\u{101010}x""#);
|
|
}
|
|
|
|
#[test]
|
|
fn string_with_too_large_unicode_escape() {
|
|
// Should be too big - max size should be 10FFFF.
|
|
// (Rust has this restriction. I assume it's a good idea.)
|
|
assert_malformed_str(
|
|
r#""abc\u{110000}def""#,
|
|
vec![located(0, 7, 0, 12, Problem::UnicodeCodePointTooLarge)],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn string_with_no_unicode_digits() {
|
|
// No digits specified
|
|
assert_malformed_str(
|
|
r#""blah\u{}foo""#,
|
|
vec![located(0, 5, 0, 8, Problem::NoUnicodeDigits)],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn string_with_no_unicode_opening_brace() {
|
|
// No opening curly brace. It can't be sure if the closing brace
|
|
// was intended to be a closing brace for the unicode escape, so
|
|
// report that there were no digits specified.
|
|
assert_malformed_str(
|
|
r#""abc\u00A0}def""#,
|
|
vec![located(0, 4, 0, 5, Problem::NoUnicodeDigits)],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn string_with_no_unicode_closing_brace() {
|
|
// No closing curly brace
|
|
assert_malformed_str(
|
|
r#""blah\u{stuff""#,
|
|
vec![located(0, 5, 0, 12, Problem::MalformedEscapedUnicode)],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn string_with_no_unicode_braces() {
|
|
// No curly braces
|
|
assert_malformed_str(
|
|
r#""zzzz\uzzzzz""#,
|
|
vec![located(0, 5, 0, 6, Problem::NoUnicodeDigits)],
|
|
);
|
|
}
|
|
|
|
/* #[test] */
|
|
/* fn string_with_interpolation_at_start() { */
|
|
/* assert_fully_parses( */
|
|
/* indoc!( */
|
|
/* r#" */
|
|
/* "\(abc)defg" */
|
|
/* "# */
|
|
/* ), */
|
|
/* InterpolatedStr(vec![("".to_string(), loc(raw("abc")))], "defg".to_string()), */
|
|
/* ); */
|
|
/* } */
|
|
|
|
/* #[test] */
|
|
/* fn string_with_interpolation_at_end() { */
|
|
/* assert_fully_parses( */
|
|
/* indoc!( */
|
|
/* r#" */
|
|
/* "abcd\(efg)" */
|
|
/* "# */
|
|
/* ), */
|
|
/* InterpolatedStr(vec![("abcd".to_string(), loc(raw("efg")))], "".to_string()), */
|
|
/* ); */
|
|
/* } */
|
|
|
|
/* #[test] */
|
|
/* fn string_with_interpolation_in_middle() { */
|
|
/* assert_fully_parses( */
|
|
/* indoc!( */
|
|
/* r#" */
|
|
/* "abcd\(efg)hij" */
|
|
/* "# */
|
|
/* ), */
|
|
/* InterpolatedStr( */
|
|
/* vec![("abcd".to_string(), loc(raw("efg")))], */
|
|
/* "hij".to_string(), */
|
|
/* ), */
|
|
/* ); */
|
|
/* } */
|
|
|
|
/* #[test] */
|
|
/* fn string_with_multiple_interpolation() { */
|
|
/* panic!("TODO start, middle, middle again, *and*, end"); */
|
|
/* assert_fully_parses( */
|
|
/* indoc!( */
|
|
/* r#" */
|
|
/* "abcd\(efg)hij" */
|
|
/* "# */
|
|
/* ), */
|
|
/* InterpolatedStr( */
|
|
/* vec![("abcd".to_string(), loc(raw("efg")))], */
|
|
/* "hij".to_string(), */
|
|
/* ), */
|
|
/* ); */
|
|
/* } */
|
|
|
|
// TODO test for \t \r and \n in string literals *outside* unicode escape sequence!
|
|
//
|
|
// TODO verify that when a string literal contains a newline before the
|
|
// closing " it correctly updates both the line *and* column in the State.
|
|
//
|
|
// TODO verify that exceeding maximum line length does NOT panic
|
|
// TODO verify that exceeding maximum line count does NOT panic
|
|
|
|
#[test]
|
|
fn empty_source_file() {
|
|
assert_parsing_fails("", FailReason::Eof(Region::zero()), Attempting::Expression);
|
|
}
|
|
|
|
#[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::Expression,
|
|
);
|
|
}
|
|
|
|
// 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));
|
|
assert_parses_to(i64::MAX.to_string().as_str(), Int(i64::MAX));
|
|
}
|
|
|
|
#[test]
|
|
fn negative_int() {
|
|
assert_parses_to("-1", Int(-1));
|
|
assert_parses_to("-42", Int(-42));
|
|
assert_parses_to(i64::MIN.to_string().as_str(), Int(i64::MIN));
|
|
}
|
|
|
|
#[test]
|
|
fn int_with_underscore() {
|
|
assert_parses_to("1_2_34_567", Int(1234567));
|
|
assert_parses_to("-1_2_34_567", Int(-1234567));
|
|
// 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(123));
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn all_i64_values_parse(num: i64) {
|
|
assert_parses_to(num.to_string().as_str(), Int(num));
|
|
}
|
|
|
|
#[test]
|
|
fn int_too_large() {
|
|
assert_parses_to(
|
|
(i64::MAX as i128 + 1).to_string().as_str(),
|
|
MalformedInt(Problem::OutsideSupportedRange),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn int_too_small() {
|
|
assert_parses_to(
|
|
(i64::MIN as i128 - 1).to_string().as_str(),
|
|
MalformedInt(Problem::OutsideSupportedRange),
|
|
);
|
|
}
|
|
|
|
// 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 highest_float() {
|
|
assert_parses_to(&format!("{}.0", f64::MAX), Float(f64::MAX));
|
|
}
|
|
|
|
#[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 lowest_float() {
|
|
assert_parses_to(&format!("{}.0", f64::MIN), Float(f64::MIN));
|
|
}
|
|
|
|
#[test]
|
|
fn float_with_underscores() {
|
|
assert_parses_to("1_23_456.0_1_23_456", Float(123456.0123456));
|
|
assert_parses_to("-1_23_456.0_1_23_456", Float(-123456.0123456));
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn all_f64_values_parse(num: f64) {
|
|
assert_parses_to(num.to_string().as_str(), Float(num));
|
|
}
|
|
|
|
#[test]
|
|
fn float_too_large() {
|
|
assert_parses_to(
|
|
format!("{}1.0", f64::MAX).as_str(),
|
|
MalformedFloat(Problem::OutsideSupportedRange),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn float_too_small() {
|
|
assert_parses_to(
|
|
format!("{}1.0", f64::MIN).as_str(),
|
|
MalformedFloat(Problem::OutsideSupportedRange),
|
|
);
|
|
}
|
|
}
|