use super::*;
use crate::lexer::Lexer;
fn parse_input(input: &str) -> Result<Program, Box<CompileError>> {
let mut lexer = Lexer::new(input);
let tokens = lexer.tokenize();
let mut parser = Parser::new(tokens);
parser.parse()
}
fn parse_err(input: &str) -> String {
parse_input(input)
.err()
.unwrap_or_else(|| panic!("expected {:?} to be rejected", input))
.to_string()
}
fn only_thing(program: &Program) -> &ThingDef {
assert_eq!(
program.things.len(),
1,
"expected exactly one thing, got {:?}",
program.things
);
&program.things[0]
}
#[test]
fn fields_keep_definition_order_types_and_defaults() {
let program = parse_input(
"A thing called point has\n a number called x is 0,\n a float called y.\n",
)
.expect("a definition should parse");
let def = only_thing(&program);
assert_eq!(def.name, "point");
assert_eq!(def.line, 1);
assert!(def.members.is_empty());
let names: Vec<&str> = def.fields.iter().map(|f| f.name.as_str()).collect();
assert_eq!(names, vec!["x", "y"]);
assert_eq!(def.fields[0].field_type, Type::Integer);
assert!(matches!(def.fields[0].default, Some(Expr::IntegerLit(0))));
assert_eq!(def.fields[1].field_type, Type::Float);
assert!(def.fields[1].default.is_none());
}
#[test]
fn function_members_are_declared_without_taking_storage() {
let program = parse_input(
"A thing called point has\n \
a function called 'placed at',\n \
a number called x is 0.\n\n\
To do the point's 'placed at', with a number called x.\n \
a point called plotted.\n \
Return a point, plotted.\n",
)
.expect("a manifest entry should parse");
let def = only_thing(&program);
assert_eq!(def.members, vec!["placed at".to_string()]);
assert_eq!(def.fields.len(), 1);
assert_eq!(def.fields[0].name, "x");
}
#[test]
fn a_field_may_name_an_earlier_thing() {
let program = parse_input(
"A thing called point has\n a number called x is 0.\n\n\
A thing called segment has\n a point called start,\n a point called end.\n",
)
.expect("a nested thing field should parse");
assert_eq!(program.things.len(), 2);
let segment = &program.things[1];
assert_eq!(segment.name, "segment");
assert_eq!(
segment.fields[0].field_type,
Type::Thing("point".to_string())
);
assert_eq!(
segment.fields[1].field_type,
Type::Thing("point".to_string())
);
}
#[test]
fn a_field_may_not_name_a_later_thing() {
let err = parse_err(
"A thing called segment has\n a point called start.\n\n\
A thing called point has\n a number called x is 0.\n",
);
assert!(
err.contains("Unknown field type 'point'"),
"unexpected error: {}",
err
);
}
#[test]
fn names_may_be_quoted_and_multi_word() {
let program = parse_input(
"A thing called 'bounding box' has\n a number called 'top left x' is 1.\n",
)
.expect("quoted multi-word names should parse");
let def = only_thing(&program);
assert_eq!(def.name, "bounding box");
assert_eq!(def.fields[0].name, "top left x");
}
#[test]
fn a_definition_emits_only_a_thing_decl() {
let program = parse_input("A thing called point has\n a number called x is 0.\n")
.expect("a definition should parse");
assert_eq!(program.statements.len(), 1);
assert!(matches!(program.statements[0], Statement::ThingDecl(_)));
}
#[test]
fn thing_stays_an_ordinary_identifier() {
let program = parse_input("a number called thing is 42.\nPrint thing.\nthing is 7.\n")
.expect("`thing` should still be usable as a variable name");
assert!(program.things.is_empty());
assert!(matches!(
&program.statements[0],
Statement::VarDecl { name, .. } if name == "thing"
));
assert!(matches!(
&program.statements[2],
Statement::Assignment { name, .. } if name == "thing"
));
}
#[test]
fn reserved_wrong_shapes_get_targeted_diagnostics() {
assert!(parse_err("Create a thing called point.\n")
.contains("A thing is defined, not created as a variable"));
assert!(parse_err("A thing called point is 5.\n")
.contains("'is' declares a variable; a thing definition uses 'has'"));
assert!(parse_err("A thing called point has.\n").contains("at least one field"));
assert!(parse_err("A thing called point.\n").contains("at least one field"));
}
const POINT: &str = "A thing called point has\n a number called x is 0,\n a number called y is 0.\n\n";
const ROUTE: &str = "A thing called segment has\n a point called start,\n a point called end.\n\n\
A thing called route has\n a segment called leg,\n a number called id.\n\n";
#[test]
fn a_declaration_names_the_thing_as_its_type() {
let program = parse_input(&format!("{}a point called origin.\n", POINT))
.expect("a thing declaration should parse");
assert!(matches!(
&program.statements[1],
Statement::VarDecl { name, var_type: Some(Type::Thing(thing)), value: None }
if name == "origin" && thing == "point"
));
}
#[test]
fn a_declaration_may_not_reuse_the_things_own_name() {
let err = parse_err(&format!("{}a point called point.\n", POINT));
assert!(
err.contains("'point' is already defined as a thing"),
"unexpected error: {}",
err
);
}
#[test]
fn a_declaration_carries_its_initialiser() {
let program = parse_input(&format!(
"{}a point called origin.\na point called mirror is origin.\n",
POINT
))
.expect("a declaration with an initialiser should parse");
match program.statements.last() {
Some(Statement::VarDecl {
name,
var_type: Some(Type::Thing(thing)),
value: Some(Expr::Identifier(source)),
}) => {
assert_eq!(name, "mirror");
assert_eq!(thing, "point");
assert_eq!(source, "origin");
}
other => panic!("expected a point declaration copying origin, got {:?}", other),
}
}
#[test]
fn a_call_returning_a_thing_declares_the_name_it_is_assigned_to() {
let program = parse_input(&format!(
"{}To nudged with a point called start.\n Return a point, start.\n\n\
a point called before.\nThe after is nudged of before.\nPrint after's x.\n",
POINT
))
.expect("declaration by inference should parse");
let declared = program.statements.iter().find_map(|stmt| match stmt {
Statement::VarDecl {
name,
var_type: Some(Type::Thing(thing)),
value: Some(Expr::FunctionCall { name: callee, .. }),
} if name == "after" => Some((thing.clone(), callee.clone())),
_ => None,
});
assert_eq!(
declared,
Some(("point".to_string(), "nudged".to_string())),
"`The after is nudged of before.` should declare a point"
);
assert!(
program.statements.iter().any(|stmt| matches!(
stmt,
Statement::Print {
value: Expr::ThingField { base, .. },
..
} if base == "after"
)),
"after's x should read as a field chain"
);
}
#[test]
fn a_thing_parameter_takes_the_things_type() {
let program = parse_input(&format!(
"{}To nudged with a point called start.\n Return a point, start.\n\n",
POINT
))
.expect("a thing parameter should parse");
let definition = program
.statements
.iter()
.find(|stmt| matches!(stmt, Statement::FunctionDef { .. }));
match definition {
Some(Statement::FunctionDef {
params,
return_type,
..
}) => {
assert_eq!(
params.as_slice(),
&[("start".to_string(), Type::Thing("point".to_string()))]
);
assert_eq!(return_type, &Type::Thing("point".to_string()));
}
other => panic!("expected a function definition, got {:?}", other),
}
}
#[test]
fn a_possessive_chain_reads_fields_in_order() {
let program = parse_input(&format!(
"{}{}a route called commute.\nPrint commute's leg's start's x.\n",
POINT, ROUTE
))
.expect("a chained possessive should parse");
let last = program.statements.last().expect("a Print statement");
match last {
Statement::Print { value: Expr::ThingField { base, path }, .. } => {
assert_eq!(base, "commute");
assert_eq!(path, &["leg", "start", "x"]);
}
other => panic!("expected a Print of a ThingField, got {:?}", other),
}
}
#[test]
fn every_write_spelling_becomes_one_field_write() {
let program = parse_input(&format!(
"{}a point called origin.\n\
Set origin's x to 3.\n\
origin's y is 4.\n\
increment origin's x.\n\
decrement origin's y.\n",
POINT
))
.expect("every write spelling should parse");
assert!(matches!(
&program.statements[2],
Statement::SetThingField { base, path, value: Expr::IntegerLit(3) }
if base == "origin" && path == &["x"]
));
assert!(matches!(
&program.statements[3],
Statement::SetThingField { base, path, value: Expr::IntegerLit(4) }
if base == "origin" && path == &["y"]
));
match &program.statements[4] {
Statement::SetThingField { base, path, value: Expr::BinaryOp { left, op, right } } => {
assert_eq!(base, "origin");
assert_eq!(path, &["x"]);
assert!(matches!(op, BinaryOperator::Add));
assert!(matches!(left.as_ref(), Expr::ThingField { path, .. } if path == &["x"]));
assert!(matches!(right.as_ref(), Expr::IntegerLit(1)));
}
other => panic!("expected increment to become a field write, got {:?}", other),
}
assert!(matches!(
&program.statements[5],
Statement::SetThingField { value: Expr::BinaryOp { op: BinaryOperator::Subtract, .. }, .. }
));
}
#[test]
fn a_step_on_a_float_field_steps_by_a_float() {
let program = parse_input(
"A thing called 'water tank' has\n a float called 'depth in metres' is 1.5.\n\n\
a 'water tank' called cistern.\n\
increment cistern's 'depth in metres'.\n",
)
.expect("a step on a float field should parse");
assert!(matches!(
&program.statements[2],
Statement::SetThingField { value: Expr::BinaryOp { right, .. }, .. }
if matches!(right.as_ref(), Expr::FloatLit(_))
));
}
#[test]
fn a_field_interpolates_into_a_format_string() {
let program = parse_input(&format!(
"{}a point called origin.\nPrint \"origin sits at {{origin's x}}\".\n",
POINT
))
.expect("a field in a format string should parse");
let last = program.statements.last().expect("a Print statement");
match last {
Statement::Print { value: Expr::FormatString { parts }, .. } => {
assert!(
parts.iter().any(|part| matches!(
part,
FormatPart::Expression { expr, .. }
if matches!(expr.as_ref(), Expr::ThingField { path, .. } if path == &["x"])
)),
"expected an interpolated ThingField, got {:?}",
parts
);
}
other => panic!("expected a Print of a FormatString, got {:?}", other),
}
}
#[test]
fn a_chain_may_end_on_a_nested_thing() {
let program = parse_input(&format!(
"{}{}a route called commute.\na segment called span is commute's leg.\n",
POINT, ROUTE
))
.expect("a chain ending on a nested thing should parse");
match program.statements.last() {
Some(Statement::VarDecl {
var_type: Some(Type::Thing(thing)),
value: Some(Expr::ThingField { base, path }),
..
}) => {
assert_eq!(thing, "segment");
assert_eq!(base, "commute");
assert_eq!(path.as_slice(), &["leg".to_string()]);
}
other => panic!("expected a segment copied out of commute, got {:?}", other),
}
}
#[test]
fn an_unknown_member_lists_what_the_thing_does_have() {
let err = parse_err(&format!("{}a point called origin.\nPrint origin's z.\n", POINT));
assert!(
err.contains("Thing 'point' has no member 'z'")
&& err.contains("point's fields are: x, y")
&& err.contains("no function above this line takes a point as its first parameter"),
"unexpected error: {}",
err
);
}
const POINT_FUNCTIONS: &str =
"To 'magnitude squared' with a point called corner.\n \
Return a number, corner's x.\n\n\
To 'scaled by' with a point called corner and a number called factor.\n \
Return a point, corner.\n\n";
#[test]
fn the_sugar_parses_to_the_same_call_as_the_free_form() {
let program = parse_input(&format!(
"{}{}a point called origin.\nPrint origin's 'magnitude squared'.\n\
Print 'magnitude squared' of origin.\n",
POINT, POINT_FUNCTIONS
))
.expect("the instance possessive should parse");
let printed: Vec<&Expr> = program
.statements
.iter()
.filter_map(|stmt| match stmt {
Statement::Print { value, .. } => Some(value),
_ => None,
})
.collect();
assert_eq!(printed.len(), 2, "expected two prints, got {:?}", printed);
match printed[0] {
Expr::FunctionCall { name, args } => {
assert_eq!(name, "magnitude squared");
assert!(
matches!(args.as_slice(), [Expr::Identifier(receiver)] if receiver == "origin"),
"expected origin as the only argument, got {:?}",
args
);
}
other => panic!("expected the sugar to become a call, got {:?}", other),
}
assert_eq!(
format!("{:?}", printed[0]),
format!("{:?}", printed[1]),
"the sugared and free forms must build the same call"
);
}
#[test]
fn the_receiver_fills_the_first_parameter_and_the_rest_follow() {
let program = parse_input(&format!(
"{}{}a point called origin.\nThe 'tripled corner' is origin's 'scaled by' on 3.\n",
POINT, POINT_FUNCTIONS
))
.expect("a sugared call carrying an argument should parse");
match program.statements.last() {
Some(Statement::VarDecl {
name,
var_type: Some(Type::Thing(thing)),
value: Some(Expr::FunctionCall { name: called, args }),
}) => {
assert_eq!(name, "tripled corner");
assert_eq!(thing, "point");
assert_eq!(called, "scaled by");
assert!(
matches!(
args.as_slice(),
[Expr::Identifier(receiver), Expr::IntegerLit(3)] if receiver == "origin"
),
"expected origin then 3, got {:?}",
args
);
}
other => panic!("expected a point declared from a sugared call, got {:?}", other),
}
}
#[test]
fn a_field_wins_the_possessive() {
let program = parse_input(&format!(
"{}{}a point called origin.\nPrint origin's x.\n",
POINT, POINT_FUNCTIONS
))
.expect("a field should still parse as a field");
match program.statements.last() {
Some(Statement::Print {
value: Expr::ThingField { base, path },
..
}) => {
assert_eq!(base, "origin");
assert_eq!(path.as_slice(), &["x".to_string()]);
}
other => panic!("expected a field read, got {:?}", other),
}
}
#[test]
fn a_field_holding_a_thing_can_be_the_receiver() {
let program = parse_input(&format!(
"{}{}{}a route called commute.\nPrint commute's leg's start's 'magnitude squared'.\n",
POINT, ROUTE, POINT_FUNCTIONS
))
.expect("a chained receiver should parse");
match program.statements.last() {
Some(Statement::Print {
value: Expr::FunctionCall { name, args },
..
}) => {
assert_eq!(name, "magnitude squared");
assert!(
matches!(
args.as_slice(),
[Expr::ThingField { base, path }]
if base == "commute" && path.as_slice() == ["leg", "start"]
),
"expected commute's leg's start as the receiver, got {:?}",
args
);
}
other => panic!("expected a call on a nested receiver, got {:?}", other),
}
}
#[test]
fn the_sugar_stands_as_a_whole_statement() {
let program = parse_input(&format!(
"{}{}a point called origin.\norigin's 'scaled by' on 3.\n",
POINT, POINT_FUNCTIONS
))
.expect("a sugared call statement should parse");
match program.statements.last() {
Some(Statement::FunctionCall { name, args }) => {
assert_eq!(name, "scaled by");
assert!(
matches!(
args.as_slice(),
[Expr::Identifier(receiver), Expr::IntegerLit(3)] if receiver == "origin"
),
"expected origin then 3, got {:?}",
args
);
}
other => panic!("expected a call statement, got {:?}", other),
}
}
#[test]
fn a_write_target_cannot_be_a_function() {
let err = parse_err(&format!(
"{}{}a point called origin.\nSet origin's 'magnitude squared' to 3.\n",
POINT, POINT_FUNCTIONS
));
assert!(
err.contains("'magnitude squared' is a function taking a point, not a field of it")
&& err.contains("A call is not storage"),
"unexpected error: {}",
err
);
}
#[test]
fn an_unknown_member_offers_the_functions_too() {
let err = parse_err(&format!(
"{}{}a point called origin.\nPrint origin's sparkle.\n",
POINT, POINT_FUNCTIONS
));
assert!(
err.contains("Thing 'point' has no member 'sparkle'")
&& err.contains(
"functions above this line taking a point first: magnitude squared, scaled by"
),
"unexpected error: {}",
err
);
}
#[test]
fn a_function_cannot_take_a_name_the_type_already_owns() {
let field_clash = parse_err(&format!("{}To x with a point called corner.\n", POINT));
assert!(
field_clash.contains("point already has a field called 'x'")
&& field_clash.contains("point is defined on line 1"),
"unexpected error: {}",
field_clash
);
let member_clash = parse_err(
"A thing called point has\n a function called 'from polar',\n \
a number called x is 0.\n\nTo 'from polar' with a point called corner.\n \
Return a point, corner.\n",
);
assert!(
member_clash
.contains("point already has a declared function member called 'from polar'"),
"unexpected error: {}",
member_clash
);
}
#[test]
fn only_a_matching_first_parameter_joins_the_member_space() {
let err = parse_err(&format!(
"{}{}To 'the length squared' with a segment called span.\n \
Return a number, span's start's x.\n\na point called origin.\n\
Print origin's 'the length squared'.\n",
POINT, ROUTE
));
assert!(
err.contains("Thing 'point' has no member 'the length squared'"),
"unexpected error: {}",
err
);
}
#[test]
fn a_thing_name_is_only_a_type_noun_before_called() {
let program = parse_input(&format!(
"{}A thing called route has\n a number called point is 0.\n",
POINT
))
.expect("a field may be named after a thing");
let route = program
.things
.iter()
.find(|def| def.name == "route")
.expect("route should be registered");
assert_eq!(route.fields[0].name, "point");
assert_eq!(route.fields[0].field_type, Type::Integer);
}
#[test]
fn a_seen_files_things_reach_the_programs_registry() {
let source = "see \"./geometry.vox\".\n\na point called origin.\n";
let mut lexer = Lexer::new(source);
let tokens = lexer.tokenize();
let program = Parser::new(tokens)
.with_include_base(std::path::Path::new("tests/include"))
.parse()
.expect("a thing defined in a seen file should be usable");
let point = program
.things
.iter()
.find(|def| def.name == "point")
.expect("the seen file's thing should be in Program.things");
let fields: Vec<&str> = point.fields.iter().map(|f| f.name.as_str()).collect();
assert_eq!(fields, vec!["x", "y"]);
assert!(matches!(program.statements[0], Statement::ThingDecl(_)));
assert!(!program
.statements
.iter()
.any(|stmt| matches!(stmt, Statement::See { .. })));
}
#[test]
fn a_quoted_reserved_word_can_name_a_thing() {
let program = parse_input(
"A thing called 'reading' has\n a number called x is 0.\n\n\
a 'reading' called gauge.\n",
)
.expect("a quoted reserved word is an ordinary name");
assert_eq!(only_thing(&program).name, "reading");
assert!(matches!(
program.statements.last(),
Some(Statement::VarDecl { name, var_type: Some(Type::Thing(thing)), .. })
if name == "gauge" && thing == "reading"
));
}
#[test]
fn a_thing_name_cannot_be_taken_by_a_variable() {
for spelling in [
"a number called point is 42.\n",
"The point is 42.\n",
"Set point to 42.\n",
"Create a number called point is 42.\n",
"To point with a number called across.\n Print across.\n",
"To 'measure the distance' with a number called point.\n Print point.\n",
] {
let err = parse_err(&format!("{}{}", POINT, spelling));
assert!(
err.contains("'point' is already defined as a thing")
&& err.contains("identifier space"),
"unexpected error for {:?}: {}",
spelling,
err
);
}
}
#[test]
fn a_thing_cannot_take_a_name_a_declaration_already_holds() {
let after_variable = parse_err(
"a number called point is 42.\n\nA thing called point has\n a number called x is 0.\n",
);
assert!(
after_variable.contains("'point' is already defined as a variable"),
"unexpected error: {}",
after_variable
);
let after_function = parse_err(
"To point with a number called across.\n Print across.\n\n\
A thing called point has\n a number called x is 0.\n",
);
assert!(
after_function.contains("'point' is already defined as a function"),
"unexpected error: {}",
after_function
);
}
const TWO_MAKERS: &str = "A thing called point has\n \
a function called 'placed at',\n \
a number called x is 0.\n\n\
A thing called 'grid square' has\n \
a function called 'placed at',\n \
a number called column is 0.\n\n\
To do the point's 'placed at', with a number called x.\n \
a point called plotted.\n \
Set plotted's x to x.\n \
Return a point, plotted.\n\n\
To do the 'grid square''s 'placed at', with a number called column.\n \
a 'grid square' called square.\n \
Return a 'grid square', square.\n\n";
#[test]
fn two_things_may_declare_the_same_member() {
let program = parse_input(TWO_MAKERS).expect("two makers should parse");
let defined: Vec<&str> = program
.statements
.iter()
.filter_map(|stmt| match stmt {
Statement::FunctionDef { name, .. } => Some(name.as_str()),
_ => None,
})
.collect();
assert_eq!(
defined,
vec!["point's placed at", "grid square's placed at"],
"each member compiles under a name carrying its owner"
);
let labels: Vec<String> = defined
.iter()
.map(|name| crate::codegen::mangle_symbol(name))
.collect();
assert_eq!(
labels,
vec![
"point_s_placed_at".to_string(),
"grid_square_s_placed_at".to_string()
],
"the existing mangling keeps the two apart in the symbol table"
);
}
#[test]
fn the_type_possessive_parses_to_an_ordinary_call() {
let program = parse_input(&format!(
"{}The corner is a point's 'placed at' with 3.\nPrint corner's x.\n",
TWO_MAKERS
))
.expect("a type possessive should parse");
let declared = program
.statements
.iter()
.find_map(|stmt| match stmt {
Statement::VarDecl { name, var_type, value } if name == "corner" => {
Some((var_type, value))
}
_ => None,
})
.expect("the call declares 'corner' from what the member returns");
assert_eq!(*declared.0, Some(Type::Thing("point".to_string())));
assert!(matches!(
declared.1,
Some(Expr::FunctionCall { name, args })
if name == "point's placed at" && args.len() == 1
));
}
#[test]
fn a_maker_is_reached_only_through_the_type() {
let err = parse_err(&format!(
"{}a point called origin.\nPrint origin's 'placed at'.\n",
TWO_MAKERS
));
assert!(
err.contains("point declares 'placed at', but a receiver cannot reach it here")
&& err.contains("`a point's 'placed at' with <arguments>`"),
"unexpected error: {}",
err
);
}
#[test]
fn a_member_may_be_called_above_its_definition() {
let program = parse_input(
"A thing called point has\n \
a function called 'placed at',\n \
a number called x is 0.\n\n\
The corner is a point's 'placed at' with 3.\n\
Print corner's x.\n\n\
To do the point's 'placed at', with a number called x.\n \
a point called plotted.\n \
Set plotted's x to x.\n \
Return a point, plotted.\n",
)
.expect("the manifest is the promise a call resolves against");
assert!(matches!(
&program.statements[1],
Statement::VarDecl { name, var_type: Some(Type::Thing(thing)), .. }
if name == "corner" && thing == "point"
));
}
#[test]
fn do_stays_an_ordinary_identifier() {
let program = parse_input(&format!(
"{}To do with a number called tally.\n Print tally.\n\ndo of 7.\n",
TWO_MAKERS
))
.expect("a function called do should parse");
assert!(
program
.statements
.iter()
.any(|stmt| matches!(stmt, Statement::FunctionDef { name, .. } if name == "do")),
"a function called do is an ordinary definition"
);
assert!(matches!(
program.statements.last(),
Some(Statement::FunctionCall { name, args }) if name == "do" && args.len() == 1
));
}
#[test]
fn a_members_return_line_is_what_the_rule_checks() {
parse_input(
"A thing called point has\n \
a function called 'placed at',\n \
a number called x is 0.\n\n\
To do the point's 'placed at', with a number called x.\n \
a point called plotted.\n \
If x is greater than 0 then,\n \
Return a point, plotted.\n",
)
.expect("a Return inside a block still names the owner");
}
#[test]
fn the_type_possessive_stands_in_statement_position() {
let program = parse_input(&format!("{}a point's 'placed at' with 3.\n", TWO_MAKERS))
.expect("a type possessive should parse as a call statement");
assert!(matches!(
program.statements.last(),
Some(Statement::FunctionCall { name, args })
if name == "point's placed at" && args.len() == 1
));
}
#[test]
fn a_definition_inside_a_block_is_rejected() {
let inside_an_if = "If 1 is 1 then,\n \
A thing called point has\n a number called x is 0.\n";
let inside_a_while = "a number called steps is 0.\n\n\
While steps is less than 3,\n \
A thing called point has\n a number called x is 0,\n \
steps is steps plus 1.\n";
let inside_a_function = "To 'plot a course'.\n \
A thing called point has\n a number called x is 0.\n \
Print \"plotted\".\n";
for source in [inside_an_if, inside_a_while, inside_a_function] {
let message = parse_err(source);
assert!(
message.contains("A thing is defined at the top level, like a function"),
"expected the top-level rule, got: {}",
message
);
assert!(
message.contains("A thing called point has <fields>."),
"the diagnostic should name the canonical form, got: {}",
message
);
}
}
#[test]
fn a_thing_the_program_registry_lost_is_a_reported_compiler_bug() {
let mut parser = Parser::new(Vec::new());
parser.things.insert(
"point".to_string(),
ThingDef {
name: "point".to_string(),
fields: Vec::new(),
members: Vec::new(),
line: 1,
},
);
let program = Program::new(Vec::new());
let message = parser
.check_thing_registry(&program)
.expect_err("a thing missing from the program's registry must be reported")
.to_string();
assert!(
message.contains("Compiler bug"),
"the report should say what it is, got: {}",
message
);
assert!(
message.contains("'point'"),
"the report should name the thing that went missing, got: {}",
message
);
}
#[test]
fn the_two_registries_agree_after_an_ordinary_parse() {
let program = parse_input("A thing called point has\n a number called x is 0.\n")
.expect("a top-level definition should parse");
assert_eq!(program.things.len(), 1);
assert_eq!(program.things[0].name, "point");
}