#include "compiler.h" #include "../src/wasm/capy_backtrace.h" #include #include #include #include #include #include #include #include #include #include #include #include int main() { capy::CompileOptions options; options.source_path = "native-test.capy"; options.module_name = "native-test.wasm"; options.abi_version = 11; capy::ParsedSourceCache parsed_cache; capy::CompileOptions cached_options = options; cached_options.canonical_source_identity = "capy-test://cached-source"; cached_options.parsed_source_cache = &parsed_cache; const std::string cached_source = "function CLI(request : dval) { print(string(request.body)) }\n"; const auto cached_first = capy::compile_bearer_unit(cached_source, cached_options); const auto cached_second = capy::compile_bearer_unit(cached_source, cached_options); const auto uncached = capy::compile_bearer_unit(cached_source, options); assert(cached_first.wasm == cached_second.wasm && cached_first.source_map == cached_second.source_map); assert(cached_first.wasm == uncached.wasm && cached_first.source_map == uncached.source_map); const auto after_hit = parsed_cache.stats(); assert(after_hit.hits >= 2 && after_hit.entries == 1 && after_hit.pinned_entries == 1 && after_hit.pinned_source_bytes > 0); capy::compile_bearer_unit("function CLI(request : dval) { print(string(request.body), 2) }\n", cached_options); const auto after_content_change = parsed_cache.stats(); assert(after_content_change.misses > after_hit.misses); cached_options.source_path = "other-diagnostic.capy"; cached_options.canonical_source_identity = "capy-test://cached-source"; capy::compile_bearer_unit(cached_source, cached_options); assert(parsed_cache.stats().misses > after_content_change.misses); const auto entries_before_failure = parsed_cache.stats().entries; cached_options.source_path = "bad.capy"; cached_options.canonical_source_identity = "capy-test://bad"; try { capy::compile_bearer_unit("function", cached_options); assert(false); } catch (const capy::Error&) {} assert(parsed_cache.stats().entries == entries_before_failure); // These identities produced the same old newline-concatenated key. cached_options.source_path = "path-c"; cached_options.canonical_source_identity = "identity\npath-b"; const auto newline_first = capy::compile_bearer_unit(cached_source, cached_options); const auto misses_before_newline_second = parsed_cache.stats().misses; cached_options.source_path = "path-b\npath-c"; cached_options.canonical_source_identity = "identity"; const auto newline_second = capy::compile_bearer_unit(cached_source, cached_options); assert(parsed_cache.stats().misses > misses_before_newline_second && newline_first.source_map != newline_second.source_map); unsigned hit_polls = 0; cached_options.source_path = "native-test.capy"; cached_options.canonical_source_identity = "capy-test://cached-source"; cached_options.cancelled = [&] { return ++hit_polls == 4; }; try { capy::compile_bearer_unit(cached_source, cached_options); assert(false); } catch (const capy::Error&) {} assert(hit_polls == 4); // initial, hash start/end, then final cache-hit return poll cached_options.cancelled = {}; std::mutex contention_mutex; std::condition_variable contention_ready, contention_release; bool first_parsing = false, second_finished = false, first_cancelled = false; const std::string contention_source = "function CLI(request : dval) {}\n" + std::string(4097, ' '); capy::CompileOptions contention_options = options; contention_options.source_path = "contention.capy"; contention_options.canonical_source_identity = "capy-test://contention"; contention_options.parsed_source_cache = &parsed_cache; std::thread first([&] { unsigned polls = 0; contention_options.cancelled = [&] { if (++polls == 5) { std::unique_lock lock(contention_mutex); first_parsing = true; contention_ready.notify_one(); contention_release.wait(lock, [&] { return second_finished; }); } return polls == 8; }; try { capy::compile_bearer_unit(contention_source, contention_options); } catch (const capy::Error&) { first_cancelled = true; } }); { std::unique_lock lock(contention_mutex); contention_ready.wait(lock, [&] { return first_parsing; }); } std::thread second([&] { contention_options.cancelled = {}; capy::compile_bearer_unit(contention_source, contention_options); std::lock_guard lock(contention_mutex); second_finished = true; contention_release.notify_one(); }); first.join(); second.join(); assert(first_cancelled); capy::ParsedSourceCache bounded(2, 256, 96); capy::CompileOptions bounded_options = options; bounded_options.parsed_source_cache = &bounded; for (const auto& identity : {"a", "b", "c"}) { bounded_options.canonical_source_identity = identity; capy::compile_bearer_unit("function CLI(request : dval) {}", bounded_options); } assert(bounded.stats().entries == 2 && bounded.stats().charged_bytes <= 256 && bounded.stats().evictions == 1); bounded_options.canonical_source_identity = "large"; capy::compile_bearer_unit("function CLI(request : dval) { print(\"" + std::string(97, ' ') + "\") }", bounded_options); assert(bounded.stats().oversize == 1); if (const pid_t child = fork(); child == 0) { if (bounded.stats().entries != 0) _exit(1); bounded_options.canonical_source_identity = "child"; capy::compile_bearer_unit("function CLI(request : dval) {}", bounded_options); _exit(bounded.stats().entries == 1 ? 0 : 1); } else { int status = 0; assert(child > 0 && waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0); } bounded.clear(); assert(bounded.stats().entries == 0 && bounded.stats().pinned_entries == 0); options.cancelled = {}; options.source_path = "native-test.capy"; options.canonical_source_identity.clear(); auto result = capy::compile_bearer_unit("function CLI(request : dval) { print(1, \"ok\") }\n", options); assert(result.wasm.size() >= 4 && result.wasm[0] == 0 && result.wasm[1] == 'a' && result.wasm[2] == 's' && result.wasm[3] == 'm'); auto validated = capy::wasm::validate_bearer_unit(result.wasm, {.bearer_abi_version = "11"}); assert(validated.valid); assert(validated.bearer_module == "native-test.wasm"); assert(result.source_map.starts_with("BEARER_SOURCE_MAP_V1\tnative-test.wasm\n")); const auto raw_markup = capy::compile_bearer_unit("function CLI(request : dval) { print(<>

\") ?>

) }\n", options); assert(capy::wasm::validate_bearer_unit(raw_markup.wasm, {.bearer_abi_version = "11"}).valid); const auto markup_strings = capy::compile_bearer_unit( "function literal() string { -> <>

literal

}\n" "function joined() string { -> literal() + <>

\") ?>

}\n" "function CLI(request : dval) { print(joined()) }\n", options); assert(capy::wasm::validate_bearer_unit(markup_strings.wasm, {.bearer_abi_version = "11"}).valid); const auto empty_if_body = capy::compile_bearer_unit("function CLI(request : dval) { if true {} }\n", options); assert(capy::wasm::validate_bearer_unit(empty_if_body.wasm, {.bearer_abi_version = "11"}).valid); const auto typed_array_reassignment = capy::compile_bearer_unit( "function CLI(request : dval) { var source : [dval] = []\n var target : [dval] = []\n target.push(dval(1))\n source = target\n print(length(source)) }\n", options); assert(capy::wasm::validate_bearer_unit(typed_array_reassignment.wasm, {.bearer_abi_version = "11"}).valid); const auto defaults = capy::compile_bearer_unit( "type Count = s64\n" "function add(left : s32, right : s32 = 2) s32 { -> left + right }\n" "function suffix(value : string, tail : string = \"!\") string { -> value + tail }\n" "function text(value : as string, suffix : string = \"!\") string { -> value + suffix }\n" "function s64(value : s32, extra : s32 = 0) s64 { -> s64(3) }\n" "function literals(a : s32 = 1, b : s64 = 2, c : u64 = 3, d : f64 = 4.5, e : bool = false, f : string = \"x\") {}\n" "function next(value : s32) s32 { -> value }\n" "function ordered(first : s32, second : s32 = 2) s32 { -> first + second }\n" "function CLI(request : dval) { var first := add(1); var second := \"x\".suffix(); var converted := text(1); var count : Count = Count(3); literals(); print(ordered(next(1)), first, second, converted, count) }\n", options); assert(capy::wasm::validate_bearer_unit(defaults.wasm, {.bearer_abi_version = "11"}).valid); assert(defaults.source_map.find("\t5\t") != std::string::npos); const auto dependent_type = capy::compile_bearer_unit( "function f(min : any, max : min::type) min::type { -> min }\n" "function legacy(value : any) value::type { -> value }\n" "function CLI(request : dval) { var value := f(1, 2); print(value, legacy(4)) }\n", options); assert(capy::wasm::validate_bearer_unit(dependent_type.wasm, {.bearer_abi_version = "11"}).valid); const auto reflection = capy::compile_bearer_unit( "struct Reflected { count : s32 }\n" "function CLI(request : dval) { var value := Reflected(s32(1)); print(value::type_name, value::size, value::items.count.value, s64(1)::type_name) }\n", options); const std::string reflection_bytes(reflection.wasm.begin(), reflection.wasm.end()); assert(capy::wasm::validate_bearer_unit(reflection.wasm, {.bearer_abi_version = "11"}).valid); assert(reflection_bytes.find("bearer_capy_reflect_struct") != std::string::npos && reflection_bytes.find("Reflected") != std::string::npos); const auto metadata_only = capy::compile_bearer_unit( "struct MetadataOnly { count : s32 }\nfunction CLI(request : dval) {}\n", options); const std::string metadata_only_bytes(metadata_only.wasm.begin(), metadata_only.wasm.end()); assert(metadata_only_bytes.find("MetadataOnly") != std::string::npos && metadata_only_bytes.find("count") != std::string::npos); assert(metadata_only_bytes.find("bearer_capy_reflect_struct") == std::string::npos); for (const auto& [source, expected] : { std::pair{"function f(min : any, max : min::type) min::type { -> min }\nfunction CLI(request : dval) { f(1, \"x\") }\n", "no overload f"}, std::pair{"function CLI(request : dval) { gen_noise(s32(0), s32(1), u64(1), u64(2)) }\n", "no overload __gen_noise"}, std::pair{"function f(min : s32, max : min::type) s32 { -> min }\n", "earlier any parameter"}, std::pair{"function f(min : any, max : later::type) min::type { -> min }\n", "unknown or forward parameter"}, std::pair{"function f(value : s32) type(value) { -> value }\n", "type(value) was removed"}, std::pair{"function CLI(request : dval) { print(s32(1)::size) }\n", "value::size requires a struct"}, std::pair{"function CLI(request : dval) { print(s32(1)::items) }\n", "value::items requires a struct"}, std::pair{"function CLI(request : dval) { print(s32(1)::missing) }\n", "unknown scope member 'missing'"}, }) { try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error& error) { assert(error.message.find(expected) != std::string::npos); } } const auto contextual_integers = capy::compile_bearer_unit( "struct WideValue { value : u64 }\n" "function parameter(value : s64) s64 { -> value }\n" "function returned() u64 { return 18446744073709551615 }\n" "function implicit() s64 { -> 8 }\n" "function pick(value : s32) s32 { -> 1 }\nfunction pick(value : s64) s32 { -> 2 }\n" "function CLI(request : dval) { var a : s64 = 8; var b := s64(8); var c : u64 = 9; var d := u64(9); " "var minimum : s64 = -9223372036854775808; var minimum_inferred := s64(-9223372036854775808); " "var maximum : u64 = 18446744073709551615; var maximum_inferred := u64(18446744073709551615); " "var values : [u64] = [18446744073709551615]; values.push(1); values.insert(0, 2); values.resize(4, 3); var field := WideValue(18446744073709551615); " "var callback : function(value : s64) s64 = parameter; " "a = 9223372036854775807; print(callback(8) + s64(1), parameter(8), returned(), implicit(), maximum == 18446744073709551615, pick(8), " "a, b, c, d, minimum, minimum_inferred, maximum, maximum_inferred, values[0], field.value) }\n", options); assert(capy::wasm::validate_bearer_unit(contextual_integers.wasm, {.bearer_abi_version = "11"}).valid); for (const auto& [source, expected] : { std::pair{"function CLI(request : dval) { var value := 9223372036854775808 }\n", "outside the s64 range"}, std::pair{"function CLI(request : dval) { var value : s64 = 9223372036854775808 }\n", "outside the s64 range"}, std::pair{"function CLI(request : dval) { var value : u64 = -1 }\n", "outside the u64 range"}, std::pair{"function CLI(request : dval) { var value := u64(18446744073709551616) }\n", "outside the u64 range"}, std::pair{"function CLI(request : dval) { var value := s64(-9223372036854775809) }\n", "outside the s64 range"}, std::pair{"function CLI(request : dval) { var value := 1s64 }\n", "numeric suffixes were removed"}, std::pair{"function CLI(request : dval) { <> }\n", "f64 markup interpolation is not supported"}, std::pair{"function CLI(request : dval) { <> }\n", "JavaScript string"}, std::pair{"function CLI(request : dval) { <>
>
}\n", "requires a quoted attribute value"}, std::pair{"function legacy() markup { -> \"x\" }\nfunction CLI(request : dval) {}\n", "unknown type 'markup'"}, }) { try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error& error) { assert(error.message.find(expected) != std::string::npos); } } const auto default_string_arc = capy::compile_bearer_unit( "function echo(value : string = \"x\") string { -> value }\nfunction CLI(request : dval) { var value := echo(); print(arc_live()) }\n", options); assert(capy::wasm::validate_bearer_unit(default_string_arc.wasm, {.bearer_abi_version = "11"}).valid); const std::string dval_default_source = "function fresh(opt : dval = {nested: {items: [1, false, \"x\"]}}) dval { -> opt }\n" "function CLI(request : dval) { var first := fresh(); first.nested.items[0] = 2; var second := fresh(); print(s32(first.nested.items[0]), s32(second.nested.items[0]), arc_live()) }\n"; const auto dval_defaults = capy::compile_bearer_unit(dval_default_source, options); const auto dval_defaults_repeat = capy::compile_bearer_unit(dval_default_source, options); assert(capy::wasm::validate_bearer_unit(dval_defaults.wasm, {.bearer_abi_version = "11"}).valid); assert(dval_defaults.wasm == dval_defaults_repeat.wasm && dval_defaults.source_map == dval_defaults_repeat.source_map); assert(dval_defaults.source_map.find("\t2\t") != std::string::npos); const std::string dval_default_bytes(dval_defaults.wasm.begin(), dval_defaults.wasm.end()); assert(dval_default_bytes.find("bearer_dv_build") != std::string::npos); const auto exact_before_default = capy::compile_bearer_unit( "function select(value : s32) s32 { -> 1 }\nfunction select(value : s32, suffix : string = \"x\") s32 { -> 2 }\nfunction CLI(request : dval) { print(select(1)) }\n", options); assert(capy::wasm::validate_bearer_unit(exact_before_default.wasm, {.bearer_abi_version = "11"}).valid); const auto dval_constructors = capy::compile_bearer_unit( "type Text = string\n" "function show(value : as Text) string { -> value }\n" "function next_value() dval { -> dval(\"7\") }\n" "function next_fallback() string { -> \"fallback\" }\n" "function CLI(request : dval) { var value := next_value(); print(string(value), string(value, {fallback: next_fallback()}), bool(value), bool(value, {fallback: true}), s32(value), s32(value, {fallback: 7}), s64(value), s64(value, {fallback: s64(7)}), u64(value), u64(value, {fallback: u64(7)}), f64(value), f64(value, {fallback: 7.0}), show(value), arc_live()) }\n", options); assert(capy::wasm::validate_bearer_unit(dval_constructors.wasm, {.bearer_abi_version = "11"}).valid); const std::string dval_constructor_bytes(dval_constructors.wasm.begin(), dval_constructors.wasm.end()); for (const char* import : {"bearer_dv_extract_string", "bearer_dv_extract_bool", "bearer_dv_extract_s32", "bearer_dv_extract_s64", "bearer_dv_extract_u64", "bearer_dv_extract_f64"}) assert(dval_constructor_bytes.find(import) != std::string::npos); const auto unrelated_dval_imports = capy::compile_bearer_unit("function CLI(request : dval) { print(\"plain\") }\n", options); const std::string unrelated_dval_bytes(unrelated_dval_imports.wasm.begin(), unrelated_dval_imports.wasm.end()); assert(unrelated_dval_bytes.find("bearer_format_s64") == std::string::npos && unrelated_dval_bytes.find("bearer_format_u64") == std::string::npos); const auto plain_comparison_condition = capy::compile_bearer_unit("function CLI(request : dval) { if 1 == 1 { print(\"x\") } }\n", options); const std::string plain_comparison_condition_bytes(plain_comparison_condition.wasm.begin(), plain_comparison_condition.wasm.end()); assert(plain_comparison_condition_bytes.find("bearer_format_s64") == std::string::npos && plain_comparison_condition_bytes.find("bearer_format_u64") == std::string::npos); const auto condition_coercions = capy::compile_bearer_unit( "function CLI(request : dval) { " "var nonzero_u64 := u64(7); var zero_u64 := u64(0); var nonzero_s32 := 5; var zero_s32 := 0; " "var nonzero_dval := dval(u64(7)); var zero_dval := dval(u64(0)); var bool_dval := dval(true); var run := u64(1); var n := u64(0); " "if nonzero_u64 { print(\"u64-nonzero\") } if zero_u64 { print(\"u64-zero\") } " "if nonzero_s32 { print(\"s32-nonzero\") } if zero_s32 { print(\"s32-zero\") } " "if nonzero_dval { print(\"dval-nonzero\") } if zero_dval { print(\"dval-zero\") } if bool_dval { print(\"dval-bool\") } " "if true { print(\"bool\") } if dval(\"true\") { print(\"string-true\") } if dval(\"1\") { print(\"string-one\") } if dval(\"yes\") { print(\"string-yes\") } " "if dval(\"hello\") { print(\"string-hello\") } if dval(\"0\") { print(\"string-zero\") } if dval(\"\") { print(\"string-empty\") } " "while run { run = u64(0) } while n < u64(2) { n = u64(2) } " "}\n", options); assert(capy::wasm::validate_bearer_unit(condition_coercions.wasm, {.bearer_abi_version = "11"}).valid); const std::string condition_coercion_bytes(condition_coercions.wasm.begin(), condition_coercions.wasm.end()); assert(condition_coercion_bytes.find("bearer_dv_extract_bool") != std::string::npos); const auto coercive_parameter_reproducer = capy::compile_bearer_unit( "function truth(flag : as bool) bool { -> flag }\n" "function CLI(request : dval) { print(truth(dval(\"hello\"))) }\n", options); assert(capy::wasm::validate_bearer_unit(coercive_parameter_reproducer.wasm, {.bearer_abi_version = "11"}).valid); for (const char* old_name : {"dval_string", "dval_bool", "dval_s32", "dval_f64", "dval_to_string", "dval_to_bool", "dval_to_f64", "dval_to_s64", "dval_to_u64"}) try { capy::compile_bearer_unit(std::string("function CLI(request : dval) { ") + old_name + "(dval(\"x\")) }\n", options); assert(false); } catch (const capy::Error& error) { assert(error.message.find("no overload") != std::string::npos); } try { capy::compile_bearer_unit("function CLI(request : dval) { request_route_from_raw_path(\"path\") }\n", options); assert(false); } catch (const capy::Error& error) { assert(error.message.find("no overload") != std::string::npos); } try { capy::compile_bearer_unit("function CLI(request : request) {}\n", options); assert(false); } catch (const capy::Error& error) { assert(error.message.find("unknown type 'request'") != std::string::npos); } for (const auto& [source, expected] : { std::pair{"function ambiguous(value : s32, suffix : string = \"x\") {}\nfunction ambiguous(value : s32, enabled : bool = true) {}\nfunction CLI(request : dval) { ambiguous(1) }\n", "ambiguous default overload"}, std::pair{"function sum(left : s32, right : s32 = 1) s32 { -> left + right }\nfunction CLI(request : dval) { var value : function(left : s32, right : s32) s32 = sum; value(1) }\n", "function value argument count"}, std::pair{"host function __bearer_bad(value : s32 = 1)\nfunction CLI(request : dval) {}\n", "host declarations are available only"}, std::pair{"function RENDER(value : s32 = 1) {}\n", "Bearer handlers cannot use default parameters"}, std::pair{"function wrong(value : s32 = 1.5) {}\nfunction CLI(request : dval) {}\n", "default parameter literal must have type s32"}, std::pair{"function wrong(value : s32 = {}) {}\nfunction CLI(request : dval) {}\n", "default parameter literal must have type s32"}, std::pair{"function generic(value : any = 1) value::type { -> value }\nfunction CLI(request : dval) {}\n", "generic functions cannot use default parameters"}, std::pair{"function values(...value : s32 = 1) {}\nfunction CLI(request : dval) {}\n", "variadic parameter cannot have a default value"}, std::pair{"function CLI(request : dval) { var value := function(input : s32 = 1) s32 { -> input } }\n", "anonymous functions cannot use default parameters"}, std::pair{"function CLI(request : dval) { var value : function(input : s32 = 1) s32 = function(input : s32) s32 { -> input } }\n", "function types cannot use default parameters"}, }) { try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error& error) { assert(error.message.find(expected) != std::string::npos); } } const auto tasks = capy::compile_bearer_unit("function TASK:NAME(request : dval) {}\n", options); const auto tasks_repeat = capy::compile_bearer_unit("function TASK:NAME(request : dval) {}\n", options); assert(tasks.wasm == tasks_repeat.wasm && tasks.source_map == tasks_repeat.source_map); const auto task_validation = capy::wasm::validate_bearer_unit(tasks.wasm, {.bearer_abi_version = "11"}); assert(task_validation.valid && task_validation.exports.size() == 1); assert(task_validation.exports[0].name == "__bearer_task_NAME" && task_validation.exports[0].kind == 0); assert(std::none_of(task_validation.imports.begin(), task_validation.imports.end(), [](const auto& imported) { return imported.module == "env" && imported.name == "bearer_handler_input_dval"; })); assert(std::string(tasks.wasm.begin(), tasks.wasm.end()).find("bearer_request") == std::string::npos); // Unit-scope variables are mutable globals initialized by the exported __wasm_call_ctors. const auto unit_variables = capy::compile_bearer_unit( "var count := 0\nvar label : string = \"x\"\nvar names := {}\nvar copy := clone(\"c\")\n" "function bump() s64 { count = count + 1; -> count }\n" "function RENDER() { names.n = bump(); label = label + \"y\"; var f := function() s64 { -> count }; var count := 7; print(string(count), label, string(names.n), string(f()), copy) }\n", options); const auto unit_validation = capy::wasm::validate_bearer_unit(unit_variables.wasm, {.bearer_abi_version = "11"}); assert(unit_validation.valid); assert(std::any_of(unit_validation.exports.begin(), unit_validation.exports.end(), [](const auto& exported) { return exported.name == "__wasm_call_ctors" && exported.kind == 0; })); // An array literal converts into an `as dval` parameter through the dval constructor. const auto array_as_dval = capy::compile_bearer_unit( "function take(v : as dval) dval { -> v }\nfunction RENDER() { print(string(take([\"a\", \"b\"])), string(take(\"s\"))) }\n", options); assert(capy::wasm::validate_bearer_unit(array_as_dval.wasm, {.bearer_abi_version = "11"}).valid); // Service handlers are named props handlers; every name is an export the runtime dispatches by event. const auto services = capy::compile_bearer_unit( "function SERVICE:START(props : dval) { print(string(props.count), string(request.unit)) }\n" "function SERVICE:DATA() { print(string(request.unit)) }\n" "function SERVICE:ASSIGN(props : dval, request : dval) { print(string(props.task)) }\n", options); const auto service_validation = capy::wasm::validate_bearer_unit(services.wasm, {.bearer_abi_version = "11"}); assert(service_validation.valid); for (const std::string name : {"__bearer_service_START", "__bearer_service_DATA", "__bearer_service_ASSIGN"}) assert(std::any_of(service_validation.exports.begin(), service_validation.exports.end(), [&](const auto& exported) { return exported.name == name && exported.kind == 0; })); const auto named_handlers = capy::compile_bearer_unit( "function RENDER:NAME(request : dval) {}\nfunction COMPONENT:NAME(request : dval) {}\nfunction CLI:NAME() {}\nfunction SERVE_HTTP:NAME(request : dval) {}\n", options); const auto named_validation = capy::wasm::validate_bearer_unit(named_handlers.wasm, {.bearer_abi_version = "11"}); assert(named_validation.valid); for (const std::string name : {"__bearer_render_NAME", "__bearer_component_NAME", "__bearer_cli_NAME", "__bearer_serve_http_NAME"}) assert(std::any_of(named_validation.exports.begin(), named_validation.exports.end(), [&](const auto& exported) { return exported.name == name && exported.kind == 0; })); // Handler parameters are optional and fixed by name; `request` is always bound. const auto implicit_handlers = capy::compile_bearer_unit( "function COMPONENT:CARD(props : dval) { print(string(props.title), string(request.method)) }\n" "function COMPONENT:EMPTY() { print(string(request.method)) }\n" "function COMPONENT:FULL(props : dval, request : dval) { print(string(props.a), string(request.method)) }\n" "function TASK:X(props : dval) { print(string(props.x)) }\n" "function greet(name : string, request : capture dval) { print(name, string(request.query.x)) }\n" "function convert_me(v : capture as dval) { print(string(v)) }\n" "#exports echo\n" "function echo(input : dval) dval { -> {t: string(request.user), i: input.n} }\n" "function RENDER() {\n" " component_render(\":CARD\", title: \"News\")\n" " component_render(\":CARD\", {title: \"A\"}, request)\n" " var html := component(\":EMPTY\")\n" " greet(\"a\")\n" " var v := \"s\"\n" " convert_me()\n" " request.user = \"Ada\"\n" " var r := echo({n: 1})\n" " var f := function(x : s32) { redirect(\"/x\" + string(x)) }\n" " f(1)\n" " var capture := {title: \"T\"}\n" " component_render(\":CARD\", title: capture)\n" " redirect(\"/done\", 301)\n" "}\n", options); const auto implicit_validation = capy::wasm::validate_bearer_unit(implicit_handlers.wasm, {.bearer_abi_version = "11"}); assert(implicit_validation.valid); for (const std::string name : {"__bearer_render", "__bearer_component_CARD", "__bearer_component_EMPTY", "__bearer_component_FULL", "__bearer_task_X", "echo"}) assert(std::any_of(implicit_validation.exports.begin(), implicit_validation.exports.end(), [&](const auto& exported) { return exported.name == name && exported.kind == 0; })); // The implicit request is typed in the capability scan, so a local derived // from it still registers the imports its uses need. const auto implicit_scan = capy::compile_bearer_unit("function RENDER() {\n\tvar files := request.files\n\tprint(\"files=\", length(files), \"\\n\")\n\tfor file := files { print(string(file.name)) }\n}\n", options); const std::string implicit_scan_bytes(implicit_scan.wasm.begin(), implicit_scan.wasm.end()); assert(implicit_scan_bytes.find("bearer_dv_count") != std::string::npos && implicit_scan_bytes.find("bearer_dv_entry_value") != std::string::npos); // A u64 expression with a literal operand boxes as u64, and a lambda that calls // an import alias captures the request the alias call needs. const auto boxed_arithmetic = capy::compile_bearer_unit("function COMPONENT(props : dval) {\n\tvar level := u64(props.level)\n\tvar m := {level: level + 1}\n\tprint(string(m.level))\n}\n", options); assert(std::string(boxed_arithmetic.wasm.begin(), boxed_arithmetic.wasm.end()).find("bearer_dv_u64") != std::string::npos); const auto lambda_alias = capy::compile_bearer_unit("function RENDER() {\n\tvar f := function() dval { return unit_call(\"/x.capy:echo\", a: 1) }\n\tprint(string(f().a))\n}\n", options); assert(capy::wasm::validate_bearer_unit(lambda_alias.wasm, {.bearer_abi_version = "11"}).valid); const auto empty_handler = capy::compile_bearer_unit("function RENDER(request : dval) {}\n", options); const auto empty_validation = capy::wasm::validate_bearer_unit(empty_handler.wasm, {.bearer_abi_version = "11"}); assert(empty_validation.valid); assert(std::none_of(empty_validation.imports.begin(), empty_validation.imports.end(), [](const auto& imported) { return imported.module == "env" && (imported.name == "bearer_format_s64" || imported.name == "bearer_format_u64"); })); for (const auto& [source, expected] : { std::pair{"function TASK:NAME(request : dval, extra : dval) {}\n", "takes (props : dval, request : dval)"}, std::pair{"function TASK:NAME(value : s32) {}\n", "takes (props : dval, request : dval)"}, std::pair{"function TASK(props : dval) {}\n", "TASK handlers are named"}, std::pair{"function TASK:NAME(context : dval) {}\n", "takes (props : dval, request : dval)"}, std::pair{"function TASK:NAME(request : dval, props : dval) {}\n", "takes (props : dval, request : dval)"}, std::pair{"function RENDER(props : dval) {}\n", "takes (request : dval)"}, std::pair{"function RENDER(context : dval) {}\n", "takes (request : dval)"}, std::pair{"function RENDER(request : capture dval) {}\n", "cannot declare capture parameters"}, std::pair{"function helper(name : string) { redirect(\"/x\") }\nfunction RENDER() { helper(\"a\") }\n", "'redirect' captures 'request', but no variable named 'request' is in scope"}, std::pair{"function f(v : capture dval) {}\nfunction RENDER() { var v := \"s\"; f() }\n", "captures 'v' as dval, but the variable in scope is string"}, std::pair{"function f(request : capture dval, name : string) {}\nfunction RENDER() { f(\"a\") }\n", "default and capture parameters must be trailing"}, std::pair{"function f(v : capture dval = {}) {}\nfunction RENDER() {}\n", "cannot be variadic or have a default"}, std::pair{"function RENDER() { var f := function(v : capture dval) {} }\n", "anonymous functions cannot use capture parameters"}, std::pair{"#exports echo\nfunction echo(input : dval) dval { -> input }\nfunction helper() dval { -> echo({}) }\nfunction RENDER() { print(string(helper())) }\n", "'echo' captures 'request'"}, std::pair{"function TASK:NAME(request : dval) s32 { -> 1 }\n", "must return void"}, std::pair{"function TASK:NAME(request : dval) {}\nfunction TASK:NAME(request : dval) {}\n", "duplicate"}, std::pair{"function TASK_NAME(request : dval) {}\n", "use ':'"}, std::pair{"function SERVICE(props : dval) {}\n", "SERVICE handlers are named"}, std::pair{"var a := 1\nvar a := 2\nfunction RENDER() {}\n", "unit variable 'a' conflicts"}, std::pair{"var RENDER := 1\nfunction RENDER() {}\n", "unit variable 'RENDER' conflicts"}, std::pair{"var x := request.unit\nfunction RENDER() {}\n", "unknown local 'request'"}, std::pair{"var y := z\nvar z := 1\nfunction RENDER() {}\n", "unknown local 'z'"}, std::pair{"var v := print(\"a\")\nfunction RENDER() {}\n", "needs a value, found void"}, std::pair{"var n := 1\nfunction RENDER() { n = \"s\" }\n", "expected s64, found string"}, std::pair{"var request := 1\nfunction RENDER() {}\n", "cannot be a unit variable name"}, std::pair{"function SERVICE_START(props : dval) {}\n", "use ':'"}, std::pair{"function SERVICE:START(context : dval) {}\n", "a SERVICE handler takes (props : dval, request : dval)"}, std::pair{"function WS:NAME {}\n", "named handler syntax applies only"}, std::pair{"function CLI_NAME() {}\n", "use ':'"}, std::pair{"function EXPORT___bearer_task_NAME(value : dval) dval { -> value }\nfunction TASK:NAME(request : dval) {}\n", "collides with custom DValue export"}, }) { try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error& error) { assert(error.message.find(expected) != std::string::npos); } } std::ofstream output("/tmp/capy-native.wasm", std::ios::binary); output.write(reinterpret_cast(result.wasm.data()), result.wasm.size()); constexpr std::string_view arc_cases[] = { "function CLI(request : dval) { var value := clone(\"x\") }\n", "function CLI(request : dval) { var value := clone(\"x\"); value = clone(\"y\") }\n", "function borrow(x : string) string { return x }\nfunction CLI(request : dval) { var value := borrow(\"x\") }\n", "function make() string { return clone(\"x\") }\nfunction CLI(request : dval) { var value := make() }\n", "function early(x : string) string { var discarded := clone(\"x\"); if true { return x }; return discarded }\nfunction CLI(request : dval) { var value := early(\"x\") " "}\n", "function CLI(request : dval) { var values := [clone(\"x\"), clone(\"y\")]; var first : string = values[0] }\n", "function CLI(request : dval) { var value := clone(\"x\"); print(arc_live()) }\n", }; for (const auto source : arc_cases) { const auto compiled = capy::compile_bearer_unit(source, options); assert(capy::wasm::validate_bearer_unit(compiled.wasm, {.bearer_abi_version = "11"}).valid); } const auto scalar_parameter_reassignment = capy::compile_bearer_unit( "function change(value : s32) s32 { value = 2; -> value }\nfunction CLI(request : dval) { print(change(1)) }\n", options); assert(capy::wasm::validate_bearer_unit(scalar_parameter_reassignment.wasm, {.bearer_abi_version = "11"}).valid); const auto managed_parameter_reassignment = capy::compile_bearer_unit( "function change_text(value : string) string { value = value + \"!\"; return value }\n" "function change_array(values : [s32]) [s32] { values = [2]; return values }\n" "function CLI(request : dval) { var text := \"x\"; var values : [s32] = [1]; print(change_text(text), text, change_array(values)[0], values[0]) }\n", options); assert(capy::wasm::validate_bearer_unit(managed_parameter_reassignment.wasm, {.bearer_abi_version = "11"}).valid); for (const auto& [source, expected] : { std::pair{"function CLI(request : dval) { var value := 1; var closure := function() void { value = 2 } }\n", "cannot assign to captured binding 'value'. Captures are immutable"}, std::pair{"function CLI(request : dval) { var value := clone(\"x\"); var closure := function() void { value = clone(\"y\") } }\n", "cannot assign to captured binding 'value'. Captures are immutable"}, }) { try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error& error) { assert(error.message.find(expected) != std::string::npos); } } const std::string bare_dval_source = "function CLI(request : dval) {\n" "var value := {name: clone(\"Ada\"), nested: {answer: 42}, values: dval([3])}\n" "print(value.name, s32(value.nested.answer), s32(value.values[0]), arc_live())\n" "}\n"; const auto bare_dval = capy::compile_bearer_unit(bare_dval_source, options); const auto bare_dval_repeat = capy::compile_bearer_unit(bare_dval_source, options); assert(capy::wasm::validate_bearer_unit(bare_dval.wasm, {.bearer_abi_version = "11"}).valid); assert(bare_dval.wasm == bare_dval_repeat.wasm && bare_dval.source_map == bare_dval_repeat.source_map); const std::string bare_dval_bytes(bare_dval.wasm.begin(), bare_dval.wasm.end()); assert(bare_dval_bytes.find("bearer_dv_build") != std::string::npos); assert(bare_dval_bytes.find("bearer_dv_read") != std::string::npos); assert(bare_dval_bytes.find("bearer_dv_has") != std::string::npos); assert(bare_dval.source_map.find("\t3\t19\n") != std::string::npos); const auto dval_has_only = capy::compile_bearer_unit("function CLI(request : dval) { var value := {}; print(has(value, \"key\")) }\n", options); const std::string dval_has_only_bytes(dval_has_only.wasm.begin(), dval_has_only.wasm.end()); assert(dval_has_only_bytes.find("bearer_dv_has") != std::string::npos); assert(dval_has_only_bytes.find("bearer_dv_read") == std::string::npos); const auto dval_none_and_paths = capy::compile_bearer_unit( "function selector() string { -> \"key\" }\nfunction replacement() string { -> \"value\" }\n" "function CLI(request : dval) { var value := {items: [{}]}; var missing := value.missing; var absent := missing?; value.items[0][selector()].name = replacement(); print(value.items[0][\"key\"].name, absent, arc_live()) }\n", options); const auto dval_none_and_paths_repeat = capy::compile_bearer_unit( "function selector() string { -> \"key\" }\nfunction replacement() string { -> \"value\" }\n" "function CLI(request : dval) { var value := {items: [{}]}; var missing := value.missing; var absent := missing?; value.items[0][selector()].name = replacement(); print(value.items[0][\"key\"].name, absent, arc_live()) }\n", options); const auto dval_none_and_paths_validation = capy::wasm::validate_bearer_unit(dval_none_and_paths.wasm, {.bearer_abi_version = "11"}); assert(dval_none_and_paths_validation.valid && dval_none_and_paths.wasm == dval_none_and_paths_repeat.wasm && dval_none_and_paths.source_map == dval_none_and_paths_repeat.source_map); std::set dval_path_imports; for (const auto& imported : dval_none_and_paths_validation.imports) dval_path_imports.insert(imported.name); for (const char* name : {"bearer_dv_read", "bearer_dv_is_none", "bearer_dv_set_path", "bearer_dv_string", "bearer_dv_s32"}) assert(dval_path_imports.contains(name)); assert(!dval_path_imports.contains("bearer_dv_s64")); const auto none_literal = capy::compile_bearer_unit("function CLI(request : dval) { var value := none; print(value?) }\n", options); assert(capy::wasm::validate_bearer_unit(none_literal.wasm, {.bearer_abi_version = "11"}).valid); const std::string none_literal_bytes(none_literal.wasm.begin(), none_literal.wasm.end()); assert(none_literal_bytes.find("bearer_dv_none") != std::string::npos); for (const char* unrelated : {"bearer_dv_has", "bearer_dv_read", "bearer_dv_string", "bearer_dv_s32"}) assert(none_literal_bytes.find(unrelated) == std::string::npos); const auto mutable_dval_roots = capy::compile_bearer_unit( "function write(value : dval) { value.key = 1; var closure := function() void { value.captured = 2 }; closure() }\n" "function CLI(request : dval) { var value := {}; write(value); set(value, \"mutated\", true) }\n", options); assert(capy::wasm::validate_bearer_unit(mutable_dval_roots.wasm, {.bearer_abi_version = "11"}).valid); const std::string mutable_dval_root_bytes(mutable_dval_roots.wasm.begin(), mutable_dval_roots.wasm.end()); assert(mutable_dval_root_bytes.find("bearer_dval_replace") == std::string::npos); // Any dval expression can be written through: the object is shared, so a // write into a temporary, a struct field, an array item, or a loop item // reaches the object those names refer to. for (const char* source : { "function CLI(request : dval) { dval({}).key = 1 }\n", "function make() dval { -> {} }\nfunction CLI(request : dval) { make().key = 1 }\n", "struct Holder { value : dval }\nfunction CLI(request : dval) { var holder := Holder({}); holder.value.key = 1 }\n", "function CLI(request : dval) { var values : [dval] = [dval({})]; values[0].key = 1 }\n", "function CLI(request : dval) { var values := dval([{}]); for item := values { item.key = 1 } }\n", }) assert(capy::wasm::validate_bearer_unit(capy::compile_bearer_unit(source, options).wasm, {.bearer_abi_version = "11"}).valid); for (const auto& [source, expected] : { std::pair{"function CLI(request : dval) { var value := {}; value.key = 1; value[true] = 2 }\n", "dval index must be string or s64"}, }) try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error& error) { assert(error.message.find(expected) != std::string::npos); } const auto empty_bare_dval = capy::compile_bearer_unit("function CLI(request : dval) { var value := {}; dval({}); {} }\n", options); assert(capy::wasm::validate_bearer_unit(empty_bare_dval.wasm, {.bearer_abi_version = "11"}).valid); const std::string empty_bare_dval_bytes(empty_bare_dval.wasm.begin(), empty_bare_dval.wasm.end()); assert(empty_bare_dval_bytes.find("bearer_dv_build") != std::string::npos); const auto dval_member_method = capy::compile_bearer_unit( "function take(receiver : dval) dval { -> receiver }\nfunction CLI(request : dval) { var value := {}; value.take() }\n", options); assert(capy::wasm::validate_bearer_unit(dval_member_method.wasm, {.bearer_abi_version = "11"}).valid); const auto missing_member = capy::compile_bearer_unit("function CLI(request : dval) {\nvar value := {}\nstring(value.missing)\n}\n", options); assert(missing_member.source_map.find("\t3\t13\n") != std::string::npos); try { capy::compile_bearer_unit("function CLI(request : dval) { var value := (1).missing }\n", options); assert(false); } catch (const capy::Error& error) { assert(error.message.find("member access requires a struct") != std::string::npos); } const auto converted_dvals = capy::compile_bearer_unit( "function accept(value : as dval) dval { -> value }\n" "function CLI(request : dval) { var a := accept(\"x\"); var b := accept(2); var c := accept(s64(-3)); var d := accept(u64(4)); var e := accept(5.5); var f := accept(true) }\n", options); const std::string converted_dval_bytes(converted_dvals.wasm.begin(), converted_dvals.wasm.end()); for (const std::string& import : {"bearer_dv_string", "bearer_dv_s64", "bearer_dv_u64", "bearer_dv_f64", "bearer_dv_bool"}) assert(converted_dval_bytes.find(import) != std::string::npos); const auto stdlib_converted_dvals = capy::compile_bearer_unit( "function CLI(request : dval) { var a := dval(\"x\"); var b := dval(1); var c := push(dval([]), u64(2)); var d := set({}, \"ok\", true); " "var task_id := task_execute(\"/task\", 3.5); var output := component(\"/component\", false); component_render(\"/component\", \"props\"); " "var loaded := unit_load(\"/unit\"); var called := loaded.call(\"echo\", 4); var direct := unit_call(\"/unit:echo\", \"input\") }\n", options); assert(capy::wasm::validate_bearer_unit(stdlib_converted_dvals.wasm, {.bearer_abi_version = "11"}).valid); const auto component_render_result = capy::compile_bearer_unit( "function CLI(request : dval) { var plain := component_render(\"/component\"); var props := component_render(\"/component\", \"props\"); if plain && props {} component_render(\"/component\"); component_render(\"/component\", \"props\") }\n", options); assert(capy::wasm::validate_bearer_unit(component_render_result.wasm, {.bearer_abi_version = "11"}).valid); const std::string component_render_result_bytes(component_render_result.wasm.begin(), component_render_result.wasm.end()); assert(component_render_result_bytes.find("bearer_dv_component_render") != std::string::npos); const auto generic_before_dval = capy::compile_bearer_unit( "function choose(value : any) string { -> \"generic\" }\nfunction choose(value : as dval) string { -> \"dval\" }\nfunction CLI(request : dval) { print(choose(1)) }\n", options); const std::string generic_before_dval_bytes(generic_before_dval.wasm.begin(), generic_before_dval.wasm.end()); assert(generic_before_dval_bytes.find("bearer_dv_s32") == std::string::npos); for (const char* source : { "struct Value { item : s32 }\nfunction accept(value : as dval) dval { -> value }\nfunction CLI(request : dval) { accept(Value(1)) }\n", }) { try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error& error) { assert(error.message.find("no overload accept") != std::string::npos); } } try { capy::compile_bearer_unit("function CLI(request : dval) { if var wrong : bool = 1 {} }\n", options); assert(false); } catch (const capy::Error& error) { assert(error.message.find("expected bool, found s64") != std::string::npos); } constexpr std::string_view generic_source = "function identity(value : any) value::type { -> value }\n" "function choose(value : any) value::type { -> value }\n" "function choose(value : s32) value::type { -> value + 1 }\n" "function countdown(value : any) value::type { if value == 0 { return value }; return countdown(value - 1) }\n" "function CLI(request : dval) { print(identity(7), choose(4), countdown(3), identity([clone(\"first\"), clone(\"x\")])[1], bool(2)) }\n"; const auto generic = capy::compile_bearer_unit(generic_source, options); assert(capy::wasm::validate_bearer_unit(generic.wasm, {.bearer_abi_version = "11"}).valid); const auto generic_method = capy::compile_bearer_unit( "function same(value : any) value::type { -> value }\nfunction CLI(request : dval) { print((7).same()) }\n", options); assert(capy::wasm::validate_bearer_unit(generic_method.wasm, {.bearer_abi_version = "11"}).valid); assert(generic_method.source_map.find("F\t1\tnative-test.capy\n") != std::string::npos); const auto converted_parameters = capy::compile_bearer_unit( "function text(value : as string) string { -> value }\nfunction wide(value : as s64) s64 { -> value }\n" "function CLI(request : dval) { print(text(42), text(false), text([1][0]), wide(u64(7)), wide(1e300), string(8)) }\n", options); const auto converted_parameters_validation = capy::wasm::validate_bearer_unit(converted_parameters.wasm, {.bearer_abi_version = "11"}); assert(converted_parameters_validation.valid); assert(std::none_of(converted_parameters_validation.imports.begin(), converted_parameters_validation.imports.end(), [](const auto& imported) { return imported.name == "bearer_format_f64"; })); const auto converted_dval_parameters = capy::compile_bearer_unit( "type Text = string\nfunction text(value : as Text) string { -> value }\nfunction truth(value : as bool) bool { -> value }\n" "function narrow(value : as s32) s32 { -> value }\nfunction signed(value : as s64) s64 { -> value }\n" "function unsigned(value : as u64) u64 { -> value }\nfunction decimal(value : as f64) f64 { -> value }\n" "function CLI(request : dval) { var value := dval(\"1\"); print(text(value), truth(value), narrow(value), signed(value), unsigned(value), decimal(value)) }\n", options); const auto converted_dval_validation = capy::wasm::validate_bearer_unit(converted_dval_parameters.wasm, {.bearer_abi_version = "11"}); assert(converted_dval_validation.valid); std::set converted_dval_imports; for (const auto& imported : converted_dval_validation.imports) converted_dval_imports.insert(imported.name); for (const std::string& target : {"string", "bool", "s32", "s64", "u64", "f64"}) assert(converted_dval_imports.contains("bearer_dv_extract_" + target)); const auto direct_dval_print = capy::compile_bearer_unit( "function CLI(request : dval) { var profile := {nickname: clone(\"Capy\")}; print(profile[\"nickname\"]); " "var printer : function(...values : as string) void = print; printer(dval(clone(\"again\"))) }\n", options); const auto direct_dval_print_validation = capy::wasm::validate_bearer_unit(direct_dval_print.wasm, {.bearer_abi_version = "11"}); assert(direct_dval_print_validation.valid); assert(std::any_of(direct_dval_print_validation.imports.begin(), direct_dval_print_validation.imports.end(), [](const auto& imported) { return imported.name == "bearer_dv_extract_string"; })); const auto mixed_dval_print = capy::compile_bearer_unit( "function CLI(request : dval) { var profile := {name: \"Ada\"}; print(\"x\", profile.name) }\n", options); assert(capy::wasm::validate_bearer_unit(mixed_dval_print.wasm, {.bearer_abi_version = "11"}).valid); const auto scalar_print = capy::compile_bearer_unit("function CLI(request : dval) { print(\"ok\", 1, true) }\n", options); const auto scalar_print_validation = capy::wasm::validate_bearer_unit(scalar_print.wasm, {.bearer_abi_version = "11"}); assert(scalar_print_validation.valid); for (const auto& imported : scalar_print_validation.imports) assert(imported.name.rfind("bearer_dv_extract_", 0) != 0); const auto constructors_and_variadics = capy::compile_bearer_unit( "struct Token { value : string }\n" "struct Pair { left : s32; right : string }\n" "function Token(value : s32) Token { -> Token(string(value)) }\n" "function token_text(value : Token) string { -> value.value }\n" "function collect(...values : as string) [string] { -> values }\n" "function CLI(request : dval) { var values : [string] = []; values.push(token_text(Token(7))); var copy := values; values[0] = \"x\"; print(...copy, ...collect(1, 2), Pair(s32(1), \"p\").right) }\n", options); assert(capy::wasm::validate_bearer_unit(constructors_and_variadics.wasm, {.bearer_abi_version = "11"}).valid); const auto variadic_function_field = capy::compile_bearer_unit( "function compose(...values : as string) string { -> values[0] }\n" "struct Collector { invoke : function(...values : as string) string }\n" "function CLI(request : dval) { var collector := Collector(compose); print(collector.invoke(...[1, 2])) }\n", options); const auto variadic_field_validation = capy::wasm::validate_bearer_unit(variadic_function_field.wasm, {.bearer_abi_version = "11"}); assert(variadic_field_validation.valid); for (const auto& imported : variadic_field_validation.imports) assert(imported.name != "bearer_format_s64" && imported.name != "bearer_format_u64"); const auto inferred_managed_local = capy::compile_bearer_unit( "function CLI(request : dval) { var greeting := \"Hello\" }\n", options); assert(capy::wasm::validate_bearer_unit(inferred_managed_local.wasm, {.bearer_abi_version = "11"}).valid); const auto standalone_struct = capy::compile_bearer_unit( "struct Point { x : s32; y : s32 }\nfunction CLI(request : dval) { var point := Point(3, 4); print(point.x) }\n", options); const auto standalone_struct_validation = capy::wasm::validate_bearer_unit(standalone_struct.wasm, {.bearer_abi_version = "11"}); assert(standalone_struct_validation.valid); assert(std::any_of(standalone_struct_validation.imports.begin(), standalone_struct_validation.imports.end(), [](const auto& imported) { return imported.name == "bearer_alloc"; })); const auto unused_conversion = capy::compile_bearer_unit( "function text(value : as string) string { -> value }\nfunction CLI(request : dval) {}\n", options); const auto unused_conversion_validation = capy::wasm::validate_bearer_unit(unused_conversion.wasm, {.bearer_abi_version = "11"}); assert(unused_conversion_validation.valid); for (const auto& imported : unused_conversion_validation.imports) assert(imported.name.rfind("bearer_format_", 0) != 0); const auto module_exports = capy::compile_bearer_unit( "#exports invoke\n#exports other\nfunction invoke(value : dval) dval { -> value }\nfunction other(value : dval) dval { -> value }\n", options); assert(module_exports.custom_exports == std::vector({"invoke", "other"})); assert(module_exports.function_exports == std::vector({"capy function invoke(dval):dval", "capy function other(dval):dval"})); assert(module_exports.source_map.find("F\t1\tnative-test.capy\n") != std::string::npos); assert(capy::wasm::validate_bearer_unit(module_exports.wasm, {.bearer_abi_version = "11"}).valid); const auto generic_exports = capy::compile_bearer_unit( "#exports same\nfunction same(first : any, second : first::type) first::type { -> second }\n", options); assert(generic_exports.function_exports == std::vector({"capy function same(any,$0::type):$0::type"})); const auto type_exports = capy::compile_bearer_unit( "#exports echo, MyType, Alias\nstruct MyType { value : s32 }\ntype Alias = MyType\nfunction echo(value : dval) dval { -> value }\nfunction MyType(value : dval) MyType { -> MyType(1) }\n", options); assert(type_exports.custom_exports == std::vector({"echo"})); assert(type_exports.function_exports == std::vector({"capy function echo(dval):dval", "capy function MyType(dval):struct:MyType"})); assert(type_exports.type_exports == std::vector({"capy type struct MyType{value:s32}", "capy type alias Alias=struct:MyType"})); const auto module_call = capy::compile_bearer_unit( "function CLI(request : dval) { var loaded : module = unit_load(\"child.capy\"); loaded.call(\"invoke\") }\n", options); const std::string module_call_bytes(module_call.wasm.begin(), module_call.wasm.end()); assert(module_call_bytes.find("bearer_unit_load") != std::string::npos && module_call_bytes.find("bearer_dv_module_call") != std::string::npos); assert(capy::wasm::validate_bearer_unit(module_call.wasm, {.bearer_abi_version = "11"}).valid); const auto module_member_call = capy::compile_bearer_unit( "function CLI(request : dval) { var loaded : module = unit_load(\"child.capy\"); loaded.invoke(); loaded.invoke({value: 1}) }\n", options); const std::string module_member_call_bytes(module_member_call.wasm.begin(), module_member_call.wasm.end()); assert(module_member_call_bytes.find("bearer_unit_load") != std::string::npos && module_member_call_bytes.find("bearer_dv_module_call") != std::string::npos); assert(capy::wasm::validate_bearer_unit(module_member_call.wasm, {.bearer_abi_version = "11"}).valid); for (const std::string invocation : {"print(loaded.default_input())", "var version := loaded.version(); print(version)", "print(loaded.echo(7))", "print(loaded.echo(\"Ada\"))", "print(unit_load(\"child.capy\").default_input())"}) { const auto default_module_input = capy::compile_bearer_unit( "function CLI(request : dval) { var loaded := unit_load(\"child.capy\"); " + invocation + " }\n", options); const std::string bytes(default_module_input.wasm.begin(), default_module_input.wasm.end()); if (invocation.find("echo") == std::string::npos) assert(bytes.find("bearer_dv_build") != std::string::npos); assert(bytes.find("bearer_dv_module_call") != std::string::npos); assert(capy::wasm::validate_bearer_unit(default_module_input.wasm, {.bearer_abi_version = "11"}).valid); } assert(module_call.source_map.find("F\t2\tcapy://stdlib.capy\n") != std::string::npos); const auto module_passthrough = capy::compile_bearer_unit( "function pass(value : module) module { -> value }\n" "function identity(value : any) value::type { -> value }\n" "function CLI(request : dval) { var loaded : module = unit_load(\"child.capy\"); var returned : module = pass(loaded); identity(returned).call(\"invoke\") }\n", options); assert(capy::wasm::validate_bearer_unit(module_passthrough.wasm, {.bearer_abi_version = "11"}).valid); capy::CompileOptions import_options = options; import_options.import_type_metadata = [](const std::string& path) { assert(path == "child.capy"); return std::vector({"capy type struct Shape{value:s32}", "capy type alias Other=struct:Shape"}); }; const auto imported_types = capy::compile_bearer_unit( "#import \"child.capy\" as child\ntype LocalShape = child.Shape\ntype LocalOther = child.Other\nfunction CLI(request : dval) {}\n", import_options); assert(capy::wasm::validate_bearer_unit(imported_types.wasm, {.bearer_abi_version = "11"}).valid); const std::string imported_type_bytes(imported_types.wasm.begin(), imported_types.wasm.end()); assert(imported_type_bytes.find("Shape") != std::string::npos && imported_type_bytes.find("value") != std::string::npos); assert(imported_type_bytes.find("bearer_capy_reflect_struct") == std::string::npos); for (const auto& [source, expected] : { std::pair{"#exports missing\nfunction CLI(request : dval) {}\n", "unknown local function or type 'missing'"}, std::pair{"#exports invoke\nfunction EXPORT_invoke(value : dval) dval { -> value }\nfunction CLI(request : dval) {}\n", "already declared"}, std::pair{"function CLI(request : dval) { var loaded : module = unit_load(\"x\"); print(loaded + loaded) }\n", "unsupported operator + for module"}, std::pair{"function CLI(request : dval) { var loaded : module = unit_load(\"x\"); if loaded {} }\n", "module is opaque and cannot be used as a condition"}, std::pair{"function CLI(request : dval) { var loaded : module = unit_load(\"x\"); s32(loaded) }\n", "no overload s32(module)"}, std::pair{"function CLI(request : dval) { var loaded : module = unit_load(\"x\"); loaded.echo({a: 1}, {b: 2}) }\n", "dynamic module member call accepts at most one dval input"}, std::pair{"function CLI(request : dval) { var loaded : module = unit_load(\"x\"); var name := loaded.echo }\n", "module member access must be called"}, std::pair{"function bad(value : as [s32]) {}\n", "requires a concrete named type constructor"}, std::pair{"function CLI(request : dval) { var callback : function(value : as s64) s64 = function(value : s64) s64 { -> value } }\n", "only variadic function-type parameters may request conversion"}, std::pair{"function CLI(request : dval) { var value := 1 as s64 }\n", "call the target type constructor instead"}, std::pair{"struct Point { x : s32 }\nfunction string(value : any) string { -> value + \"!\" }\nfunction show(value : as string) {}\nfunction CLI(request : dval) { show(Point(1)) }\n", "string operators require string operands"}, std::pair{"function s32(value : string) string { -> value }\nfunction CLI(request : dval) {}\n", "constructor 's32' must return s32"}, std::pair{"struct Point { x : s32 }\nfunction Point(x : s32) Point { -> Point(x) }\nfunction CLI(request : dval) {}\n", "duplicates the generated Point field constructor"}, std::pair{"function CLI(request : dval) { var loaded : module = unit_load(\"x\"); var values := [loaded] }\n", "module is opaque and cannot be stored in array layouts"}, std::pair{"struct Box { handle : module }\nfunction CLI(request : dval) {}\n", "module is opaque and cannot be stored in struct layouts"}, std::pair{"function CLI(request : dval) { var loaded : module = unit_load(\"x\"); var f := function() module { -> loaded } }\n", "module is opaque and cannot be captured by a closure"}, }) { try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error& error) { assert(error.message.find(expected) != std::string::npos); } } const auto returning = capy::compile_bearer_unit( "function choose(value : bool) s32 { if value { return 1 } else { return 2 } }\nfunction CLI(request : dval) { print(choose(true)) }\n", options); assert(capy::wasm::validate_bearer_unit(returning.wasm, {.bearer_abi_version = "11"}).valid); const std::string aliases_source = "type Count = s64\ntype Counts = [Count]\ntype Pair = CountText\ntype Printer = function(...values : as string) void\n" "type User = Person\ntype Json = dval\nstruct CountText { count : Count; text : string }\nstruct Person { value : s32 }\n" "function take(value : Count) Count { -> value }\nfunction CLI(request : dval) { var values : Counts = [Count(4)]; var pair : Pair = Pair(values[0], \"x\"); " "var user : User = User(7); var json : Json = Json({value: user.value}); var output : Printer = print; " "output(take(pair.count), pair.text, s32(json[\"value\"])) }\n"; const auto aliases = capy::compile_bearer_unit(aliases_source, options); const auto aliases_repeat = capy::compile_bearer_unit(aliases_source, options); assert(capy::wasm::validate_bearer_unit(aliases.wasm, {.bearer_abi_version = "11"}).valid); assert(aliases.wasm == aliases_repeat.wasm && aliases.source_map == aliases_repeat.source_map); for (const auto& [source, expected] : { std::pair{"const x : s32 = 1\nfunction CLI(request : dval) {}\n", "const declarations were removed"}, std::pair{"function CLI(request : dval) { var none := dval(\"x\") }\n", "'none' is a reserved literal"}, std::pair{"type A = B\ntype B = A\nfunction CLI(request : dval) {}\n", "cyclic type alias"}, std::pair{"type MissingAlias = Missing\nfunction CLI(request : dval) {}\n", "unknown type 'Missing'"}, std::pair{"type s32 = u64\nfunction CLI(request : dval) {}\n", "is reserved"}, std::pair{"type Thing = s32\nstruct Thing { value : s32 }\nfunction CLI(request : dval) {}\n", "conflicts with type alias"}, std::pair{"type Id = s32\nfunction use(value : Id) {}\nfunction use(value : s32) {}\nfunction CLI(request : dval) {}\n", "return type does not distinguish overloads"}, }) { try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error& error) { assert(error.message.find(expected) != std::string::npos); } } const auto boundary = capy::compile_bearer_unit("function CLI(request : dval) { print(-2147483648, 2147483647) }\n", options); assert(capy::wasm::validate_bearer_unit(boundary.wasm, {.bearer_abi_version = "11"}).valid); const auto ordinary_sqlite = capy::compile_bearer_unit("function CLI(request : dval) { var db := sqlite_connect(\":memory:\"); print(sqlite_info(db, \"error\")); sqlite_disconnect(db) }\n", options); assert(capy::wasm::validate_bearer_unit(ordinary_sqlite.wasm, {.bearer_abi_version = "11"}).valid); const std::string sqlite_bytes(ordinary_sqlite.wasm.begin(), ordinary_sqlite.wasm.end()); assert(sqlite_bytes.find("bearer_sqlite_connect") != std::string::npos); assert(sqlite_bytes.find("bearer_sqlite_error") != std::string::npos); assert(sqlite_bytes.find("bearer_hard_error") != std::string::npos); assert(sqlite_bytes.find("bearer_capy_backtrace") == std::string::npos); assert(ordinary_sqlite.source_map.find("F\t2\tcapy://stdlib.capy\n") != std::string::npos); bool sqlite_stdlib_marker = false; std::istringstream sqlite_map(ordinary_sqlite.source_map); for (std::string row; std::getline(sqlite_map, row);) { std::istringstream fields(row); std::string kind, address; unsigned source_id = 0; if (std::getline(fields, kind, '\t') && kind == "L" && std::getline(fields, address, '\t') && (fields >> source_id) && source_id == 2) sqlite_stdlib_marker = true; } assert(sqlite_stdlib_marker); const auto flush_output = capy::compile_bearer_unit("function RENDER(request : dval) { print(\"first\"); flush_output(); print(\"second\") }\n", options); assert(capy::wasm::validate_bearer_unit(flush_output.wasm, {.bearer_abi_version = "11"}).valid); const std::string flush_output_bytes(flush_output.wasm.begin(), flush_output.wasm.end()); assert(flush_output_bytes.find("bearer_flush_output") != std::string::npos); const auto no_stdlib_demand = capy::compile_bearer_unit("function CLI(request : dval) { print(1) }\n", options); const std::string no_stdlib_bytes(no_stdlib_demand.wasm.begin(), no_stdlib_demand.wasm.end()); assert(no_stdlib_bytes.find("bearer_sqlite_") == std::string::npos && no_stdlib_bytes.find("bearer_mysql_") == std::string::npos && no_stdlib_bytes.find("bearer_capy_backtrace") == std::string::npos); assert(no_stdlib_bytes.find("bearer_format_s64") == std::string::npos && no_stdlib_bytes.find("bearer_format_u64") == std::string::npos && no_stdlib_bytes.find("bearer_print_bytes") != std::string::npos); assert(no_stdlib_bytes.find("bearer_alloc") != std::string::npos && no_stdlib_bytes.find("bearer_free") != std::string::npos && no_stdlib_bytes.find("bearer_handler_input_dval") == std::string::npos); assert(no_stdlib_demand.source_map.find("\t1\t1\t32\n") != std::string::npos); std::string dense_dvals = "function CLI(request : dval) {\n"; for (unsigned i = 0; i != 100; ++i) dense_dvals += " var value_" + std::to_string(i) + " := dval(" + std::to_string(i) + ")\n"; dense_dvals += "}\n"; const auto dense_dval_result = capy::compile_bearer_unit(dense_dvals, options); assert(capy::wasm::validate_bearer_unit(dense_dval_result.wasm, {.bearer_abi_version = "11"}).valid); assert(dense_dval_result.wasm.size() < 16000); const auto literal_print = capy::compile_bearer_unit("function CLI(request : dval) { print(\"ok\") }\n", options); assert(literal_print.wasm.size() < empty_handler.wasm.size() + 600); const auto f64_print = capy::compile_bearer_unit("function emit(value : f64) { print(value) }\nfunction CLI(request : dval) { emit(1.5) }\n", options); const std::string f64_print_bytes(f64_print.wasm.begin(), f64_print.wasm.end()); assert(f64_print_bytes.find("bearer_format_f64") == std::string::npos && f64_print_bytes.find("bearer_print_bytes") != std::string::npos); assert(f64_print_bytes.find("bearer_print_f64") == std::string::npos && f64_print_bytes.find("bearer_print_s64") == std::string::npos && f64_print_bytes.find("bearer_print_u64") == std::string::npos); assert(f64_print_bytes.find("bearer_handler_input_dval") == std::string::npos); const auto contextual_host = capy::compile_bearer_unit("function CLI(request : dval) { print(gen_noise64(1, 2)) }\n", options); assert(std::string(contextual_host.wasm.begin(), contextual_host.wasm.end()).find("bearer_noise_u64") != std::string::npos); const auto contextual_f32 = capy::compile_bearer_unit("function take(value : f32) {}\nfunction CLI(request : dval) { take(1.5) }\n", options); const std::string contextual_f32_bytes(contextual_f32.wasm.begin(), contextual_f32.wasm.end()); assert(contextual_f32_bytes.find(std::string("\x43\x00\x00\xc0\x3f", 5)) != std::string::npos); assert(contextual_f32_bytes.find(std::string("\x44\x00\x00\x00\x00\x00\x00\xf8\x3f", 9)) == std::string::npos); try { capy::compile_bearer_unit("function take(value : f32) {}\nfunction CLI(request : dval) { take(1e100) }\n", options); assert(false); } catch (const capy::Error& error) { assert(error.message.find("outside the f32 range") != std::string::npos); } const auto print_value = capy::compile_bearer_unit( "function CLI(request : dval) { var output : function(...values : as string) void = print; output(1, \"x\") }\n", options); assert(capy::wasm::validate_bearer_unit(print_value.wasm, {.bearer_abi_version = "11"}).valid); const std::string print_value_bytes(print_value.wasm.begin(), print_value.wasm.end()); assert(print_value_bytes.find("bearer_alloc") != std::string::npos && print_value_bytes.find("bearer_free") != std::string::npos); const auto ordinary_backtrace = capy::compile_bearer_unit( "function outer() string { -> inner() }\nfunction inner() string { var capture := function() string { -> backtrace_get_frames(2, 1) }; -> capture() }\nfunction CLI(request : dval) { print(outer(), backtrace_get_frames()) }\n", options); assert(capy::wasm::validate_bearer_unit(ordinary_backtrace.wasm, {.bearer_abi_version = "11"}).valid); const std::string backtrace_bytes(ordinary_backtrace.wasm.begin(), ordinary_backtrace.wasm.end()); assert(backtrace_bytes.find("bearer_capy_backtrace") != std::string::npos); assert(backtrace_bytes.find("void*") == std::string::npos); assert(ordinary_backtrace.source_map.find("F\t2\tcapy://stdlib.capy\n") != std::string::npos); capy::CompileOptions rooted_path_options = options; rooted_path_options.source_path = "/checkout/site/tests/rooted.capy"; rooted_path_options.source_root = "/checkout"; const auto rooted_path = capy::compile_bearer_unit( "function CLI(request : dval) { print(backtrace_get_frames()) }\n", rooted_path_options); const std::string rooted_path_bytes(rooted_path.wasm.begin(), rooted_path.wasm.end()); assert(rooted_path_bytes.find("source=site/tests/rooted.capy\n") != std::string::npos); assert(rooted_path_bytes.find(rooted_path_options.source_path) == std::string::npos); assert(rooted_path.source_map.find("F\t1\tsite/tests/rooted.capy\n") != std::string::npos); capy::CompileOptions outside_path_options = rooted_path_options; outside_path_options.source_path = "/outside/private/source.capy"; const auto outside_path = capy::compile_bearer_unit("function CLI(request : dval) {}\n", outside_path_options); assert(outside_path.source_map.find("F\t1\tsource.capy\n") != std::string::npos); capy::CompileOptions virtual_path_options = options; virtual_path_options.source_path = "capy://generated/test.capy"; virtual_path_options.source_root = "/checkout"; const auto virtual_path = capy::compile_bearer_unit("function CLI(request : dval) {}\n", virtual_path_options); assert(virtual_path.source_map.find("F\t1\tcapy://generated/test.capy\n") != std::string::npos); try { capy::compile_bearer_unit("function CLI(request : dval) { missing() }\n", rooted_path_options); assert(false); } catch (const capy::Error& error) { assert(error.location.file == rooted_path_options.source_path); } capy::CompileOptions control_path_options = options; control_path_options.source_path = "tab\tline\n\033.capy"; const auto control_path_backtrace = capy::compile_bearer_unit("function CLI(request : dval) { print(backtrace_get_frames()) }\n", control_path_options); const std::string control_path_bytes(control_path_backtrace.wasm.begin(), control_path_backtrace.wasm.end()); const std::string control_record = std::string("\003\0\0\0\017\0\0\0\001\0\0\0\001\0\0\0CLI", 19) + control_path_options.source_path; assert(control_path_bytes.find(control_record) != std::string::npos); std::string control_frame; assert(capy_backtrace::format_record(control_record, control_frame)); assert(control_frame == "CLI at tab\\x09line\\x0A\\x1B.capy:1:1"); std::string oversized_frame; const std::string oversized_path(5000, 'p'); std::string oversized_record("\003\0\0\0", 4); oversized_record.append("\210\023\0\0", 4); oversized_record.append("\001\0\0\0\001\0\0\0CLI", 11); oversized_record += oversized_path; assert(capy_backtrace::format_record(oversized_record, oversized_frame)); assert(oversized_frame.find("...[truncated]") != std::string::npos); capy::CompileOptions long_path_options = options; long_path_options.source_path = std::string(5000, 'p') + ".capy"; const auto long_path_backtrace = capy::compile_bearer_unit("function CLI(request : dval) { print(backtrace_get_frames()) }\n", long_path_options); assert(capy::wasm::validate_bearer_unit(long_path_backtrace.wasm, {.bearer_abi_version = "11"}).valid); assert(std::string(long_path_backtrace.wasm.begin(), long_path_backtrace.wasm.end()).find(long_path_options.source_path) != std::string::npos); const auto ordinary_boundary = capy::compile_bearer_unit( "function CLI(request : dval) { print(string(request.body), string(request.websocket.connection_id), component_resolve(\"card\"), regex_replace(\"x\", \"y\", \"x\"), base64_encode(\"x\"), file_temp(), time()) }\n", options); assert(ordinary_boundary.source_map.find("F\t2\tcapy://stdlib.capy\n") != std::string::npos); const std::string boundary_bytes(ordinary_boundary.wasm.begin(), ordinary_boundary.wasm.end()); for (const auto& import : {"bearer_component_resolve", "bearer_regex", "bearer_codec", "bearer_file_temp", "bearer_time"}) assert(boundary_bytes.find(import) != std::string::npos); assert(boundary_bytes.find("bearer_handler_input_dval") == std::string::npos); const auto narrow_boundary = capy::compile_bearer_unit("function CLI(request : dval) { print(string(request.body)) }\n", options); const std::string narrow_bytes(narrow_boundary.wasm.begin(), narrow_boundary.wasm.end()); assert(narrow_bytes.find("bearer_handler_input_dval") == std::string::npos); for (const auto& absent : {"bearer_request_value", "bearer_request_body", "bearer_ws_", "bearer_component_", "bearer_regex", "bearer_codec", "bearer_file_", "bearer_time", "bearer_unit_load", "bearer_dv_module_call"}) assert(narrow_bytes.find(absent) == std::string::npos); std::ifstream compiler_source("src/capy/compiler.cpp"); const std::string compiler_text((std::istreambuf_iterator(compiler_source)), {}); assert(compiler_source && compiler_text.find("bearer_string" "_list") == std::string::npos && compiler_text.find("__legacy" "_") == std::string::npos); assert(compiler_text.find("named->value == \"__bearer_") == std::string::npos); assert(compiler_text.find("named->value == \"__bearer_mysql") == std::string::npos); assert(compiler_text.find("named->value == \"__bearer_sqlite") == std::string::npos); assert(compiler_text.find("named->value == \"__bearer_regex") == std::string::npos); assert(compiler_text.find("named->value == \"__bearer_unit_call") == std::string::npos); assert(compiler_text.find("named->value == \"__bearer_codec") == std::string::npos); assert(compiler_text.find("named->value == \"unit_load\"") == std::string::npos); assert(compiler_text.find("named->value == \"call\"") == std::string::npos); for (const char* old_name : {"dval_string", "dval_bool", "dval_s32", "dval_f64", "dval_to_string", "dval_to_bool", "dval_to_f64", "dval_to_s64", "dval_to_u64"}) assert(compiler_text.find("\"" + std::string(old_name) + "\"") == std::string::npos); for (const auto& public_name : {"array_merge", "set", "push", "pop", "delete", "clear", "get_by_path", "get_or_create", "get_type_name", "get", "keys", "values", "has", "request_param", "request_get", "request_post", "request_cookie", "request_session", "request_body", "request_context", "session_start", "session_set", "session_remove", "session_destroy", "session_id_create", "redirect", "csrf_token", "csrf_valid", "csrf_rotate", "csrf_field", "ws_message", "ws_connection_id", "ws_scope", "ws_opcode", "ws_is_binary", "ws_send", "ws_send_to", "ws_close", "component", "component_render", "component_resolve", "unit_render", "unit_call", "unit_info", "units_list", "unit_compile", "regex_match", "regex_search", "regex_search_all", "regex_replace", "regex_split", "base64_encode", "base64_decode", "uri_encode", "uri_decode", "json_encode", "json_decode", "html_escape", "strpos", "file_open", "file_read", "file_write", "file_seek", "file_tell", "file_fsync", "file_close", "file_temp", "file_unlink", "time", "time_precise"}) assert(compiler_text.find("named->value == \"" + std::string(public_name) + "\"") == std::string::npos); const auto ordinary_mysql = capy::compile_bearer_unit("function CLI(request : dval) { print(mysql_escape(\"Ada\")) }\n", options); assert(capy::wasm::validate_bearer_unit(ordinary_mysql.wasm, {.bearer_abi_version = "11"}).valid); const std::string mysql_bytes(ordinary_mysql.wasm.begin(), ordinary_mysql.wasm.end()); assert(mysql_bytes.find("bearer_mysql_escape") != std::string::npos); const auto typed_sized_hosts = capy::compile_bearer_unit( "function CLI(request : dval) { var params := dval({}); mysql_query(u64(0), \"select 1\", params); sqlite_query(u64(0), \"select 1\", params); regex_search(\"x\", \"x\"); regex_replace(\"x\", \"y\", \"x\"); unit_call(\"/x:CLI\", params); json_decode(\"{}\"); base64_encode(\"x\") }\n", options); assert(capy::wasm::validate_bearer_unit(typed_sized_hosts.wasm, {.bearer_abi_version = "11"}).valid); const std::string typed_sized_host_bytes(typed_sized_hosts.wasm.begin(), typed_sized_hosts.wasm.end()); for (const auto& import : {"bearer_mysql_query", "bearer_sqlite_query", "bearer_regex_dval", "bearer_regex_text", "bearer_dv_unit_call", "bearer_codec_dval", "bearer_codec_text"}) assert(typed_sized_host_bytes.find(import) != std::string::npos); const auto owned_sized_host = capy::compile_bearer_unit( "function CLI(request : dval) { sqlite_query(u64(0), clone(\"select 1\"), dval({\"owned\": clone(\"value\")})) }\n", options); assert(capy::wasm::validate_bearer_unit(owned_sized_host.wasm, {.bearer_abi_version = "11"}).valid); const std::string sizing_release = "release_inputs(); append(code, module_.marker(value->location));"; const std::size_t first_sizing_release = compiler_text.find(sizing_release); assert(first_sizing_release != std::string::npos && compiler_text.find(sizing_release, first_sizing_release + 1) != std::string::npos); const std::string function_value_source = "function CLI(request : dval) { var escape : function(raw : string) string = mysql_escape; print(escape(\"Ada\")) }\n"; const auto function_value = capy::compile_bearer_unit(function_value_source, options); assert(capy::wasm::validate_bearer_unit(function_value.wasm, {.bearer_abi_version = "11"}).valid); assert(std::string(function_value.wasm.begin(), function_value.wasm.end()).find("bearer_mysql_escape") != std::string::npos); const auto parsed_function_value = capy::parse(function_value_source, options.source_path); const auto parsed_result = capy::compile_bearer_unit(parsed_function_value, options.source_path, options.module_name, options.abi_version); assert(function_value.wasm == parsed_result.wasm && function_value.source_map == parsed_result.source_map); const auto scoped_stdlib_parameter = capy::compile_bearer_unit( "function CLI(request : dval) { print(encode_query(dval({\"name\": \"Ada\"}))) }\n", options); const auto bare_stdlib_function_value = capy::compile_bearer_unit("function CLI(request : dval) { map(dval([\"X\"]), lower) }\n", options); assert(capy::wasm::validate_bearer_unit(bare_stdlib_function_value.wasm, {.bearer_abi_version = "11"}).valid); assert(std::string(bare_stdlib_function_value.wasm.begin(), bare_stdlib_function_value.wasm.end()).find("bearer_string_lower") != std::string::npos); const auto overloaded_stdlib_function_value = capy::compile_bearer_unit( "function CLI(request : dval) { var slice : function(value : string, start : s64) string = substr; print(slice(\"Ada\", s64(1))) }\n", options); assert(capy::wasm::validate_bearer_unit(overloaded_stdlib_function_value.wasm, {.bearer_abi_version = "11"}).valid); const auto returned_stdlib_function_value = capy::compile_bearer_unit( "function lower_callback() (function(value : string) string) { return lower }\nfunction CLI(request : dval) { map(dval([\"X\"]), lower_callback()) }\n", options); assert(std::string(returned_stdlib_function_value.wasm.begin(), returned_stdlib_function_value.wasm.end()).find("bearer_string_lower") != std::string::npos); const auto shadowed_stdlib = capy::compile_bearer_unit( "function CLI(request : dval) { var sqlite_connect : function(path : string) u64 = function(path : string) u64 { -> u64(1) }; print(sqlite_connect(\"x\")) }\n", options); assert(std::string(shadowed_stdlib.wasm.begin(), shadowed_stdlib.wasm.end()).find("bearer_sqlite_") == std::string::npos); const auto shadowed_function_value = capy::compile_bearer_unit( "function CLI(request : dval) { var lower : function(value : string) string = function(value : string) string { -> value }; map(dval([\"X\"]), lower) }\n", options); assert(std::string(shadowed_function_value.wasm.begin(), shadowed_function_value.wasm.end()).find("bearer_string_lower") == std::string::npos); for (const auto& [source, expected] : { std::pair{"host function __bearer_trace() string\nfunction CLI(request : dval) {}\n", "host declarations are available only in the embedded Capy standard library"}, std::pair{"trace host function __bearer_trace() string\nfunction CLI(request : dval) {}\n", "host declarations are available only in the embedded Capy standard library"}, std::pair{"function __bearer_sqlite_connect(path : string) u64 { -> u64(0) }\nfunction CLI(request : dval) {}\n", "reserved for the Capy standard library"}, std::pair{"function CLI(request : dval) { __bearer_sqlite_connect(\":memory:\") }\n", "reserved for the Capy standard library"}, std::pair{"function sqlite_connect(path : string) u64 { -> u64(0) }\nfunction CLI(request : dval) {}\n", "reserved by the Capy standard library"}, std::pair{"function CLI(request : dval) { var callback : function(__bearer_value : s32) s32 = function(__bearer_value : s32) s32 { -> __bearer_value } }\n", "__bearer_* names are reserved for the Capy standard library"}, std::pair{"function CLI(request : dval) { for value, __bearer_key := dval({}) { value } }\n", "__bearer_* names are reserved for the Capy standard library"}, std::pair{"function duplicate(value : any) value::type { -> value }\nfunction duplicate(value : any) value::type { -> value }\nfunction CLI(request : dval) {}\n", "duplicate overload duplicate(any)"}, }) { try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error& error) { assert(error.message.find(expected) != std::string::npos); } } const auto wide = capy::compile_bearer_unit("function next(value : u64) u64 { -> value + u64(1) }\nfunction half(value : f64) f64 { -> value / 2.0 }\n" "function CLI(request : dval) { var fn : function(value : u64) u64 = next; print(fn(u64(4)), half(3.0), u64(1), s32(9.0)) }\n", options); assert(capy::wasm::validate_bearer_unit(wide.wasm, {.bearer_abi_version = "11"}).valid); const auto marker_collision = capy::compile_bearer_unit("function CLI(request : dval) { 1509949440; print([1][1]) }\n", options); assert(capy::wasm::validate_bearer_unit(marker_collision.wasm, {.bearer_abi_version = "11"}).valid); assert(marker_collision.source_map.find("\t1\t1\t53\n") != std::string::npos); const auto merge_marker = capy::compile_bearer_unit( "function CLI(request : dval) {\n array_merge(dval({\"left\": \"x\"}), dval({\"right\": \"y\"}))\n}\n", options); assert(capy::wasm::validate_bearer_unit(merge_marker.wasm, {.bearer_abi_version = "11"}).valid); assert(merge_marker.source_map.find("\t2\t5\n") != std::string::npos); for (const auto& [source, expected] : { std::pair{"function implicit() s32 { 1 }\nfunction CLI(request : dval) { print(implicit()) }\n", "not all paths produce s32"}, std::pair{"function misplaced() s32 { -> 1; 2 }\nfunction CLI(request : dval) { print(misplaced()) }\n", "block yield must be the final item"}, std::pair{"function CLI(request : dval) { print(-> 1) }\n", "block yield is only valid as a block item"}, std::pair{"function CLI(request : dval) { var value := { 1 } }\n", "value-producing block must end with '-> expression'"}, std::pair{"function wrong() string { -> 1 }\nfunction CLI(request : dval) { print(wrong()) }\n", "expected string, found s64"}, }) { try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error& error) { assert(error.message.find(expected) != std::string::npos); } } for (const auto source : { "function square(value : any) value::type { -> value * value }\nfunction CLI(request : dval) { square(clone(\"x\")) }\n", "function choose(a : any, b : s32) a::type { -> a }\nfunction choose(a : s32, b : any) b::type { -> b }\nfunction CLI(request : dval) { choose(1, 1) }\n", "function incomplete(value : bool) s32 { if value { return 1 } }\nfunction CLI(request : dval) { print(incomplete(false)) }\n", "function CLI(request : dval) { print(9223372036854775808) }\n", "function CLI(request : dval) { print(-9223372036854775809) }\n", "function CLI(request : dval) { print(999999999999999999999999999999999999) }\n", "function CLI(request : dval) { value := 1; value := 2 }\n", "function CLI(request : dval) { print(1 && true) }\n", "function CLI(request : dval) { false && (hidden := true); print(hidden) }\n", "function CLI(request : dval) { var held := clone(\"old\"); var replace := function() { held = clone(\"new\") } }\n", }) { try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error&) { } } const auto wide_aggregates = capy::compile_bearer_unit( "struct Wide { narrow : s32; signed : s64; unsigned : u64; decimal : f64; text : string }\n" "function capture(value : u64, decimal : f64, text : string) (function(add : u64) f64) { -> function(add : u64) f64 { -> f64(value + add) + decimal + f64(length(text)) } }\n" "function CLI(request : dval) { var wide := Wide(s32(1), s64(-2), u64(3), 4.5, clone(\"x\")); var fn := capture(wide.unsigned, wide.decimal, wide.text); print(wide.signed, fn(u64(2)), arc_live()) }\n", options); assert(capy::wasm::validate_bearer_unit(wide_aggregates.wasm, {.bearer_abi_version = "11"}).valid); const auto assignment_contexts = capy::compile_bearer_unit( "struct Inner { signed : s64; unsigned : u64; decimal : f64; text : string }\n" "struct Holder { inner : Inner; callback : function(value : s64) s64 }\n" "function identity(value : s64) s64 { -> value }\nfunction identity(value : u64) u64 { -> value }\nfunction identity(value : f64) f64 { -> value }\n" "function select(value : s64) s32 { -> 1 }\nfunction select(value : u64) s32 { -> 2 }\nfunction select(value : f64) s32 { -> 3 }\n" "function return_signed(inner : Inner) s64 { -> inner.signed = 9 }\nfunction return_unsigned(inner : Inner) u64 { -> inner.unsigned = 10 }\nfunction return_decimal(inner : Inner) f64 { -> inner.decimal = 2.5 }\n" "function borrowed(inner : Inner, value : string) string { -> inner.text = value }\n" "function owned(inner : Inner) string { -> inner.text = clone(\"owned\") }\n" "function CLI(request : dval) { var holder := Holder(Inner(s64(0), u64(0), 0.0, clone(\"old\")), function(value : s64) s64 { -> value }); " "var inferred_signed := (holder.inner.signed = 9223372036854775807); var inferred_unsigned := (holder.inner.unsigned = 18446744073709551615); var inferred_decimal := (holder.inner.decimal = 1.25); " "print(identity(holder.inner.signed = 7), identity(holder.inner.unsigned = 8), identity(holder.inner.decimal = 1.5), select(holder.inner.signed = 8), " "select(holder.inner.unsigned = 9), select(holder.inner.decimal = 2.0), inferred_signed, inferred_unsigned, inferred_decimal, " "return_signed(holder.inner), return_unsigned(holder.inner), return_decimal(holder.inner)); " "holder.inner.decimal = 1.5; " "holder.callback = function(value : s64) s64 { -> value + s64(1) }; print(holder.callback(s64(2)), borrowed(holder.inner, clone(\"borrowed\")), owned(holder.inner), arc_live()) }\n", options); assert(capy::wasm::validate_bearer_unit(assignment_contexts.wasm, {.bearer_abi_version = "11"}).valid); const auto cycle_characterization = capy::compile_bearer_unit( "struct Cycle { children : [Cycle] }\nfunction empty_cycles() [Cycle] { var values : [Cycle] = []; -> values }\nfunction CLI(request : dval) { var cycle := Cycle(empty_cycles()); cycle.children.push(cycle); print(arc_live() > 0) }\n", options); assert(capy::wasm::validate_bearer_unit(cycle_characterization.wasm, {.bearer_abi_version = "11"}).valid); const auto value_expressions = capy::compile_bearer_unit( "function choose(flag : bool) string { -> { var prefix := clone(\"p\"); -> if flag { -> prefix + \"a\" } else { return clone(\"b\") } } }\n" "function CLI(request : dval) { var scalar := { var base := 2; -> if true { -> base + 1 } else { -> base + 2 } }; print(scalar, choose(true), choose(false), arc_live()) }\n", options); assert(capy::wasm::validate_bearer_unit(value_expressions.wasm, {.bearer_abi_version = "11"}).valid); for (const auto& [source, expected] : { std::pair{"function CLI(request : dval) { (1 + 2) := 3 }\n", "inferred declaration target must be a local name"}, std::pair{"function CLI(request : dval) { print(1 && true) }\n", "logical operators require bool operands"}, std::pair{"function CLI(request : dval) { var held := clone(\"old\"); var replace := function() { held = clone(\"new\") } }\n", "cannot assign to captured binding 'held'. Captures are immutable"}, std::pair{"struct Item { wide : s64; text : string }\nfunction CLI(request : dval) { var item := Item(s64(0), clone(\"x\")); item.wide = \"bad\" }\n", "expected s64, found string"}, std::pair{"struct Item { wide : s64 }\nfunction CLI(request : dval) { var item := Item(s64(0)); item.missing = 1 }\n", "struct has no member 'missing'"}, std::pair{"struct Item { wide : s64 }\nfunction CLI(request : dval) { var item := Item(s64(0)); item.wide = 9223372036854775808 }\n", "outside the s64 range"}, std::pair{"struct Item { wide : u64 }\nfunction CLI(request : dval) { var item := Item(u64(0)); item.wide = -1 }\n", "outside the u64 range"}, std::pair{"function CLI(request : dval) { var value := if true { -> 1 } else { -> \"x\" } }\n", "if branches produce s64 and string"}, std::pair{"function CLI(request : dval) { print(first(\"ok\", 1)) }\n", "no overload first(string, s64)"}, std::pair{"function CLI(request : dval) { array_merge(dval({\"x\": \"y\"}), \"bad\") }\n", "no overload array_merge(dval, string)"}, std::pair{"function CLI(request : dval) { array_merge(dval({\"x\": \"y\"})) }\n", "no overload array_merge(dval)"}, }) { try { capy::compile_bearer_unit(source, options); assert(false); } catch (const capy::Error& error) { assert(error.message.find(expected) != std::string::npos); assert(error.message.find("unsupported operator") == std::string::npos); } } bool cancelled = false; options.cancelled = [&] { return cancelled; }; cancelled = true; try { capy::compile_bearer_unit("function CLI(request : dval) {}", options); assert(false); } catch (const capy::Error&) { } unsigned cancellation_polls = 0; options.cancelled = [&] { return ++cancellation_polls == 2; }; const std::string large_source = "function CLI(request : dval) { print(\"" + std::string(20000, 'x') + "\") }\n"; try { capy::compile_bearer_unit(large_source, options); assert(false); } catch (const capy::Error&) { } assert(cancellation_polls == 2); std::cout << "native Capy compiler smoke tests passed\n"; }