#include "frontend.h" #include #include #include #include #include #include #include using namespace capy; static void expect_error(const std::string& source, const std::string& text, const std::string& file = "bad.capy") { try { parse(source, file); std::cerr << "expected error: " << text << " for " << source << "\n"; assert(false); } catch (const Error& e) { assert(e.what() == std::string(e.what())); assert(e.message.find(text) != std::string::npos); } } int main(int argc, char** argv) { { Program p = parse("type Count = s64\n", "alias.capy"); auto* alias = static_cast(p.items[0]); assert(alias->name == "Count" && type_name(*alias->value) == "s64"); } { Program p = parse("trace host function __bearer_trace(value : s32) string\n", "host.capy"); auto* f = static_cast(p.items[0]); assert(f->host && f->trace_host && !f->body); } { Program p = parse("function value(a : s32, b : string = \"x\", c : bool = true, opt : dval = {items: [1, {enabled: false}]}) string { -> b }\n", "defaults.capy"); auto* f = static_cast(p.items[0]); auto* opt = static_cast(f->parameters[3].default_value); auto* items = static_cast(opt->entries[0].second); assert(f->parameters.size() == 4 && !f->parameters[0].default_value && static_cast(f->parameters[1].default_value)->value == "x" && static_cast(f->parameters[2].default_value)->value == "true" && items->items.size() == 2 && static_cast(items->items[1])->entries[0].first == "enabled"); } { Program p = parse("var callback : function(value : s32) s32 = function(value : s32) s32 { return value + 1 }\n", "test.capy"); auto* v = static_cast(p.items[0]); assert(v->annotation->kind == ExprKind::FunctionType && v->value->kind == ExprKind::Lambda && type_name(*v->annotation) == "function(s32) s32"); } { Program p = parse("var callback : function(__bearer_value : s32) s32 = function(__bearer_value : s32) s32 { __bearer_value }\nfor value, __bearer_key := dval({}) { value }\n", "private-binders.capy"); assert(static_cast(static_cast(p.items[0])->value)->parameters[0].name == "__bearer_value"); assert(static_cast(p.items[1])->names[1] == "__bearer_key"); } { Program p = parse("function CLI(request : dval) { print(\";\", \",\", \"}\") }\n", "test.capy"); auto* f = static_cast(p.items[0]); assert(static_cast(f->body->items[0])->arguments.size() == 3); } { Program p = parse("function CLI(request : dval) { box.callback(7) }\n", "member-call.capy"); auto* call = static_cast(static_cast(p.items[0])->body->items[0]); auto* member = static_cast(call->function); assert(member->member == "callback" && static_cast(member->value)->value == "box" && call->arguments.size() == 1 && call->location.file == "member-call.capy" && call->location.line == 1 && call->location.column == 35); } { Program p = parse("var a := 1 // first\nvar b := 2\n", "comments.capy"); assert(p.items.size() == 2); } { Program p = parse("var a : s32 = test(10)\nvar b := test(10, 20)\n", "vars.capy"); auto* a = static_cast(p.items[0]); auto* b = static_cast(p.items[1]); assert(!a->inferred && b->inferred && !b->annotation); } { Program p = parse("var value := dval({\"name\": \"Ada\", age: 42})\nfor item, key := value { print(key) }\n", "map.capy"); auto* c = static_cast(static_cast(p.items[0])->value); assert(static_cast(c->arguments[0])->entries.size() == 2); assert(static_cast(p.items[1])->names.size() == 2); } { Program p = parse("function RENDER(request : dval)\n{\n\tvar n\n\t\t:= 1\n\tif n == 1\n\t{\n\t\treturn\n\t}\n\telse\n\t{\n\t\twhile n < 2\n\t\t{\n\t\t\tn = 2\n\t\t\tbreak\n\t\t}\n\t}\n\treturn\n\tn\n}\n", "line-breaks.capy"); auto* body = static_cast(p.items[0])->body; auto* branch = static_cast(body->items[1]); auto* loop = static_cast(branch->else_body->items[0]); assert(body->items.size() == 3 && static_cast(body->items[0])->inferred && !static_cast(branch->then_body->items[0])->value && loop->body->items.size() == 2 && loop->body->items[1]->kind == ExprKind::Break && static_cast(body->items[2])->value->kind == ExprKind::Name); } { Program p = parse("function build(type : string, host : string) dval {\n\tvar event : dval = {}\n\tevent[\"type\"] = type\n\tevent[\"host\"] = host\n\ttype\n\tevent[\"x\"] = 1\n\t-> event\n}\n", "value-names.capy"); auto* body = static_cast(p.items[0])->body; assert(body->items.size() == 6 && body->items[3]->kind == ExprKind::Name && body->items[4]->kind == ExprKind::Binary); } { Program p = parse("test(1, page: page,\n\t\"per page\": 2, -10, last: f(a: 1))\nonly(key: value)\n", "map-arguments.capy"); auto* c = static_cast(p.items[0]); auto* first = static_cast(c->arguments[1]); auto* last = static_cast(c->arguments[3]); assert(c->arguments.size() == 4 && c->arguments[0]->kind == ExprKind::Integer && first->entries.size() == 2 && first->entries[0].first == "page" && first->entries[1].first == "per page" && first->location.column == 9 && c->arguments[2]->kind == ExprKind::Integer && last->entries.size() == 1); auto* nested = static_cast(last->entries[0].second); assert(static_cast(nested->arguments[0])->entries[0].first == "a"); assert(static_cast(static_cast(p.items[1])->arguments[0])->entries.size() == 1); } { Program p = parse("var x := {}\nvar value := {name: \"Ada\"}\nvar next := { var t := 1 -> t + 1 }\nfunction CLI(request : dval) { value.name }\n", "bare-map.capy"); assert(static_cast(static_cast(p.items[0])->value)->entries.empty()); assert(static_cast(static_cast(p.items[1])->value)->entries.size() == 1); assert(static_cast(p.items[2])->value->kind == ExprKind::Block); auto* member = static_cast(static_cast(p.items[3])->body->items[0]); assert(member->member == "name" && member->location.file == "bare-map.capy" && member->location.line == 4 && member->location.column == 37); } { Program p = parse("function CLI(request : dval) { var value := none; value.a[1]?; value.a?[1] }\n", "presence.capy"); auto* body = static_cast(p.items[0])->body; auto* presence = static_cast(body->items[1]); auto* indexed_presence = static_cast(body->items[2]); assert(static_cast(static_cast(body->items[0])->value)->value == "none" && presence->operator_ == "postfix?" && static_cast(presence->left)->value->kind == ExprKind::Member && static_cast(indexed_presence->value)->operator_ == "postfix?" && static_cast(static_cast(indexed_presence->value)->left)->member == "a" && presence->location.column == 61); } { Program p = parse("0..10\n", "range.capy"); assert(static_cast(p.items[0])->operator_ == ".."); } { Program p = parse("-2147483648\n2147483647\n", "integers.capy"); assert(static_cast(p.items[0])->negative && static_cast(p.items[0])->magnitude == 2147483648ULL); assert(!static_cast(p.items[1])->negative && static_cast(p.items[1])->magnitude == 2147483647ULL); } { Program p = parse("0; 18446744073709551615; -9223372036854775808; 9223372036854775807; 1.5; 2e3; 1..3", "wide.capy"); assert(!static_cast(p.items[0])->negative && static_cast(p.items[0])->magnitude == 0); assert(!static_cast(p.items[1])->negative && static_cast(p.items[1])->magnitude == std::numeric_limits::max()); assert(static_cast(p.items[2])->negative && static_cast(p.items[2])->magnitude == (std::uint64_t{1} << 63)); assert(!static_cast(p.items[3])->negative && static_cast(p.items[3])->magnitude == static_cast(std::numeric_limits::max())); assert(static_cast(p.items[4])->value == 1.5 && static_cast(p.items[5])->value == 2000.0); assert(static_cast(p.items[6])->operator_ == ".."); } { Program p = parse("\"one and\n two\"\n", "string.capy"); assert(static_cast(p.items[0])->value == "one and\n two"); } { Program p = parse("<>

\n", "markup.capy"); auto* m = static_cast(p.items[0]); assert(m->parts.size() == 4 && static_cast(m->parts[0])->value == "

" && static_cast(m->parts[1])->escaped && !static_cast(m->parts[3])->escaped); } { Program p = parse("<>
<>x
\n", "markup.capy"); auto* m = static_cast(p.items[0]); assert(m->parts.size() == 1 && static_cast(m->parts[0])->value == "
x
"); } { Program p = parse("<>

?>

\n", "markup.capy"); auto* m = static_cast(p.items[0]); assert(static_cast(m->parts[1])->value->kind == ExprKind::Markup); } { Program p = parse("<>\n", "markup.capy"); assert(static_cast(static_cast(p.items[0])->parts[0])->value == ""); } { bearer::MarkupContextScanner script; script.consume("\n", "contexts.capy"); auto* markup = static_cast(p.items[0]); std::vector contexts; for (Expr* part : markup->parts) if (auto* field = dynamic_cast(part)) contexts.push_back(field->context); assert(contexts == std::vector({bearer::MarkupContext::html_attribute, bearer::MarkupContext::html_text, bearer::MarkupContext::html_attribute, bearer::MarkupContext::javascript_value, bearer::MarkupContext::javascript_value, bearer::MarkupContext::css_value})); } { Program p = parse("#exports first, second\n#exports third\n", "exports.capy"); auto* first = static_cast(p.items[0]); auto* second = static_cast(p.items[1]); assert(first->names == std::vector({"first", "second"}) && second->names == std::vector({"third"})); } { Program p = parse("#import \"child.capy\" as child\n", "import.capy"); auto* import = static_cast(p.items[0]); assert(import->path == "child.capy" && import->alias == "child"); } { Program p = parse("function value(min : any, max : min::type) min::type { -> min }\n", "dependent.capy"); auto* function = static_cast(p.items[0]); assert(type_name(*function->parameters[1].type_expr) == "min::type" && type_name(*function->return_type) == "min::type"); auto* parameter_type = static_cast(function->parameters[1].type_expr); assert(static_cast(parameter_type->value)->value == "min" && parameter_type->member == "type"); bool rejected = false; try { Program removed = parse("function value(min : any, max : type(min)) type(min) { -> min }\n", "removed.capy"); type_name(*static_cast(removed.items[0])->parameters[1].type_expr); } catch (const Error&) { rejected = true; } assert(rejected); } { Program p = parse("function value(x : s32) s32 { return x }\nfunction value(x : as string) string { return x }\n", "overload.capy"); auto* converted = static_cast(p.items[1]); assert(!static_cast(p.items[0])->parameters[0].convert && converted->parameters[0].convert); DeclarationIndex index; index.add_program(p); assert(index.functions.size() == 2); } { Program p = parse("function gather(prefix : string, ...values : as string) [string] { return values }\nfunction CLI(request : dval) { gather(\"x\", ...[\"a\", \"b\"]) }\n", "variadic.capy"); auto* gather = static_cast(p.items[0]); assert(gather->parameters.size() == 2 && gather->parameters[1].variadic && gather->parameters[1].convert); auto* call = static_cast(static_cast(p.items[1])->body->items[0]); assert(call->arguments.size() == 2 && call->arguments[1]->kind == ExprKind::Spread); } { Program p = parse("function value(x : s32) s32 { return x }\nfunction value(x : s32) string { return x }\n", "overload.capy"); try { DeclarationIndex().add_program(p); assert(false); } catch (const Error& e) { assert(e.message.find("return type does not distinguish overloads") != std::string::npos); } } expect_error("const answer : s32 = 42\nfunction CLI(request : dval) {}\n", "const declarations were removed"); expect_error("function CLI(request : dval) { type Count = s64 }\n", "type aliases are top-level only"); expect_error("function bad(s32) s32 { return 1 }\n", "name:type annotations"); expect_error("function bad(...values : string, tail : string) {}\n", "variadic parameter must be last"); expect_error("function bad(first : s32 = 1, second : s32) {}\n", "default and capture parameters must be trailing"); expect_error("function bad(first : capture dval, second : s32) {}\n", "default and capture parameters must be trailing"); expect_error("function bad(first : capture dval = {}) {}\n", "cannot be variadic or have a default"); expect_error("function bad(...values : s32 = 1) {}\n", "variadic parameter cannot have a default value"); expect_error("function bad(value : s32 = other) {}\n", "default parameter value must be a literal"); expect_error("function bad(value : s32 = next()) {}\n", "default parameter value must be a literal"); expect_error("function bad(value : dval = [next()]) {}\n", "default parameter value must be a literal"); expect_error("function bad(value : s32 = value = 1) {}\n", "default parameter value must be a literal"); expect_error("function bad(value : dval = {item: next()}) {}\n", "default parameter value must be a literal"); expect_error("{:}\n", "empty dval maps use '{}'"); expect_error("function CLI(request : dval) { var value := 1 as s64 }\n", "call the target type constructor instead"); expect_error("function CLI(request : dval) { for value = [1] {} }\n", "old loop syntax is not supported. Use ':=' after the loop bindings"); expect_error("function CLI(request : dval) { for value in [1] {} }\n", "old loop syntax is not supported. Use ':=' after the loop bindings"); expect_error("function CLI(request : dval) { for value, value := [1] {} }\n", "loop bindings must have different names"); expect_error("trace function bad() string {}\n", "trace modifier applies only to host function declarations"); expect_error("function bad(value : any, value : any) value::type { value }\n", "function parameter 'value' is already declared"); try { parse("\"broken", "bad.capy"); assert(false); } catch (const Error& e) { assert(std::string(e.what()) == "bad.capy:1:1: unterminated string literal"); } expect_error("<>", "empty markup interpolation"); expect_error("<>", "unterminated markup interpolation"); expect_error("<><>x", "tag or attribute name"); expect_error("<>
=\"x\">
", "tag or attribute name"); expect_error("<>
>
", "requires a quoted attribute value"); expect_error("<>", "JavaScript string"); expect_error("<>", "JavaScript comment"); expect_error("<>", "JavaScript value boundary"); expect_error("<>", "CSS string"); expect_error("<>", "CSS value boundary"); expect_error("<>", "CSS comment"); expect_error("<>", "HTML comment or declaration"); expect_error("<>

\">

", "raw markup interpolation is only allowed in HTML text"); expect_error("<>", "raw markup interpolation is only allowed in HTML text"); expect_error("<>", "raw markup interpolation is only allowed in HTML text"); expect_error("<>

missing", "unterminated markup expression"); { Program p = parse("function value() s32 { ->\n42 }\nfunction returned() s32 { return\n7 }\nfunction stop() { return\n}\n", "results.capy"); auto* value = static_cast(p.items[0]); auto* yield = static_cast(value->body->items[0]); auto* returned = static_cast(static_cast(p.items[1])->body->items[0]); auto* stop = static_cast(static_cast(p.items[2])->body->items[0]); assert(static_cast(yield->value)->magnitude == 42 && static_cast(returned->value)->magnitude == 7 && stop->value == nullptr); } expect_error("function value() s32 { -> }\n", "expected expression"); expect_error("function value() s32 { -> return 1 }\n", "block yield requires a value expression"); expect_error("function CLI(request : dval) { while true { -> break } }\n", "block yield requires a value expression"); expect_error("function CLI(request : dval) { break junk }\n", "break does not accept arguments or operators"); expect_error("function CLI(request : dval) { continue() }\n", "continue does not accept arguments or operators"); expect_error("#exports\n", "#exports requires at least one name"); expect_error("#exports first,\n", "expected exported name after ','"); expect_error("function CLI(request : dval) { #exports first }\n", "#exports is a top-level directive"); expect_error("#import child.capy as child\n", "#import requires a string path"); expect_error("function CLI(request : dval) { #import \"child.capy\" as child }\n", "#import is a top-level directive"); expect_error("function meta(x : any) { #compile { emit(x) } }\n", "#compile compile-time metaprogramming is deferred beyond Capy phase 3"); expect_error("#wat", "unknown compiler directive #wat"); expect_error("@", "unexpected character '@'"); expect_error("1s32", "numeric suffixes were removed"); expect_error("1s64", "numeric suffixes were removed"); expect_error("1u64", "numeric suffixes were removed"); expect_error("1u64x", "numeric suffixes were removed"); expect_error("1e", "invalid f64 exponent"); expect_error("18446744073709551616", "outside the u64 range"); expect_error("999999999999999999999999999999", "outside the u64 range"); try { Parser({}).token(); assert(false); } catch (const Error& e) { assert(e.message == "parser received no tokens"); } { Lexer lexer("hé\n", "utf8.capy"); auto tokens = lexer.tokens(); assert(tokens[0].location.column == 1 && tokens[1].location.column == 3 && tokens[1].location.offset == 2); } { const std::string source = "#import \"child.capy\" as child\nfunction CLI(request : dval) { var text := \"héllo\\nworld\" // keep\nif true { print(\"this string makes the call exceed the formatter line limit and proves that every map entry receives one extra tab\", page: 1, limit: 2) } else { return }\n}\n"; const std::string formatted = format_source(source); assert(formatted == "#import \"child.capy\" as child\nfunction CLI(request : dval)\n{\n\tvar text := \"héllo\\nworld\" // keep\n\tif true\n\t{\n\t\tprint(\"this string makes the call exceed the formatter line limit and proves that every map entry receives one extra tab\",\n\t\t\tpage: 1,\n\t\t\tlimit: 2\n\t\t)\n\t}\n\telse\n\t{\n\t\treturn\n\t}\n}\n"); assert(format_source(formatted) == formatted); assert(parse(source).items.size() == parse(formatted).items.size()); auto input_tokens = Lexer(source).tokens(), output_tokens = Lexer(formatted).tokens(); input_tokens.erase(std::remove_if(input_tokens.begin(), input_tokens.end(), [](const Token& token) { return token.kind == TokenKind::newline; }), input_tokens.end()); output_tokens.erase(std::remove_if(output_tokens.begin(), output_tokens.end(), [](const Token& token) { return token.kind == TokenKind::newline; }), output_tokens.end()); assert(input_tokens.size() == output_tokens.size()); for (std::size_t index = 0; index < input_tokens.size(); ++index) assert(input_tokens[index].kind == output_tokens[index].kind && input_tokens[index].text == output_tokens[index].text); assert(format_source("function CLI(request : dval) { <> raw markup }\n") == "function CLI(request : dval)\n{\n\t<> raw markup \n}\n"); } { const std::string source = "#exports CLI \n\n\nfunction CLI(request : dval) { if !bool(false) { print(\":\", \")\", \",\", \"}\", \"function\") } }\n// last comment \n"; const std::string formatted = format_source(source); assert(formatted.starts_with("#exports CLI \n\nfunction")); assert(formatted.find("if !bool(false)") != std::string::npos); assert(formatted.ends_with("// last comment \n")); assert(format_source(formatted) == formatted); assert(parse(source).items.size() == parse(formatted).items.size()); const std::string host = "host function example(value : s32) s32 // host \nfunction next() {}\n"; assert(parse(format_source(host)).items.size() == 2); } { const std::string source = "function f(n : s32) s32 { var a := -1; var b := n * -2; if !(a < b) { return -a }; for i := 0..n { a = a + i }; -> a - b }"; const std::string expected = "function f(n : s32) s32\n{\n\tvar a := -1;\n\tvar b := n * -2;\n\tif !(a < b)\n\t{\n\t\treturn -a\n\t}\n\t;\n\tfor i := 0..n\n\t{\n\t\ta = a + i\n\t}\n\t;\n\t-> a - b\n}\n"; assert(format_source(source) == expected); assert(format_source(expected) == expected); const std::string annotations = "struct Pair { x : s32\ny : s32 }\nfunction f(n : as s32) { var x : dval = {key: -1}\nprint(key: x) }\n"; const std::string formatted = format_source(annotations); assert(formatted.find("x : s32") != std::string::npos); assert(formatted.find("y : s32") != std::string::npos); assert(formatted.find("n : as s32") != std::string::npos); assert(formatted.find("var x : dval = { key: -1 }") != std::string::npos); assert(formatted.find("print(key: x)") != std::string::npos); assert(format_source(formatted) == formatted); const std::string fixture = "struct Pair{x:s32,y:s32}\nfunction f(...rest: [s32]) { var m := {key: 1}\nvar callback := function(x:s32) { return x } }\n"; const std::string expected_fixture = "struct Pair\n{\n\tx : s32,\n\ty : s32\n}\nfunction f(...rest : [s32])\n{\n\tvar m := { key: 1 }\n\tvar callback := function(x : s32)\n\t{\n\t\treturn x\n\t}\n}\n"; assert(format_source(fixture) == expected_fixture); assert(format_source(expected_fixture) == expected_fixture); const std::string direct_contexts = "function f(callback : function(s32) s32, ...rest : [s32]) s32 { return callback(1) }\nfunction g() { var callback := function(x : s32) s32 { return x }\nvar value := consume(function(x : s32) s32 { return x }, ...[1], fallback: 2)\nvar map := {outer: {inner: value}} }\nstruct Fields { first : s32\nsecond : s32 }\n"; const std::string direct_formatted = format_source(direct_contexts); assert(direct_formatted.find("struct Fields\n{\n\tfirst : s32\n\tsecond : s32\n}") != std::string::npos); assert(direct_formatted.find("function(x : s32) s32\n\t{") != std::string::npos); assert(format_source(direct_formatted) == direct_formatted); } { std::ifstream input("scripts/format/canonical.capy"), expected("scripts/format/canonical.capy.expected"); assert(input && expected); std::ostringstream source, output; source << input.rdbuf(); output << expected.rdbuf(); assert(format_source(source.str()) == output.str()); assert(format_source(output.str()) == output.str()); } for (int index = 1; index < argc; ++index) { std::ifstream input(argv[index], std::ios::binary); assert(input && "fixture must be readable"); std::ostringstream source; source << input.rdbuf(); parse(source.str(), argv[index]); } std::cout << "native Capy frontend tests passed"; if (argc > 1) std::cout << " and " << argc - 1 << " tracked fixtures parsed"; std::cout << '\n'; }