Private
Public Access
Handlers take (props : dval, request : dval) for COMPONENT and TASK and (request : dval) otherwise; every parameter is optional and keeps its name, and request is bound in every handler body. name : capture type fills an omitted argument from the caller variable of that name. Exported (dval) dval functions carry an implicit request capture. Request builtins capture request; redirect writes request.out. The runtime builds one request value per request, passes request and props handles to handlers, applies request.out once after the top-level handler, and drops the pending-props slot, handler frames, per-handler refresh, and current_request(). Core ABI 38, unit ABI 39. Legacy request type checks and the removed-API migration table are gone from the compiler. Documentation, generated routes, tests, and the project home follow; the home page loses the demo and starter. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
409 lines
25 KiB
C++
409 lines
25 KiB
C++
#include "frontend.h"
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <sstream>
|
|
#include <string>
|
|
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<TypeAlias*>(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<Function*>(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<Function*>(p.items[0]);
|
|
auto* opt = static_cast<MapLiteral*>(f->parameters[3].default_value);
|
|
auto* items = static_cast<ArrayLiteral*>(opt->entries[0].second);
|
|
assert(f->parameters.size() == 4 && !f->parameters[0].default_value && static_cast<String*>(f->parameters[1].default_value)->value == "x" &&
|
|
static_cast<Name*>(f->parameters[2].default_value)->value == "true" && items->items.size() == 2 &&
|
|
static_cast<MapLiteral*>(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<Variable*>(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<Lambda*>(static_cast<Variable*>(p.items[0])->value)->parameters[0].name == "__bearer_value");
|
|
assert(static_cast<For*>(p.items[1])->names[1] == "__bearer_key");
|
|
}
|
|
{
|
|
Program p = parse("function CLI(request : dval) { print(\";\", \",\", \"}\") }\n", "test.capy");
|
|
auto* f = static_cast<Function*>(p.items[0]);
|
|
assert(static_cast<Call*>(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<Call*>(static_cast<Function*>(p.items[0])->body->items[0]);
|
|
auto* member = static_cast<Member*>(call->function);
|
|
assert(member->member == "callback" && static_cast<Name*>(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<Variable*>(p.items[0]);
|
|
auto* b = static_cast<Variable*>(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<Call*>(static_cast<Variable*>(p.items[0])->value);
|
|
assert(static_cast<MapLiteral*>(c->arguments[0])->entries.size() == 2);
|
|
assert(static_cast<For*>(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<Function*>(p.items[0])->body;
|
|
auto* branch = static_cast<If*>(body->items[1]);
|
|
auto* loop = static_cast<While*>(branch->else_body->items[0]);
|
|
assert(body->items.size() == 3 && static_cast<Variable*>(body->items[0])->inferred && !static_cast<Return*>(branch->then_body->items[0])->value &&
|
|
loop->body->items.size() == 2 && loop->body->items[1]->kind == ExprKind::Break && static_cast<Return*>(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<Function*>(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<Call*>(p.items[0]);
|
|
auto* first = static_cast<MapLiteral*>(c->arguments[1]);
|
|
auto* last = static_cast<MapLiteral*>(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<Call*>(last->entries[0].second);
|
|
assert(static_cast<MapLiteral*>(nested->arguments[0])->entries[0].first == "a");
|
|
assert(static_cast<MapLiteral*>(static_cast<Call*>(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<MapLiteral*>(static_cast<Variable*>(p.items[0])->value)->entries.empty());
|
|
assert(static_cast<MapLiteral*>(static_cast<Variable*>(p.items[1])->value)->entries.size() == 1);
|
|
assert(static_cast<Variable*>(p.items[2])->value->kind == ExprKind::Block);
|
|
auto* member = static_cast<Member*>(static_cast<Function*>(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<Function*>(p.items[0])->body;
|
|
auto* presence = static_cast<Binary*>(body->items[1]);
|
|
auto* indexed_presence = static_cast<Index*>(body->items[2]);
|
|
assert(static_cast<Name*>(static_cast<Variable*>(body->items[0])->value)->value == "none" && presence->operator_ == "postfix?" &&
|
|
static_cast<Index*>(presence->left)->value->kind == ExprKind::Member && static_cast<Binary*>(indexed_presence->value)->operator_ == "postfix?" &&
|
|
static_cast<Member*>(static_cast<Binary*>(indexed_presence->value)->left)->member == "a" && presence->location.column == 61);
|
|
}
|
|
{
|
|
Program p = parse("0..10\n", "range.capy");
|
|
assert(static_cast<Binary*>(p.items[0])->operator_ == "..");
|
|
}
|
|
{
|
|
Program p = parse("-2147483648\n2147483647\n", "integers.capy");
|
|
assert(static_cast<Integer*>(p.items[0])->negative && static_cast<Integer*>(p.items[0])->magnitude == 2147483648ULL);
|
|
assert(!static_cast<Integer*>(p.items[1])->negative && static_cast<Integer*>(p.items[1])->magnitude == 2147483647ULL);
|
|
}
|
|
{
|
|
Program p = parse("0; 18446744073709551615; -9223372036854775808; 9223372036854775807; 1.5; 2e3; 1..3", "wide.capy");
|
|
assert(!static_cast<Integer*>(p.items[0])->negative && static_cast<Integer*>(p.items[0])->magnitude == 0);
|
|
assert(!static_cast<Integer*>(p.items[1])->negative && static_cast<Integer*>(p.items[1])->magnitude == std::numeric_limits<std::uint64_t>::max());
|
|
assert(static_cast<Integer*>(p.items[2])->negative && static_cast<Integer*>(p.items[2])->magnitude == (std::uint64_t{1} << 63));
|
|
assert(!static_cast<Integer*>(p.items[3])->negative && static_cast<Integer*>(p.items[3])->magnitude == static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()));
|
|
assert(static_cast<Float*>(p.items[4])->value == 1.5 && static_cast<Float*>(p.items[5])->value == 2000.0);
|
|
assert(static_cast<Binary*>(p.items[6])->operator_ == "..");
|
|
}
|
|
{
|
|
Program p = parse("\"one and\n two\"\n", "string.capy");
|
|
assert(static_cast<String*>(p.items[0])->value == "one and\n two");
|
|
}
|
|
{
|
|
Program p = parse("<><h1><?= title ?></h1><?: trusted ?></>\n", "markup.capy");
|
|
auto* m = static_cast<Markup*>(p.items[0]);
|
|
assert(m->parts.size() == 4 && static_cast<MarkupText*>(m->parts[0])->value == "<h1>" && static_cast<MarkupField*>(m->parts[1])->escaped &&
|
|
!static_cast<MarkupField*>(m->parts[3])->escaped);
|
|
}
|
|
{
|
|
Program p = parse("<><div><><span>x</span></></div></>\n", "markup.capy");
|
|
auto* m = static_cast<Markup*>(p.items[0]);
|
|
assert(m->parts.size() == 1 && static_cast<MarkupText*>(m->parts[0])->value == "<div><span>x</span></div>");
|
|
}
|
|
{
|
|
Program p = parse("<><p><?= <><b><?= clone(\"x\") ?></b></> ?></p></>\n", "markup.capy");
|
|
auto* m = static_cast<Markup*>(p.items[0]);
|
|
assert(static_cast<MarkupField*>(m->parts[1])->value->kind == ExprKind::Markup);
|
|
}
|
|
{
|
|
Program p = parse("<><script>const close = \"\\</>\";</script></>\n", "markup.capy");
|
|
assert(static_cast<MarkupText*>(static_cast<Markup*>(p.items[0])->parts[0])->value == "<script>const close = \"</>\";</script>");
|
|
}
|
|
{
|
|
bearer::MarkupContextScanner script;
|
|
script.consume("<script/>");
|
|
assert(script.context() == bearer::MarkupContext::javascript_value);
|
|
bearer::MarkupContextScanner style;
|
|
style.consume("<style/>");
|
|
assert(style.context() == bearer::MarkupContext::css_value);
|
|
bearer::MarkupContextScanner css_comment;
|
|
css_comment.consume("<style>.x { color: /* note */ ");
|
|
assert(css_comment.context() == bearer::MarkupContext::css_value);
|
|
bearer::MarkupContextScanner javascript_comment;
|
|
javascript_comment.consume("<script>const value = // note\xE2\x80\xA8");
|
|
assert(javascript_comment.context() == bearer::MarkupContext::javascript_value);
|
|
bearer::MarkupContextScanner javascript_keyword;
|
|
javascript_keyword.consume("<script>const value = new ");
|
|
assert(javascript_keyword.context() == bearer::MarkupContext::javascript_value);
|
|
bearer::MarkupContextScanner javascript_division;
|
|
javascript_division.consume("<script>const ratio = total / ");
|
|
assert(javascript_division.context() == bearer::MarkupContext::javascript_value);
|
|
bearer::MarkupContextScanner css_division;
|
|
css_division.consume("<style>.x { width: calc(1 / ");
|
|
assert(css_division.context() == bearer::MarkupContext::css_value);
|
|
}
|
|
{
|
|
Program p = parse("<><p title=\"<?= title ?>\"><?= body ?></p><a href=\"<?= url ?>\">link</a><script>const value = <?= script ?>;</script><script>function value() { return <?= returned ?>; }</script><style>.x { content: <?= css ?>; }</style></>\n", "contexts.capy");
|
|
auto* markup = static_cast<Markup*>(p.items[0]);
|
|
std::vector<bearer::MarkupContext> contexts;
|
|
for (Expr* part : markup->parts)
|
|
if (auto* field = dynamic_cast<MarkupField*>(part)) contexts.push_back(field->context);
|
|
assert(contexts == std::vector<bearer::MarkupContext>({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<Exports*>(p.items[0]);
|
|
auto* second = static_cast<Exports*>(p.items[1]);
|
|
assert(first->names == std::vector<std::string>({"first", "second"}) && second->names == std::vector<std::string>({"third"}));
|
|
}
|
|
{
|
|
Program p = parse("#import \"child.capy\" as child\n", "import.capy");
|
|
auto* import = static_cast<Import*>(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<Function*>(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<ScopeLookup*>(function->parameters[1].type_expr);
|
|
assert(static_cast<Name*>(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<Function*>(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<Function*>(p.items[1]);
|
|
assert(!static_cast<Function*>(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<Function*>(p.items[0]);
|
|
assert(gather->parameters.size() == 2 && gather->parameters[1].variadic && gather->parameters[1].convert);
|
|
auto* call = static_cast<Call*>(static_cast<Function*>(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("<><?= value </>", "unterminated markup interpolation");
|
|
expect_error("<><<?= name ?>>x</div></>", "tag or attribute name");
|
|
expect_error("<><div <?= name ?>=\"x\"></div></>", "tag or attribute name");
|
|
expect_error("<><div title=<?= value ?>></div></>", "requires a quoted attribute value");
|
|
expect_error("<><script>const value = \"<?= value ?>\";</script></>", "JavaScript string");
|
|
expect_error("<><script>// <?= value ?>\n</script></>", "JavaScript comment");
|
|
expect_error("<><script>window.name_<?= value ?> = true;</script></>", "JavaScript value boundary");
|
|
expect_error("<><style>.x { content: \"<?= value ?>\"; }</style></>", "CSS string");
|
|
expect_error("<><style>.x { color-name-<?= value ?>: red; }</style></>", "CSS value boundary");
|
|
expect_error("<><style>/* <?= value ?> */</style></>", "CSS comment");
|
|
expect_error("<><!-- <?= value ?> --></>", "HTML comment or declaration");
|
|
expect_error("<><p title=\"<?: value ?>\"></p></>", "raw markup interpolation is only allowed in HTML text");
|
|
expect_error("<><script><?: value ?></script></>", "raw markup interpolation is only allowed in HTML text");
|
|
expect_error("<><style><?: value ?></style></>", "raw markup interpolation is only allowed in HTML text");
|
|
expect_error("<><p>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<Function*>(p.items[0]);
|
|
auto* yield = static_cast<Yield*>(value->body->items[0]);
|
|
auto* returned = static_cast<Return*>(static_cast<Function*>(p.items[1])->body->items[0]);
|
|
auto* stop = static_cast<Return*>(static_cast<Function*>(p.items[2])->body->items[0]);
|
|
assert(static_cast<Integer*>(yield->value)->magnitude == 42 && static_cast<Integer*>(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 <?= string(\"x\") ?> </> }\n") == "function CLI(request : dval)\n{\n\t<> raw markup <?= string(\"x\") ?> </>\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';
|
|
}
|