Lines
99.75 %
Functions
100 %
Branches
use nomiscript::{Expr, Fraction, Program, Reader};
fn parse(input: &str) -> Program {
Reader::parse(input).unwrap()
}
fn parse_one(input: &str) -> Expr {
let program = parse(input);
assert_eq!(program.exprs.len(), 1, "expected single expression");
program.exprs.into_iter().next().unwrap()
fn num(n: i64) -> Expr {
Expr::Number(Fraction::from_integer(n))
fn frac(n: i64, d: i64) -> Expr {
Expr::Number(Fraction::new(n, d))
fn sym(s: &str) -> Expr {
Expr::Symbol(s.into())
fn list(items: Vec<Expr>) -> Expr {
Expr::List(items)
mod atoms {
use super::*;
#[test]
fn test_nil_variations() {
let cases = [("nil", Expr::Nil), (" nil ", Expr::Nil)];
for (input, expected) in cases {
assert_eq!(parse_one(input), expected, "input: {input:?}");
fn test_booleans() {
let cases = [
("#t", Expr::Bool(true)),
("#f", Expr::Nil),
(" #t ", Expr::Bool(true)),
(" #f ", Expr::Nil),
];
mod numbers {
fn test_integers() {
("0", num(0)),
("1", num(1)),
("42", num(42)),
("123456789", num(123456789)),
("-1", num(-1)),
("-42", num(-42)),
("+1", num(1)),
("+42", num(42)),
fn test_decimals() {
("0.1", frac(1, 10)),
("0.01", frac(1, 100)),
("0.001", frac(1, 1000)),
("1.5", frac(3, 2)),
("3.14", frac(314, 100)),
("10.25", frac(1025, 100)),
("-0.5", frac(-1, 2)),
("-1.25", frac(-5, 4)),
fn test_decimal_normalization() {
let half = parse_one("0.5");
let also_half = parse_one("0.50");
assert_eq!(half, also_half);
let quarter = parse_one("0.25");
assert_eq!(quarter, frac(1, 4));
mod strings {
fn test_simple_strings() {
(r#""""#, Expr::String(String::new())),
(r#""hello""#, Expr::String("hello".into())),
(r#""hello world""#, Expr::String("hello world".into())),
(r#""123""#, Expr::String("123".into())),
fn test_escape_sequences() {
(r#""hello\nworld""#, Expr::String("hello\nworld".into())),
(r#""tab\there""#, Expr::String("tab\there".into())),
(r#""return\rhere""#, Expr::String("return\rhere".into())),
(r#""slash\\here""#, Expr::String("slash\\here".into())),
(r#""quote\"here""#, Expr::String("quote\"here".into())),
(r#""multi\n\tline""#, Expr::String("multi\n\tline".into())),
fn test_triple_quoted_strings() {
(r#""""""""#, Expr::String(String::new())),
(r#""""hello""""#, Expr::String("hello".into())),
(
r#""""line1
line2""""#,
Expr::String("line1\nline2".into()),
),
r#""""contains "quotes" inside""""#,
Expr::String("contains \"quotes\" inside".into()),
mod symbols {
fn test_simple_symbols() {
("x", sym("X")),
("foo", sym("FOO")),
("hello-world", sym("HELLO-WORLD")),
("snake_case", sym("SNAKE_CASE")),
("CamelCase", sym("CAMELCASE")),
fn test_operator_symbols() {
("+", sym("+")),
("-", sym("-")),
("*", sym("*")),
("/", sym("/")),
("=", sym("=")),
("<", sym("<")),
(">", sym(">")),
("<=", sym("<=")),
(">=", sym(">=")),
("!=", sym("!=")),
("++", sym("++")),
("->", sym("->")),
("=>", sym("=>")),
fn test_special_symbols() {
("define", sym("DEFINE")),
("lambda", sym("LAMBDA")),
("if", sym("IF")),
("cond", sym("COND")),
("let", sym("LET")),
("let*", sym("LET*")),
("set!", sym("SET!")),
("begin", sym("BEGIN")),
("car", sym("CAR")),
("cdr", sym("CDR")),
("cons", sym("CONS")),
("list?", sym("LIST?")),
("null?", sym("NULL?")),
("pair?", sym("PAIR?")),
fn test_case_insensitive() {
assert_eq!(parse_one("foo"), parse_one("FOO"));
assert_eq!(parse_one("foo"), parse_one("Foo"));
assert_eq!(parse_one("Sum"), sym("SUM"));
fn test_nil_as_symbol_variant() {
assert_eq!(parse_one("nIl"), Expr::Nil);
assert_eq!(parse_one("nIL"), Expr::Nil);
mod lists {
fn test_empty_list() {
assert_eq!(parse_one("()"), list(vec![]));
assert_eq!(parse_one("( )"), list(vec![]));
fn test_simple_lists() {
("(1)", list(vec![num(1)])),
("(1 2)", list(vec![num(1), num(2)])),
("(1 2 3)", list(vec![num(1), num(2), num(3)])),
("(a b c)", list(vec![sym("A"), sym("B"), sym("C")])),
fn test_nested_lists() {
("(())", list(vec![list(vec![])])),
("((1))", list(vec![list(vec![num(1)])])),
"((1 2) (3 4))",
list(vec![list(vec![num(1), num(2)]), list(vec![num(3), num(4)])]),
"(a (b (c d)))",
list(vec![
sym("A"),
list(vec![sym("B"), list(vec![sym("C"), sym("D")])]),
]),
fn test_mixed_lists() {
("(+ 1 2)", list(vec![sym("+"), num(1), num(2)])),
r#"(print "hello")"#,
list(vec![sym("PRINT"), Expr::String("hello".into())]),
"(if #t 1 0)",
list(vec![sym("IF"), Expr::Bool(true), num(1), num(0)]),
mod quotes {
fn test_quoted_atoms() {
("'x", Expr::Quote(Box::new(sym("X")))),
("'42", Expr::Quote(Box::new(num(42)))),
("'nil", Expr::Quote(Box::new(Expr::Nil))),
("'#t", Expr::Quote(Box::new(Expr::Bool(true)))),
fn test_quoted_lists() {
("'()", Expr::Quote(Box::new(list(vec![])))),
"'(1 2 3)",
Expr::Quote(Box::new(list(vec![num(1), num(2), num(3)]))),
"'(a b c)",
Expr::Quote(Box::new(list(vec![sym("A"), sym("B"), sym("C")]))),
fn test_nested_quotes() {
let input = "''x";
let expected = Expr::Quote(Box::new(Expr::Quote(Box::new(sym("X")))));
assert_eq!(parse_one(input), expected);
mod programs {
fn test_empty_program() {
let program = parse("");
assert!(program.exprs.is_empty());
let program = parse(" ");
let program = parse("\n\n");
fn test_multiple_expressions() {
let program = parse("1 2 3");
assert_eq!(program.exprs.len(), 3);
assert_eq!(program.exprs[0], num(1));
assert_eq!(program.exprs[1], num(2));
assert_eq!(program.exprs[2], num(3));
fn test_scheme_like_programs() {
"(define x 10)",
vec![list(vec![sym("DEFINE"), sym("X"), num(10)])],
"(define (square x) (* x x))",
vec![list(vec![
sym("DEFINE"),
list(vec![sym("SQUARE"), sym("X")]),
list(vec![sym("*"), sym("X"), sym("X")]),
])],
"(define x 10) (+ x 5)",
vec![
list(vec![sym("DEFINE"), sym("X"), num(10)]),
list(vec![sym("+"), sym("X"), num(5)]),
],
"(if (> x 0) x (- x))",
sym("IF"),
list(vec![sym(">"), sym("X"), num(0)]),
sym("X"),
list(vec![sym("-"), sym("X")]),
assert_eq!(program.exprs, expected, "input: {input:?}");
fn test_lambda_expressions() {
"(lambda (x) x)",
list(vec![sym("LAMBDA"), list(vec![sym("X")]), sym("X")]),
"(lambda (x y) (+ x y))",
sym("LAMBDA"),
list(vec![sym("X"), sym("Y")]),
list(vec![sym("+"), sym("X"), sym("Y")]),
"((lambda (x) (* x x)) 5)",
list(vec![sym("X")]),
num(5),
fn test_let_expressions() {
"(let ((x 1)) x)",
sym("LET"),
list(vec![list(vec![sym("X"), num(1)])]),
"(let ((x 1) (y 2)) (+ x y))",
list(vec![sym("X"), num(1)]),
list(vec![sym("Y"), num(2)]),
fn test_cond_expressions() {
let input = "(cond ((< x 0) -1) ((= x 0) 0) (#t 1))";
let expected = list(vec![
sym("COND"),
list(vec![list(vec![sym("<"), sym("X"), num(0)]), num(-1)]),
list(vec![list(vec![sym("="), sym("X"), num(0)]), num(0)]),
list(vec![Expr::Bool(true), num(1)]),
]);
mod whitespace {
fn test_various_whitespace() {
let inputs = [
"(+ 1 2)",
"( + 1 2 )",
"(\n+\n1\n2\n)",
"(\t+\t1\t2\t)",
"( \n\t + \n\t 1 \n\t 2 \n\t )",
let expected = list(vec![sym("+"), num(1), num(2)]);
for input in inputs {
fn test_leading_trailing_whitespace() {
let inputs = ["42", " 42", "42 ", " 42 ", "\n42\n", "\t42\t"];
assert_eq!(parse_one(input), num(42), "input: {input:?}");
mod edge_cases {
fn test_deeply_nested() {
let input = "((((((1))))))";
let mut expr = num(1);
for _ in 0..6 {
expr = list(vec![expr]);
assert_eq!(parse_one(input), expr);
fn test_long_symbol() {
let input = "a".repeat(1000);
let expected = "A".repeat(1000);
assert_eq!(parse_one(&input), sym(&expected));
fn test_many_list_elements() {
let input = format!(
"({})",
(1..=100)
.map(|n| n.to_string())
.collect::<Vec<_>>()
.join(" ")
);
let program = parse(&input);
assert_eq!(program.exprs.len(), 1);
if let Expr::List(items) = &program.exprs[0] {
assert_eq!(items.len(), 100);
} else {
panic!("expected list");