diff --git a/CMakeLists.txt b/CMakeLists.txt index 3878cd059..34794dc9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,6 +147,7 @@ add_library( src/cpp/jank/util/cli.cpp src/cpp/jank/util/mapped_file.cpp src/cpp/jank/util/scope_exit.cpp + src/cpp/jank/util/escape.cpp src/cpp/jank/profile/time.cpp src/cpp/jank/read/lex.cpp src/cpp/jank/read/parse.cpp diff --git a/include/cpp/jank/option.hpp b/include/cpp/jank/option.hpp index 298462c0f..e5afbecec 100644 --- a/include/cpp/jank/option.hpp +++ b/include/cpp/jank/option.hpp @@ -199,7 +199,7 @@ namespace jank return *reinterpret_cast(data) != *reinterpret_cast(rhs.data); } - return true; + return false; } constexpr native_bool operator==(option const &rhs) const diff --git a/include/cpp/jank/read/lex.hpp b/include/cpp/jank/read/lex.hpp index 9a4b5fa06..8e436ffa3 100644 --- a/include/cpp/jank/read/lex.hpp +++ b/include/cpp/jank/read/lex.hpp @@ -40,6 +40,7 @@ namespace jank::read::lex struct token { + token() = default; token(token_kind const k); token(size_t const p, token_kind const k); token(size_t const p, token_kind const k, native_integer const); @@ -51,6 +52,7 @@ namespace jank::read::lex token(size_t const p, size_t const s, token_kind const k, native_integer const); token(size_t const p, size_t const s, token_kind const k, native_real const); token(size_t const p, size_t const s, token_kind const k, native_persistent_string_view const); + token(size_t const p, size_t const s, token_kind const k, char const * const); token(size_t const p, size_t const s, token_kind const k, native_bool const); native_bool operator==(token const &rhs) const; @@ -67,7 +69,7 @@ namespace jank::read::lex static constexpr size_t ignore_pos{ std::numeric_limits::max() }; size_t pos{ ignore_pos }; size_t size{ 1 }; - token_kind kind; + token_kind kind{ token_kind::eof }; boost::variant data; }; diff --git a/include/cpp/jank/read/parse.hpp b/include/cpp/jank/read/parse.hpp index 07b12dd46..71fd1defe 100644 --- a/include/cpp/jank/read/parse.hpp +++ b/include/cpp/jank/read/parse.hpp @@ -14,8 +14,16 @@ namespace jank::read::parse { struct processor { - /* TODO: none instead of nullptr. */ - using object_result = result; + struct object_source_info + { + native_bool operator==(object_source_info const &rhs) const; + native_bool operator!=(object_source_info const &rhs) const; + + runtime::object_ptr ptr{}; + lex::token start, end; + }; + + using object_result = result, error>; struct iterator { @@ -60,6 +68,7 @@ namespace jank::read::parse runtime::context &rt_ctx; lex::processor::iterator token_current, token_end; option expected_closer; + lex::token latest_token; /* Whether or not the next form is considered quoted. */ native_bool quoted{}; }; diff --git a/include/cpp/jank/codegen/escape.hpp b/include/cpp/jank/util/escape.hpp similarity index 59% rename from include/cpp/jank/codegen/escape.hpp rename to include/cpp/jank/util/escape.hpp index 32c03c18a..42525fe82 100644 --- a/include/cpp/jank/codegen/escape.hpp +++ b/include/cpp/jank/util/escape.hpp @@ -1,10 +1,14 @@ #pragma once -/* This provides a fmt extension for escaping strings and wrapping them in - * quotes. It's largely adapted from here: - * https://github.com/fmtlib/fmt/issues/825#issuecomment-1227501168 */ -namespace jank::codegen +namespace jank::util { + /* This provides a fmt extension for escaping strings and wrapping them in + * quotes. It's largely adapted from here: + * https://github.com/fmtlib/fmt/issues/825#issuecomment-1227501168 + * + * Usage just looks like: + * fmt::format("{}", util::escaped_quoted_view(s)) + */ template struct escape_view { @@ -50,16 +54,22 @@ namespace jank::codegen }; constexpr escape_view - escaped(native_persistent_string_view const &sv, char const q = '"', char const e = '\\') + escaped_quoted_view(native_persistent_string_view const &sv, + char const q = '"', + char const e = '\\') { return escape_view{ sv, q, e }; } + + /* These provide normal escaping/unescaping, with no quoting. */ + string_result unescape(native_transient_string const &input); + native_transient_string escape(native_transient_string const &input); } template -struct fmt::formatter> +struct fmt::formatter> { - using V = jank::codegen::escape_view; + using V = jank::util::escape_view; template constexpr auto parse(C &ctx) @@ -68,7 +78,7 @@ struct fmt::formatter> } template - auto format(jank::codegen::escape_view const &s, C &ctx) + auto format(jank::util::escape_view const &s, C &ctx) { return s.copy(ctx.out()); } diff --git a/src/cpp/jank/analyze/processor.cpp b/src/cpp/jank/analyze/processor.cpp index b919ab2bd..f2d577b37 100644 --- a/src/cpp/jank/analyze/processor.cpp +++ b/src/cpp/jank/analyze/processor.cpp @@ -65,7 +65,7 @@ namespace jank::analyze { return err(parse_current->expect_err_move()); } - fn.push_back(parse_current->expect_ok()); + fn.push_back(parse_current->expect_ok().unwrap().ptr); } auto fn_list(make_box(fn.rbegin(), fn.rend())); return analyze(fn_list, expression_type::expression); @@ -957,54 +957,68 @@ namespace jank::analyze /* native/raw expressions are broken up into chunks of either literal C++ code or * interpolated jank code, the latter needing to also be analyzed. */ decltype(expr::native_raw::chunks) chunks; - /* TODO: Just use } for end and rely on token parsing info for when that is. - * This requires storing line/col start/end meta in each object. */ - constexpr native_persistent_string_view interp_start{ "#{" }, interp_end{ "}#" }; - for(size_t it{}; it != native_persistent_string::npos;) + constexpr native_persistent_string_view interp_start{ "~{" }; + for(size_t it{}; it < code_str->data.size();) { - auto const next_start(code_str->data.find(interp_start.data(), it)); - if(next_start == native_persistent_string::npos) + auto const next_interp(code_str->data.find(interp_start.data(), it)); + if(next_interp == native_persistent_string::npos) { /* This is the final chunk. */ chunks.emplace_back(native_persistent_string_view{ code_str->data.data() + it }); break; } - auto const next_end(code_str->data.find(interp_end.data(), next_start)); - if(next_end == native_persistent_string::npos) - { - return err( - error{ fmt::format("no matching {} found for native/raw interpolation", interp_end) }); - } + /* Once we've found the start of an interpolation, we begin lexing/parsing at that + * spot, so we can get a jank value. */ read::lex::processor l_prc{ - {code_str->data.data() + next_start + interp_start.size(), - next_end - next_start - interp_end.size()} + {code_str->data.data() + next_interp + interp_start.size(), + code_str->data.data() + code_str->data.size()} }; read::parse::processor p_prc{ rt_ctx, l_prc.begin(), l_prc.end() }; - auto parsed_it(p_prc.begin()); - if(parsed_it->is_err()) + auto parsed_obj(p_prc.next()); + if(parsed_obj.is_err()) + { + return parsed_obj.expect_err_move(); + } + else if(parsed_obj.expect_ok().is_none()) { - return parsed_it->expect_err_move(); + return err(error{ next_interp + interp_start.size(), "invalid native/raw interpolation" }); } - auto result( - analyze(parsed_it->expect_ok(), current_frame, expression_type::expression, fn_ctx, true)); + + /* We get back an AST expression and keep track of it as a chunk for later codegen. */ + auto result(analyze(parsed_obj.expect_ok().unwrap().ptr, + current_frame, + expression_type::expression, + fn_ctx, + true)); if(result.is_err()) { return result.expect_err_move(); } - if(next_start - it > 0) + /* C++ code before the next interpolation. */ + if(next_interp - it > 0) { chunks.emplace_back( - native_persistent_string_view{ code_str->data.data() + it, next_start - it }); + native_persistent_string_view{ code_str->data.data() + it, next_interp - it }); } chunks.emplace_back(result.expect_ok()); - it = next_end + interp_end.size(); - if(++parsed_it != p_prc.end()) + /* The next token needs to be a }, to match our original ~{. If it's not, either multiple + * forms were included in the interpolation or there is no closing }. We don't know for + * sure. */ + auto const next_token(*p_prc.token_current); + if(next_token.is_err()) + { + return next_token.expect_err(); + } + else if(next_token.expect_ok().kind != read::lex::token_kind::close_curly_bracket) { - return err(error{ "invalid native/raw: only one expression per interpolation" }); + return err(error{ + "invalid native/raw interpolation: ~{ must be followed by a single form and then a }" }); } + it = next_interp + interp_start.size() + next_token.expect_ok().pos + + next_token.expect_ok().size; } return make_box(expr::native_raw{ diff --git a/src/cpp/jank/codegen/processor.cpp b/src/cpp/jank/codegen/processor.cpp index 8719a4845..3965f1b80 100644 --- a/src/cpp/jank/codegen/processor.cpp +++ b/src/cpp/jank/codegen/processor.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include /* The strategy for codegen to C++ is quite simple. Codegen always happens on a @@ -181,7 +181,7 @@ namespace jank::codegen { fmt::format_to(inserter, "jank::make_box({})", - escaped(typed_o->data)); + util::escaped_quoted_view(typed_o->data)); } else if constexpr(std::same_as) { diff --git a/src/cpp/jank/read/lex.cpp b/src/cpp/jank/read/lex.cpp index 2ab38f856..c49453870 100644 --- a/src/cpp/jank/read/lex.cpp +++ b/src/cpp/jank/read/lex.cpp @@ -138,6 +138,14 @@ namespace jank::read { } + token::token(size_t const p, size_t const s, token_kind const k, char const * const d) + : pos{ p } + , size{ s } + , kind{ k } + , data{ native_persistent_string_view{ d } } + { + } + token::token(size_t const p, size_t const s, token_kind const k, native_bool const d) : pos{ p } , size{ s } diff --git a/src/cpp/jank/read/parse.cpp b/src/cpp/jank/read/parse.cpp index c25fc76c4..7cd62c0c2 100644 --- a/src/cpp/jank/read/parse.cpp +++ b/src/cpp/jank/read/parse.cpp @@ -12,58 +12,20 @@ #include #include #include +#include namespace jank::read::parse { - namespace detail + native_bool + processor::object_source_info::operator==(processor::object_source_info const &rhs) const { - string_result unescape(native_transient_string const &input) - { - native_transient_string ss; - ss.reserve(input.size()); - native_bool escape{}; - - for(auto const c : input) - { - if(!escape) - { - if(c == '\\') - { - escape = true; - } - else - { - ss += c; - } - } - else - { - switch(c) - { - case 'n': - ss += '\n'; - break; - case 't': - ss += '\t'; - break; - case 'r': - ss += '\r'; - break; - case '\\': - ss += '\\'; - break; - case '"': - ss += '"'; - break; - default: - return err(fmt::format("invalid escape sequence: \\{}", c)); - } - escape = false; - } - } + return !(*this != rhs); + } - return ok(ss); - } + native_bool + processor::object_source_info::operator!=(processor::object_source_info const &rhs) const + { + return ptr != rhs.ptr || start != rhs.start || end != rhs.end; } processor::iterator::value_type processor::iterator::operator*() const @@ -109,10 +71,9 @@ namespace jank::read::parse processor::object_result processor::next() { - /* TODO: Replace nullptr with none. */ if(token_current == token_end) { - return ok(nullptr); + return ok(none); } while(true) @@ -122,8 +83,8 @@ namespace jank::read::parse { return token_result.err().unwrap(); } - auto token(token_result.expect_ok()); - switch(token.kind) + latest_token = token_result.expect_ok(); + switch(latest_token.kind) { /* We ignore comments, but everything else returns out of the loop. */ case lex::token_kind::comment: @@ -139,14 +100,14 @@ namespace jank::read::parse case lex::token_kind::close_square_bracket: case lex::token_kind::close_paren: case lex::token_kind::close_curly_bracket: - if(expected_closer != token.kind) + if(expected_closer != latest_token.kind) { - return err( - error{ token.pos, native_persistent_string{ "unexpected closing character" } }); + return err(error{ latest_token.pos, + native_persistent_string{ "unexpected closing character" } }); } ++token_current; expected_closer = none; - return ok(nullptr); + return ok(none); case lex::token_kind::single_quote: return parse_quote(); case lex::token_kind::nil: @@ -166,12 +127,12 @@ namespace jank::read::parse case lex::token_kind::escaped_string: return parse_escaped_string(); case lex::token_kind::eof: - return ok(nullptr); + return ok(none); default: { native_persistent_string msg{ fmt::format("unexpected token kind: {}", - magic_enum::enum_name(token.kind)) }; - return err(error{ token.pos, std::move(msg) }); + magic_enum::enum_name(latest_token.kind)) }; + return err(error{ latest_token.pos, std::move(msg) }); } } } @@ -179,7 +140,7 @@ namespace jank::read::parse processor::object_result processor::parse_list() { - auto const start_token(token_current.latest.unwrap().expect_ok()); + auto const start_token((*token_current).expect_ok()); ++token_current; auto const prev_expected_closer(expected_closer); expected_closer = some(lex::token_kind::close_paren); @@ -191,7 +152,7 @@ namespace jank::read::parse { return err(it.latest.unwrap().expect_err()); } - ret.push_back(it.latest.unwrap().expect_ok()); + ret.push_back(it.latest.unwrap().expect_ok().unwrap().ptr); } if(expected_closer.is_some()) { @@ -199,12 +160,14 @@ namespace jank::read::parse } expected_closer = prev_expected_closer; - return make_box(ret.rbegin(), ret.rend()); + return object_source_info{ make_box(ret.rbegin(), ret.rend()), + start_token, + latest_token }; } processor::object_result processor::parse_vector() { - auto const start_token(token_current.latest.unwrap().expect_ok()); + auto const start_token((*token_current).expect_ok()); ++token_current; auto const prev_expected_closer(expected_closer); expected_closer = some(lex::token_kind::close_square_bracket); @@ -216,7 +179,7 @@ namespace jank::read::parse { return err(it.latest.unwrap().expect_err()); } - ret.push_back(it.latest.unwrap().expect_ok()); + ret.push_back(it.latest.unwrap().expect_ok().unwrap().ptr); } if(expected_closer.is_some()) { @@ -224,13 +187,15 @@ namespace jank::read::parse } expected_closer = prev_expected_closer; - return make_box(ret.persistent()); + return object_source_info{ make_box(ret.persistent()), + start_token, + latest_token }; } /* TODO: Uniqueness check. */ processor::object_result processor::parse_map() { - auto const start_token(token_current.latest.unwrap().expect_ok()); + auto const start_token((*token_current).expect_ok()); ++token_current; auto const prev_expected_closer(expected_closer); expected_closer = some(lex::token_kind::close_curly_bracket); @@ -255,7 +220,7 @@ namespace jank::read::parse } auto const value(it.latest.unwrap().expect_ok()); - ret.insert_or_assign(key, value); + ret.insert_or_assign(key.unwrap().ptr, value.unwrap().ptr); } if(expected_closer.is_some()) { @@ -263,12 +228,14 @@ namespace jank::read::parse } expected_closer = prev_expected_closer; - return make_box(ret); + return object_source_info{ make_box(ret), + start_token, + latest_token }; } processor::object_result processor::parse_quote() { - auto const start_token(token_current.latest.unwrap().expect_ok()); + auto const start_token((*token_current).expect_ok()); ++token_current; auto const old_quoted(quoted); quoted = true; @@ -278,20 +245,22 @@ namespace jank::read::parse { return val_result; } - else if(val_result.expect_ok() == nullptr) + else if(val_result.expect_ok().is_none()) { return err(error{ start_token.pos, native_persistent_string{ "invalid value after quote" } }); } - return runtime::erase( - make_box(make_box("quote"), - val_result.expect_ok_move())); + return object_source_info{ runtime::erase(make_box( + make_box("quote"), + val_result.expect_ok().unwrap().ptr)), + start_token, + latest_token }; } processor::object_result processor::parse_nil() { ++token_current; - return ok(runtime::obj::nil::nil_const()); + return object_source_info{ runtime::obj::nil::nil_const(), latest_token, latest_token }; } processor::object_result processor::parse_boolean() @@ -299,7 +268,7 @@ namespace jank::read::parse auto const token((*token_current).expect_ok()); ++token_current; auto const b(boost::get(token.data)); - return ok(make_box(b)); + return object_source_info{ make_box(b), token, token }; } processor::object_result processor::parse_symbol() @@ -341,7 +310,7 @@ namespace jank::read::parse { name = sv; } - return ok(make_box(ns, name)); + return object_source_info{ make_box(ns, name), token, token }; } processor::object_result processor::parse_keyword() @@ -377,21 +346,26 @@ namespace jank::read::parse { return err(intern_res.expect_err()); } - return ok(intern_res.expect_ok()); + return object_source_info{ intern_res.expect_ok(), token, token }; } processor::object_result processor::parse_integer() { auto const token(token_current->expect_ok()); ++token_current; - return ok(make_box(boost::get(token.data))); + return object_source_info{ make_box( + boost::get(token.data)), + token, + token }; } processor::object_result processor::parse_real() { auto const token(token_current->expect_ok()); ++token_current; - return ok(make_box(boost::get(token.data))); + return object_source_info{ make_box(boost::get(token.data)), + token, + token }; } processor::object_result processor::parse_string() @@ -399,8 +373,10 @@ namespace jank::read::parse auto const token(token_current->expect_ok()); ++token_current; auto const sv(boost::get(token.data)); - return ok( - make_box(native_persistent_string{ sv.data(), sv.size() })); + return object_source_info{ make_box( + native_persistent_string{ sv.data(), sv.size() }), + token, + token }; } processor::object_result processor::parse_escaped_string() @@ -408,12 +384,14 @@ namespace jank::read::parse auto const token(token_current->expect_ok()); ++token_current; auto const sv(boost::get(token.data)); - auto res(detail::unescape({ sv.data(), sv.size() })); + auto res(util::unescape({ sv.data(), sv.size() })); if(res.is_err()) { return err(error{ token.pos, res.expect_err_move() }); } - return ok(make_box(res.expect_ok_move())); + return object_source_info{ make_box(res.expect_ok_move()), + token, + token }; } processor::iterator processor::begin() @@ -423,6 +401,6 @@ namespace jank::read::parse processor::iterator processor::end() { - return { some(ok(nullptr)), *this }; + return { some(ok(none)), *this }; } } diff --git a/src/cpp/jank/runtime/context.cpp b/src/cpp/jank/runtime/context.cpp index 98332af13..e4de502fc 100644 --- a/src/cpp/jank/runtime/context.cpp +++ b/src/cpp/jank/runtime/context.cpp @@ -197,7 +197,8 @@ namespace jank::runtime native_vector exprs{}; for(auto const &form : p_prc) { - auto const expr(an_prc.analyze(form.expect_ok(), analyze::expression_type::statement)); + auto const expr( + an_prc.analyze(form.expect_ok().unwrap().ptr, analyze::expression_type::statement)); ret = evaluate::eval(*this, jit_prc, expr.expect_ok()); exprs.emplace_back(expr.expect_ok()); } @@ -230,7 +231,8 @@ namespace jank::runtime native_vector ret{}; for(auto const &form : p_prc) { - auto const expr(an_prc.analyze(form.expect_ok(), analyze::expression_type::statement)); + auto const expr( + an_prc.analyze(form.expect_ok().unwrap().ptr, analyze::expression_type::statement)); if(eval) { evaluate::eval(*this, jit_prc, expr.expect_ok()); diff --git a/src/cpp/jank/util/escape.cpp b/src/cpp/jank/util/escape.cpp new file mode 100644 index 000000000..825baedc6 --- /dev/null +++ b/src/cpp/jank/util/escape.cpp @@ -0,0 +1,92 @@ +#include + +namespace jank::util +{ + string_result unescape(native_transient_string const &input) + { + native_transient_string ss; + ss.reserve(input.size()); + native_bool escape{}; + + for(auto const c : input) + { + if(!escape) + { + if(c == '\\') + { + escape = true; + } + else + { + ss += c; + } + } + else + { + switch(c) + { + case 'n': + ss += '\n'; + break; + case 't': + ss += '\t'; + break; + case 'r': + ss += '\r'; + break; + case '\\': + ss += '\\'; + break; + case '"': + ss += '"'; + break; + default: + return err(fmt::format("invalid escape sequence: \\{}", c)); + } + escape = false; + } + } + + return ok(std::move(ss)); + } + + native_transient_string escape(native_transient_string const &input) + { + native_transient_string ss; + /* We can expect on relocation, since escaping anything will result in a larger string. + * I'm not going to guess at the stats, to predict a better allocation, until this shows + * up in the profiler, though. */ + ss.reserve(input.size()); + + for(auto const c : input) + { + switch(c) + { + case '\n': + ss += '\\'; + ss += 'n'; + break; + case '\t': + ss += '\\'; + ss += 't'; + break; + case '\r': + ss += '\\'; + ss += 'r'; + break; + case '\\': + ss += '\\'; + ss += '\\'; + break; + case '"': + ss += '\\'; + ss += '"'; + break; + default: + ss += c; + } + } + + return ss; + } +} diff --git a/src/jank/clojure/core.jank b/src/jank/clojure/core.jank index 0fa10574d..01997bd09 100644 --- a/src/jank/clojure/core.jank +++ b/src/jank/clojure/core.jank @@ -5,19 +5,19 @@ ; Exceptions (def ex-info - (fn* [msg map] + (fn* ex-info [msg map] {:error msg :data map})) ; Relations. ;; Miscellaneous. (def nil? - (fn* [o] - (native/raw "__value = (o == obj::nil::nil_const()) ? #{ true }# : #{ false }#"))) + (fn* nil? [o] + (native/raw "__value = (o == obj::nil::nil_const()) ? ~{ true } : ~{ false }"))) ;; Collections. (def empty? - (fn* [o] + (fn* empty? [o] (if (nil? o) true (native/raw "__value = visit_object @@ -29,16 +29,16 @@ if constexpr(behavior::seqable) { return make_box(typed_o->seq() == nullptr); } else - { throw #{ (ex-info :not-seqable {:o o}) }#; } - }, - #{ o }# + { throw ~{ (ex-info :not-seqable {:o o}) }; } + }, + ~{ o } );")))) (def count - (fn* [o] + (fn* count [o] (if (nil? o) 0 - (native/raw "__value = make_box(runtime::detail::sequence_length(#{ o }#));")))) + (native/raw "__value = make_box(runtime::detail::sequence_length(~{ o }));")))) ; Lists. ; TODO: Can these be removed from the C++ source? @@ -61,9 +61,9 @@ ; return ret; ; } ; else -; { throw #{ (ex-info :not-seqable {:o o}) }#; } +; { throw ~{ (ex-info :not-seqable {:o o}) }; } ; }, -; #{ o }# +; ~{ o } ; );"))) ;(def fresh-seq ; (fn* fresh-seq [o] @@ -84,20 +84,20 @@ ; return ret; ; } ; else -; { throw #{ (ex-info :not-seqable {:o o}) }#; } +; { throw ~{ (ex-info :not-seqable {:o o}) }; } ; }, -; #{ o }# +; ~{ o } ; );"))) (def first (fn* first [o] - (native/raw "__value = jank::runtime::first(#{ o }#);"))) + (native/raw "__value = jank::runtime::first(~{ o });"))) ; Returns a fresh seq. (def next (fn* next [o] - (native/raw "__value = jank::runtime::next(#{ o }#);"))) + (native/raw "__value = jank::runtime::next(~{ o });"))) (def next-in-place (fn* next-in-place [o] - (native/raw "__value = jank::runtime::next_in_place(#{ o }#);"))) + (native/raw "__value = jank::runtime::next_in_place(~{ o });"))) (def nnext (fn* nnext [o] (next-in-place (next o)))) @@ -118,16 +118,16 @@ { auto const seq(typed_o->seq()); if(!seq) - { return #{ '() }#; } + { return ~{ '() }; } auto const ret(seq->next()); if(ret == nullptr) - { return #{ '() }#; } + { return ~{ '() }; } return ret; } else - { throw #{ (ex-info :not-seqable {:o o}) }#; } + { throw ~{ (ex-info :not-seqable {:o o}) }; } }, - #{ o }# + ~{ o } );")))) (def cons (fn* cons [head tail] @@ -138,17 +138,17 @@ using T = typename decltype(typed_tail)::value_type; if constexpr(behavior::seqable) - { return make_box(#{ head }#, typed_tail->seq()); } + { return make_box(~{ head }, typed_tail->seq()); } else - { throw #{ (ex-info :invalid-cons-tail {:head head :tail tail}) }#; } + { throw ~{ (ex-info :invalid-cons-tail {:head head :tail tail}) }; } }, - #{ tail }# + ~{ tail } );"))) (def list? (fn* list? [o] ; TODO: Visit and use a behavior for this check instead. It should apply to conses and others. - (native/raw "__value = make_box(#{ o }#->type == object_type::persistent_list);"))) + (native/raw "__value = make_box(~{ o }->type == object_type::persistent_list);"))) (def list (fn* list [& items] @@ -161,15 +161,15 @@ if constexpr(behavior::seqable) { return jank::runtime::obj::persistent_list::create(typed_items); } else - { throw #{ (ex-info :invalid-seq {:items items}) }#; } + { throw ~{ (ex-info :invalid-seq {:items items}) }; } }, - #{ items }# + ~{ items } );"))) ;; Vectors. (def vector? (fn* vector? [o] - (native/raw "__value = make_box(#{ o }#->type == object_type::persistent_vector);"))) + (native/raw "__value = make_box(~{ o }->type == object_type::persistent_vector);"))) (def vec (fn* vec [coll] @@ -182,9 +182,9 @@ if constexpr(behavior::seqable) { return jank::runtime::obj::persistent_vector::create(typed_coll); } else - { throw #{ (ex-info :invalid-seq {:coll coll}) }#; } + { throw ~{ (ex-info :invalid-seq {:coll coll}) }; } }, - #{ coll }# + ~{ coll } );"))) (def conj) @@ -195,7 +195,7 @@ ([coll] coll) ([coll x] - (native/raw "__value = jank::runtime::conj(#{ coll }#, #{ x }#);")) + (native/raw "__value = jank::runtime::conj(~{ coll }, ~{ x });")) ([coll x & args] (let* [res (conj coll x)] (if (empty? args) @@ -218,13 +218,13 @@ if constexpr(behavior::metadatable) { if(typed_o->meta.is_none()) - { return #{ nil }#; } + { return ~{ nil }; } return typed_o->meta.unwrap(); } else - { return #{ nil }#; } + { return ~{ nil }; } }, - #{ o }# + ~{ o } );"))) (def with-meta (fn* with-meta [o m] @@ -235,11 +235,11 @@ using T = typename decltype(typed_o)::value_type; if constexpr(behavior::metadatable) - { return typed_o->with_meta(#{ m }#); } + { return typed_o->with_meta(~{ m }); } else - { throw #{ (ex-info :not-metadatable {:o o}) }#; } + { throw ~{ (ex-info :not-metadatable {:o o}) }; } }, - #{ o }# + ~{ o } );"))) (def reset-meta! (fn* reset-meta! [o m] @@ -251,14 +251,14 @@ if constexpr(behavior::metadatable) { - auto const meta(behavior::detail::validate_meta(#{ m }#)); + auto const meta(behavior::detail::validate_meta(~{ m })); typed_o->meta = meta; - return #{ m }#; + return ~{ m }; } else - { throw #{ (ex-info :not-metadatable {:o o}) }#; } + { throw ~{ (ex-info :not-metadatable {:o o}) }; } }, - #{ o }# + ~{ o } );"))) ;; Macros. @@ -277,7 +277,7 @@ ([] (native/raw "__value = make_box(runtime::context::unique_symbol());")) ([prefix] - (native/raw "__value = make_box(runtime::context::unique_symbol(runtime::detail::to_string(#{ prefix }#)));"))) + (native/raw "__value = make_box(runtime::context::unique_symbol(runtime::detail::to_string(~{ prefix })));"))) ; TODO: This doesn't support macros with overloads. (defn defmacro [&form &env fn-name fn-args & body] @@ -307,7 +307,7 @@ ; TODO: Higher arities. Needs clojure.core/spread, which needs clojure.core/cons. (defn apply* [f args] - (native/raw "__value = runtime::apply_to(#{ f }#, #{ args }#);")) + (native/raw "__value = runtime::apply_to(~{ f }, ~{ args });")) (defn or* ([] @@ -378,23 +378,23 @@ [& body]) (defn macroexpand-1 [form] - (native/raw "__value = __rt_ctx.macroexpand1(#{ form }#);")) + (native/raw "__value = __rt_ctx.macroexpand1(~{ form });")) (defn macroexpand [form] - (native/raw "__value = __rt_ctx.macroexpand(#{ form }#);")) + (native/raw "__value = __rt_ctx.macroexpand(~{ form });")) ; Relations. ;; Miscellaneous. (defn true? [o] - (native/raw "__value = runtime::detail::equal(#{ o }#, #{ true }#) ? #{ true }# : #{ false }#")) + (native/raw "__value = runtime::detail::equal(~{ o }, ~{ true }) ? ~{ true } : ~{ false }")) (defn false? [o] - (native/raw "__value = runtime::detail::equal(#{ o }#, #{ false }#) ? #{ true }# : #{ false }#")) + (native/raw "__value = runtime::detail::equal(~{ o }, ~{ false }) ? ~{ true } : ~{ false }")) (defn not [o] (native/raw "if(o == obj::nil::nil_const()) - { __value = #{ true }#; } + { __value = ~{ true }; } else - { __value = runtime::detail::equal(#{ o }#, #{ false }#) ? #{ true }# : #{ false }#; }")) + { __value = runtime::detail::equal(~{ o }, ~{ false }) ? ~{ true } : ~{ false }; }")) (defn some? [o] - (native/raw "__value = (o == obj::nil::nil_const()) ? #{ false }# : #{ true }#")) + (native/raw "__value = (o == obj::nil::nil_const()) ? ~{ false } : ~{ true }")) ; Functions. @@ -421,15 +421,15 @@ ; Applies fn f to the argument list formed by prepending intervening arguments to args. (defn apply ([f args] - (native/raw "__value = runtime::apply_to(#{ f }#, #{ args }#);")) + (native/raw "__value = runtime::apply_to(~{ f }, ~{ args });")) ([f x args] - (native/raw "__value = runtime::apply_to(#{ f }#, #{ (list* x args) }#);")) + (native/raw "__value = runtime::apply_to(~{ f }, ~{ (list* x args) });")) ([f x y args] - (native/raw "__value = runtime::apply_to(#{ f }#, #{ (list* x y args) }#);")) + (native/raw "__value = runtime::apply_to(~{ f }, ~{ (list* x y args) });")) ([f x y z args] - (native/raw "__value = runtime::apply_to(#{ f }#, #{ (list* x y z args) }#);")) + (native/raw "__value = runtime::apply_to(~{ f }, ~{ (list* x y z args) });")) ([f a b c d & args] - (native/raw "__value = runtime::apply_to(#{ f }#, #{ (cons a (cons b (cons c (cons d (spread args))))) }#);"))) + (native/raw "__value = runtime::apply_to(~{ f }, ~{ (cons a (cons b (cons c (cons d (spread args))))) });"))) ; Takes a fn f and returns a fn that takes the same arguments as f, ; has the same effects, if any, and returns the opposite truth value. @@ -456,25 +456,25 @@ if constexpr(behavior::seqable) { - object_ptr res{ #{ val }# }; + object_ptr res{ ~{ val } }; for(auto it(typed_coll->fresh_seq()); it != nullptr; it = it->next_in_place()) - { res = dynamic_call(#{ f }#, res, it->first()); } + { res = dynamic_call(~{ f }, res, it->first()); } return res; } else - { throw #{ (ex-info :invalid-seq {:coll coll}) }#; } + { throw ~{ (ex-info :invalid-seq {:coll coll}) }; } }, - #{ coll }# + ~{ coll } );"))) ; Strings. (defn string? [o] - (native/raw "__value = make_box(#{ o }#->type == object_type::persistent_string);")) + (native/raw "__value = make_box(~{ o }->type == object_type::persistent_string);")) (defn str ([] "") ([o] - (native/raw "__value = make_box(runtime::detail::to_string(#{ o }#));")) + (native/raw "__value = make_box(runtime::detail::to_string(~{ o }));")) ([o & args] (native/raw "__value = visit_object ( @@ -486,7 +486,7 @@ { fmt::memory_buffer buff; buff.reserve(16); - runtime::detail::to_string(#{ o }#, buff); + runtime::detail::to_string(~{ o }, buff); if(0 < runtime::detail::sequence_length(typed_args)) { auto const fresh(typed_args->fresh_seq()); @@ -497,9 +497,9 @@ return make_box(native_persistent_string{ buff.data(), buff.size() }); } else - { throw #{ (ex-info :invalid-seq {:args args}) }#; } + { throw ~{ (ex-info :invalid-seq {:args args}) }; } }, - #{ args }# + ~{ args } );"))) ; TODO: Proper version. @@ -514,11 +514,11 @@ using T = typename decltype(typed_s)::value_type; if constexpr(std::is_same_v) - { return typed_s->substring(to_int(#{ start }#)).expect_ok(); } + { return typed_s->substring(to_int(~{ start })).expect_ok(); } else - { throw #{ (ex-info :not-a-string {:s s}) }#; } + { throw ~{ (ex-info :not-a-string {:s s}) }; } }, - #{ s }# + ~{ s } );")) ([s start end] (native/raw "__value = visit_object @@ -528,11 +528,11 @@ using T = typename decltype(typed_s)::value_type; if constexpr(std::is_same_v) - { return typed_s->substring(to_int(#{ start }#), to_int(#{ end }#)).expect_ok(); } + { return typed_s->substring(to_int(~{ start }), to_int(~{ end })).expect_ok(); } else - { throw #{ (ex-info :not-a-string {:s s}) }#; } + { throw ~{ (ex-info :not-a-string {:s s}) }; } }, - #{ s }# + ~{ s } );"))) (defn- first-index-of [s c] @@ -545,11 +545,11 @@ using T = typename decltype(typed_s)::value_type; if constexpr(std::is_same_v) - { return typed_s->first_index_of(#{ c }#); } + { return typed_s->first_index_of(~{ c }); } else - { throw #{ (ex-info :not-a-string {:s s}) }#; } + { throw ~{ (ex-info :not-a-string {:s s}) }; } }, - #{ s }# + ~{ s } ) );")) (defn- last-index-of [s c] @@ -562,11 +562,11 @@ using T = typename decltype(typed_s)::value_type; if constexpr(std::is_same_v) - { return typed_s->last_index_of(#{ c }#); } + { return typed_s->last_index_of(~{ c }); } else - { throw #{ (ex-info :not-a-string {:s s}) }#; } + { throw ~{ (ex-info :not-a-string {:s s}) }; } }, - #{ s }# + ~{ s } ) );")) @@ -585,7 +585,7 @@ else { return false; } }, - #{ o }# + ~{ o } ) );")) @@ -674,7 +674,7 @@ ([] {}) ([& kvs] - (native/raw "__value = obj::persistent_hash_map::create_from_seq(#{ kvs }#);"))) + (native/raw "__value = obj::persistent_hash_map::create_from_seq(~{ kvs });"))) (defn keys [m] ; TODO: Use a proper key seq instead. @@ -692,18 +692,18 @@ (defn get ([m k] - (native/raw "__value = jank::runtime::get(#{ m }#, #{ k }#);")) + (native/raw "__value = jank::runtime::get(~{ m }, ~{ k });")) ([m k fallback] - (native/raw "__value = jank::runtime::get(#{ m }#, #{ k }#, #{ fallback }#);"))) + (native/raw "__value = jank::runtime::get(~{ m }, ~{ k }, ~{ fallback });"))) (defn get-in ([m ks] - (native/raw "__value = jank::runtime::get_in(#{ m }#, #{ ks }#);")) + (native/raw "__value = jank::runtime::get_in(~{ m }, ~{ ks });")) ([m ks fallback] - (native/raw "__value = jank::runtime::get_in(#{ m }#, #{ ks }#, #{ fallback }#);"))) + (native/raw "__value = jank::runtime::get_in(~{ m }, ~{ ks }, ~{ fallback });"))) (defn assoc ([map key val] - (native/raw "__value = jank::runtime::assoc(#{ map }#, #{ key }#, #{ val }#);")) + (native/raw "__value = jank::runtime::assoc(~{ map }, ~{ key }, ~{ val });")) ([map key val & kvs] (let [res (assoc map key val)] (if (empty? kvs) @@ -718,16 +718,16 @@ ; range of indexes. 'contains?' operates constant or logarithmic time; ; it will not perform a linear search for a value. See also 'some'. (defn contains? [coll k] - (native/raw "__value = make_box(jank::runtime::contains(#{ coll }#, #{ k }#));")) + (native/raw "__value = make_box(jank::runtime::contains(~{ coll }, ~{ k }));")) ; Returns the map entry for key, or nil if key not present. (defn find [coll k] - (native/raw "__value = jank::runtime::find(#{ coll }#, #{ k }#);")) + (native/raw "__value = jank::runtime::find(~{ coll }, ~{ k });")) ; Returns a map containing only those entries in map whose key is in keys (defn select-keys [m ks] (reduce* (fn [acc k] - (let [e (native/raw "__value = jank::runtime::find(#{ m }#, #{ k }#);")] + (let [e (native/raw "__value = jank::runtime::find(~{ m }, ~{ k });")] (if e (conj acc e) acc))) @@ -746,7 +746,7 @@ ;; Sets. (defn set? [o] - (native/raw "__value = make_box(#{ o }#->type == object_type::persistent_set);")) + (native/raw "__value = make_box(~{ o }->type == object_type::persistent_set);")) ; Returns a set of the distinct elements of coll. (defn set [coll] @@ -760,7 +760,7 @@ ;; Other. (defn hash [o] - (native/raw "__value = make_box(hash::visit(#{ o }#));")) + (native/raw "__value = make_box(hash::visit(~{ o }));")) (defn name [o] (if (string? o) @@ -776,9 +776,9 @@ if constexpr(behavior::nameable) { return typed_o->get_name(); } else - { throw #{ (ex-info :not-nameable {:o o}) }#; } + { throw ~{ (ex-info :not-nameable {:o o}) }; } }, - #{ o }# + ~{ o } ) );"))) @@ -794,9 +794,9 @@ if constexpr(behavior::nameable) { return typed_o->get_namespace(); } else - { throw #{ (ex-info :not-nameable {:o o}) }#; } + { throw ~{ (ex-info :not-nameable {:o o}) }; } }, - #{ o }# + ~{ o } ) );")) @@ -808,9 +808,9 @@ ([x] x) ([l r] - (native/raw "__value = add(#{ l }#, #{ r }#);")) + (native/raw "__value = add(~{ l }, ~{ r });")) ([l r & args] - (let [res (native/raw "__value = add(#{ l }#, #{ r }#);")] + (let [res (native/raw "__value = add(~{ l }, ~{ r });")] (if (empty? args) res (recur res (first args) (next args)))))) @@ -821,9 +821,9 @@ ([x] (- 0 x)) ([l r] - (native/raw "__value = sub(#{ l }#, #{ r }#);")) + (native/raw "__value = sub(~{ l }, ~{ r });")) ([l r & args] - (let [res (native/raw "__value = sub(#{ l }#, #{ r }#);")] + (let [res (native/raw "__value = sub(~{ l }, ~{ r });")] (if (empty? args) res (recur res (first args) (next args)))))) @@ -836,9 +836,9 @@ ([x] x) ([l r] - (native/raw "__value = mul(#{ l }#, #{ r }#);")) + (native/raw "__value = mul(~{ l }, ~{ r });")) ([l r & args] - (let [res (native/raw "__value = mul(#{ l }#, #{ r }#);")] + (let [res (native/raw "__value = mul(~{ l }, ~{ r });")] (if (empty? args) res (recur res (first args) (next args)))))) @@ -854,9 +854,9 @@ ([x] (/ 1 x)) ([l r] - (native/raw "__value = div(#{ l }#, #{ r }#);")) + (native/raw "__value = div(~{ l }, ~{ r });")) ([l r & args] - (let [res (native/raw "__value = div(#{ l }#, #{ r }#);")] + (let [res (native/raw "__value = div(~{ l }, ~{ r });")] (if (empty? args) res (recur res (first args) (next args))))))) @@ -867,12 +867,12 @@ ([x] true) ([l r] - (native/raw "__value = make_box(runtime::detail::equal(#{ l }#, #{ r }#));")) + (native/raw "__value = make_box(runtime::detail::equal(~{ l }, ~{ r }));")) ([l r & args] - (if (native/raw "__value = make_box(runtime::detail::equal(#{ l }#, #{ r }#));") + (if (native/raw "__value = make_box(runtime::detail::equal(~{ l }, ~{ r }));") (if (next args) (recur r (first args) (next args)) - (native/raw "__value = make_box(runtime::detail::equal(#{ r }#, #{ (first args) }#));")) + (native/raw "__value = make_box(runtime::detail::equal(~{ r }, ~{ (first args) }));")) false))) (defn not= @@ -887,12 +887,12 @@ ([x] true) ([l r] - (native/raw "__value = make_box(lt(#{ l }#, #{ r }#));")) + (native/raw "__value = make_box(lt(~{ l }, ~{ r }));")) ([l r & args] - (if (native/raw "__value = make_box(lt(#{ l }#, #{ r }#));") + (if (native/raw "__value = make_box(lt(~{ l }, ~{ r }));") (if (next args) (recur r (first args) (next args)) - (native/raw "__value = make_box(lt(#{ r }#, #{ (first args) }#));")) + (native/raw "__value = make_box(lt(~{ r }, ~{ (first args) }));")) false))) (reset-meta! (var <) {:arities {2 {:supports-unboxed-input? true :unboxed-output? true}}}) @@ -901,12 +901,12 @@ ([x] true) ([l r] - (native/raw "__value = make_box(lte(#{ l }#, #{ r }#));")) + (native/raw "__value = make_box(lte(~{ l }, ~{ r }));")) ([l r & args] - (if (native/raw "__value = make_box(lte(#{ l }#, #{ r }#));") + (if (native/raw "__value = make_box(lte(~{ l }, ~{ r }));") (if (next args) (recur r (first args) (next args)) - (native/raw "__value = make_box(lte(#{ r }#, #{ (first args) }#));")) + (native/raw "__value = make_box(lte(~{ r }, ~{ (first args) }));")) false))) (reset-meta! (var <=) {:arities {2 {:supports-unboxed-input? true :unboxed-output? true}}}) @@ -915,12 +915,12 @@ ([x] true) ([l r] - (native/raw "__value = make_box(lt(#{ r }#, #{ l }#));")) + (native/raw "__value = make_box(lt(~{ r }, ~{ l }));")) ([l r & args] - (if (native/raw "__value = make_box(lt(#{ r }#, #{ l }#));") + (if (native/raw "__value = make_box(lt(~{ r }, ~{ l }));") (if (next args) (recur r (first args) (next args)) - (native/raw "__value = make_box(lt(#{ (first args) }#, #{ r }#));")) + (native/raw "__value = make_box(lt(~{ (first args) }, ~{ r }));")) false))) (reset-meta! (var >) {:arities {2 {:supports-unboxed-input? true :unboxed-output? true}}}) @@ -929,12 +929,12 @@ ([x] true) ([l r] - (native/raw "__value = make_box(lte(#{ r }#, #{ l }#));")) + (native/raw "__value = make_box(lte(~{ r }, ~{ l }));")) ([l r & args] - (if (native/raw "__value = make_box(lte(#{ r }#, #{ l }#));") + (if (native/raw "__value = make_box(lte(~{ r }, ~{ l }));") (if (next args) (recur r (first args) (next args)) - (native/raw "__value = make_box(lte(#{ (first args) }#, #{ r }#));")) + (native/raw "__value = make_box(lte(~{ (first args) }, ~{ r }));")) false))) (reset-meta! (var >=) {:arities {2 {:supports-unboxed-input? true :unboxed-output? true}}}) @@ -943,9 +943,9 @@ ([x] x) ([l r] - (native/raw "__value = min(#{ l }#, #{ r }#);")) + (native/raw "__value = min(~{ l }, ~{ r });")) ([l r & args] - (let [res (native/raw "__value = min(#{ l }#, #{ r }#);")] + (let [res (native/raw "__value = min(~{ l }, ~{ r });")] (if (empty? args) res (recur res (first args) (next args)))))) @@ -956,9 +956,9 @@ ([x] x) ([l r] - (native/raw "__value = max(#{ l }#, #{ r }#);")) + (native/raw "__value = max(~{ l }, ~{ r });")) ([l r & args] - (let [res (native/raw "__value = max(#{ l }#, #{ r }#);")] + (let [res (native/raw "__value = max(~{ l }, ~{ r });")] (if (empty? args) res (recur res (first args) (next args)))))) @@ -966,19 +966,19 @@ :unboxed-output? true}}}) (defn inc [n] - (native/raw "__value = inc(#{ n }#);")) + (native/raw "__value = inc(~{ n });")) (defn dec [n] - (native/raw "__value = dec(#{ n }#);")) + (native/raw "__value = dec(~{ n });")) (defn pos? [n] - (native/raw "__value = make_box(is_pos(#{ n }#));")) + (native/raw "__value = make_box(is_pos(~{ n }));")) (defn neg? [n] - (native/raw "__value = make_box(is_neg(#{ n }#));")) + (native/raw "__value = make_box(is_neg(~{ n }));")) (defn zero? [n] - (native/raw "__value = make_box(is_zero(#{ n }#));")) + (native/raw "__value = make_box(is_zero(~{ n }));")) (defn rem [num div] - (native/raw "__value = rem(#{ num }#, #{ div }#);")) + (native/raw "__value = rem(~{ num }, ~{ div });")) (defn mod [num div] (let [m (rem num div)] ; TODO: Could use an or here, if we had it. @@ -990,11 +990,11 @@ ;; Numbers. (defn integer? [o] - (native/raw "__value = make_box(#{ o }#->type == object_type::integer)")) + (native/raw "__value = make_box(~{ o }->type == object_type::integer)")) (defn float? [o] - (native/raw "__value = make_box(#{ o }#->type == object_type::real)")) + (native/raw "__value = make_box(~{ o }->type == object_type::real)")) (defn boolean? [o] - (native/raw "__value = make_box(#{ o }#->type == object_type::boolean)")) + (native/raw "__value = make_box(~{ o }->type == object_type::boolean)")) (defn number? [o] (native/raw "__value = make_box ( @@ -1009,12 +1009,12 @@ else { return false; } }, - #{ o }# + ~{ o } ) );")) (defn int [o] - (native/raw "__value = make_box(to_int(#{ o }#));")) + (native/raw "__value = make_box(to_int(~{ o }));")) (defn float [o] (native/raw "__value = make_box ( @@ -1027,9 +1027,9 @@ if constexpr(behavior::numberable) { return typed_o->to_real(); } else - { throw #{ (ex-info :not-a-number {:o o}) }#; } + { throw ~{ (ex-info :not-a-number {:o o}) }; } }, - #{ o }# + ~{ o } ) );")) @@ -1045,9 +1045,9 @@ if constexpr(std::is_same_v) { return typed_o->data % 2 == 0; } else - { throw #{ (ex-info :not-an-integer {:o o}) }#; } + { throw ~{ (ex-info :not-an-integer {:o o}) }; } }, - #{ o }# + ~{ o } ) );")) (defn odd? [o] @@ -1062,9 +1062,9 @@ if constexpr(std::is_same_v) { return typed_o->data % 2 == 1; } else - { throw #{ (ex-info :not-an-integer {:o o}) }#; } + { throw ~{ (ex-info :not-an-integer {:o o}) }; } }, - #{ o }# + ~{ o } ) );")) @@ -1109,7 +1109,7 @@ ;; Vars. (defn var? [o] - (native/raw "__value = make_box(#{ o }#->type == object_type::var)")) + (native/raw "__value = make_box(~{ o }->type == object_type::var)")) ; TODO: Make this private. (defmacro assert-macro-args @@ -1144,7 +1144,7 @@ ; (finally ; (pop-thread-bindings))) (defn push-thread-bindings [bindings] - (native/raw "__rt_ctx.push_thread_bindings(#{ bindings }#).expect_ok();")) + (native/raw "__rt_ctx.push_thread_bindings(~{ bindings }).expect_ok();")) ; Pop one set of bindings pushed with push-binding before. It is an error to ; pop bindings without pushing before. @@ -1190,10 +1190,10 @@ (defn with-redefs-fn [binding-map fun] (let [root-bind (fn [m] (doseq [kv m] - (native/raw "expect_object(#{ (first kv) }#)->bind_root(#{ (second kv) }#);"))) + (native/raw "expect_object(~{ (first kv) })->bind_root(~{ (second kv) });"))) old-vals (zipmap (keys binding-map) (map (fn [v] - (native/raw "__value = expect_object(#{ v }#)->get_root();")) + (native/raw "__value = expect_object(~{ v })->get_root();")) (keys binding-map)))] ; TODO: try/finally (do ;try @@ -1221,7 +1221,7 @@ ;; Keywords. (defn keyword? [o] - (native/raw "__value = make_box(#{ o }#->type == object_type::keyword)")) + (native/raw "__value = make_box(~{ o }->type == object_type::keyword)")) (defn simple-keyword? [o] (native/raw "__value = make_box ( @@ -1236,7 +1236,7 @@ else { return false; } }, - #{ o }# + ~{ o } ) );")) (defn qualified-keyword? [o] @@ -1253,7 +1253,7 @@ else { return false; } }, - #{ o }# + ~{ o } ) );")) (defn simple-symbol? [o] @@ -1270,7 +1270,7 @@ else { return false; } }, - #{ o }# + ~{ o } ) );")) (defn qualified-symbol? [o] @@ -1287,11 +1287,11 @@ else { return false; } }, - #{ o }# + ~{ o } ) );")) (defn symbol? [o] - (native/raw "__value = make_box(#{ o }#->type == object_type::symbol)")) + (native/raw "__value = make_box(~{ o }->type == object_type::symbol)")) ; Returns a Symbol with the given namespace and name. Arity-1 works ; on strings, keywords, and vars. @@ -1300,12 +1300,12 @@ (cond (symbol? o) o ; TODO: Intern. - (string? o) (native/raw "__value = make_box(runtime::detail::to_string(#{ o }#));") - (var? o) (native/raw "__value = expect_object(#{ o }#)->name;") - (keyword? o) (native/raw "__value = make_box(expect_object(#{ o }#)->sym) ;") - :else (native/raw "throw #{ (ex-info :cannot-convert-to-symbol {:o o}) }#;"))) + (string? o) (native/raw "__value = make_box(runtime::detail::to_string(~{ o }));") + (var? o) (native/raw "__value = expect_object(~{ o })->name;") + (keyword? o) (native/raw "__value = make_box(expect_object(~{ o })->sym) ;") + :else (native/raw "throw ~{ (ex-info :cannot-convert-to-symbol {:o o}) };"))) ([ns o] - (native/raw "__value = make_box(runtime::detail::to_string(#{ ns }#), runtime::detail::to_string(#{ o }#));"))) + (native/raw "__value = make_box(runtime::detail::to_string(~{ ns }), runtime::detail::to_string(~{ o }));"))) ;; Sequences. (defn iterate [f x] @@ -1316,22 +1316,22 @@ using T = typename decltype(typed_f)::value_type; if constexpr(std::is_base_of_v) - { return make_box(typed_f, #{ x }#); } + { return make_box(typed_f, ~{ x }); } else - { throw #{ (ex-info :not-callable {:f f}) }#; } + { throw ~{ (ex-info :not-callable {:f f}) }; } }, - #{ f }# + ~{ f } );")) (defn range ([] (iterate inc 0)) ([end] - (native/raw "__value = make_box(#{ end }#);")) + (native/raw "__value = make_box(~{ end });")) ([start end] - (native/raw "__value = make_box(#{ start }#, #{ end }#);")) + (native/raw "__value = make_box(~{ start }, ~{ end });")) ([start end step] - (native/raw "__value = make_box(#{ start }#, #{ end }#, #{ step }#);"))) + (native/raw "__value = make_box(~{ start }, ~{ end }, ~{ step });"))) (defn take ; TODO: Transducer. @@ -1393,10 +1393,10 @@ ; TODO: Move println back into here once I sort out two things: ; 1. Escaped strings, so I can do the space in between each ; 2. The linker errors showing up when using fmt::format_to here - (native/raw "__value = jank::runtime::context::println(#{ args }#);")) + (native/raw "__value = jank::runtime::context::println(~{ args });")) (defn print [o] - (native/raw "__value = jank::runtime::context::print(#{ o }#);")) + (native/raw "__value = jank::runtime::context::print(~{ o });")) ;; Functions. (defn ifn? [o] @@ -1413,14 +1413,14 @@ else { return false; } }, - #{ o }# + ~{ o } ) );")) (defn fn? [o] (native/raw "auto const fn ( - #{ o }#->type == object_type::native_function_wrapper - || #{ o }#->type == object_type::jit_function + ~{ o }->type == object_type::native_function_wrapper + || ~{ o }->type == object_type::jit_function ); __value = make_box(fn);")) @@ -1431,7 +1431,7 @@ ;; jank.compiler things. ; TODO: Options, following what criterium offers. (defn benchmark [label fun] - (native/raw "auto const label_str(expect_object(#{ (str label) }#)); + (native/raw "auto const label_str(expect_object(~{ (str label) })); visit_object ( [=](auto const typed_fun) @@ -1465,27 +1465,27 @@ ); } else - { throw #{ (ex-info :not-callable {:fun fun}) }#; } + { throw ~{ (ex-info :not-callable {:fun fun}) }; } }, - #{ fun }# + ~{ fun } );")) ;; Extra things that usually come from Java. (defn tan [o] - (native/raw "__value = make_box(std::tan(runtime::detail::to_real(#{ o }#)));")) + (native/raw "__value = make_box(std::tan(runtime::detail::to_real(~{ o })));")) (defn sqrt [o] - (native/raw "__value = make_box(std::sqrt(runtime::detail::to_real(#{ o }#)));")) + (native/raw "__value = make_box(std::sqrt(runtime::detail::to_real(~{ o })));")) (reset-meta! (var sqrt) {:arities {1 {:supports-unboxed-input? true :unboxed-output? true}}}) (defn abs [o] - (native/raw "__value = abs(#{ o }#);")) + (native/raw "__value = abs(~{ o });")) (reset-meta! (var abs) {:arities {1 {:supports-unboxed-input? true :unboxed-output? true}}}) (defn pow [x y] - (native/raw "__value = make_box(std::pow(runtime::detail::to_real(#{ x }#), runtime::detail::to_real(#{ y }#)));")) + (native/raw "__value = make_box(std::pow(runtime::detail::to_real(~{ x }), runtime::detail::to_real(~{ y })));")) (reset-meta! (var pow) {:arities {2 {:supports-unboxed-input? true :unboxed-output? true}}}) @@ -1497,44 +1497,44 @@ ; Vars. (defn var? [o] - (native/raw "__value = make_box(#{ o }#->type == object_type::var);")) + (native/raw "__value = make_box(~{ o }->type == object_type::var);")) (defn var-get [v] (assert (var? v)) - (native/raw "__value = expect_object(#{ v }#)->deref();")) + (native/raw "__value = expect_object(~{ v })->deref();")) ; Namespaces (again). (defn create-ns [sym] (assert (symbol? sym)) - (native/raw "__value = __rt_ctx.intern_ns(expect_object(#{ sym }#));")) + (native/raw "__value = __rt_ctx.intern_ns(expect_object(~{ sym }));")) (defn find-ns [sym] (assert (symbol? sym)) - (native/raw "__value = __rt_ctx.find_ns(expect_object(#{ sym }#)).unwrap_or(nullptr); + (native/raw "__value = __rt_ctx.find_ns(expect_object(~{ sym })).unwrap_or(nullptr); if(!__value) { __value = obj::nil::nil_const(); }")) (defn remove-ns [sym] (assert (symbol? sym)) - (native/raw "__value = __rt_ctx.remove_ns(expect_object(#{ sym }#)).unwrap_or(nullptr); + (native/raw "__value = __rt_ctx.remove_ns(expect_object(~{ sym })).unwrap_or(nullptr); if(!__value) { __value = obj::nil::nil_const(); }")) (defn the-ns [ns-or-sym] - (if (native/raw "__value = make_box(#{ ns-or-sym }#->type == object_type::ns);") + (if (native/raw "__value = make_box(~{ ns-or-sym }->type == object_type::ns);") ns-or-sym (let [found (find-ns ns-or-sym)] (if (nil? found) - (native/raw "throw #{ (ex-info :not-an-ns-or-sym {:value ns-or-sym}) }#;") + (native/raw "throw ~{ (ex-info :not-an-ns-or-sym {:value ns-or-sym}) };") found)))) (defn ns-name [ns-sym] (let [ns (the-ns ns-sym)] - (native/raw "__value = expect_object(#{ ns }#)->name;"))) + (native/raw "__value = expect_object(~{ ns })->name;"))) (defn ns-map [ns-sym] (let [ns (the-ns ns-sym)] - (native/raw "__value = expect_object(#{ ns }#)->get_mappings();"))) + (native/raw "__value = expect_object(~{ ns })->get_mappings();"))) (defn ns-publics [ns-sym] (let [ns (the-ns ns-sym)] @@ -1545,7 +1545,7 @@ v (second kv)] ; TODO: Check for visibility. (if (var? v) - (if (native/raw "__value = make_box(#{ ns }# == expect_object(#{ v }#)->n);") + (if (native/raw "__value = make_box(~{ ns } == expect_object(~{ v })->n);") (assoc acc k v) acc) acc))) @@ -1568,21 +1568,21 @@ (defn- throw-if [pred msg] (when pred - (native/raw "throw #{ (ex-info :assertion-failure {:msg msg }) }#;"))) + (native/raw "throw ~{ (ex-info :assertion-failure {:msg msg }) };"))) (defn alias [alias ns-sym] (let [ns-obj (the-ns ns-sym)] (assert (symbol? alias)) - (native/raw "expect_object(#{ *ns* }#)->add_alias + (native/raw "expect_object(~{ *ns* })->add_alias ( - expect_object(#{ alias }#), - expect_object(#{ ns-obj }#) + expect_object(~{ alias }), + expect_object(~{ ns-obj }) ).expect_ok();"))) (defn refer [ns-sym & filters] (let [ns (find-ns ns-sym) _ (when (nil? ns) - (native/raw "throw #{ (ex-info :unknown-namespace {:value ns-sym}) }#;")) + (native/raw "throw ~{ (ex-info :unknown-namespace {:value ns-sym}) };")) filters (apply hash-map filters) rename (or (:rename filters) {}) exclude? (set (:exclude filters)) @@ -1596,11 +1596,11 @@ (let [v (get sym->var sym) sym (rename sym sym)] (when (nil? v) - (native/raw "throw #{ (ex-info :var-does-not-exist {:value sym}) }#;")) - (native/raw "expect_object(#{ *ns* }#)->refer + (native/raw "throw ~{ (ex-info :var-does-not-exist {:value sym}) };")) + (native/raw "expect_object(~{ *ns* })->refer ( - expect_object(#{ sym }#), - expect_object(#{ v }#) + expect_object(~{ sym }), + expect_object(~{ v }) ).expect_ok();")))) nil to-refer))) @@ -1610,7 +1610,7 @@ nil (let [path (first paths)] ; TODO: Check for cyclic deps, once we have dynamic vars. - (native/raw "__rt_ctx.load_module(runtime::detail::to_string(#{ path }#)).expect_ok();") + (native/raw "__rt_ctx.load_module(runtime::detail::to_string(~{ path })).expect_ok();") (recur (rest paths))))) ; Loads a lib given its name. If `need-ns?`, ensures that the associated @@ -1653,7 +1653,7 @@ as-alias (:as-alias opts) ; TODO: Dynamic var ;loaded (contains? @*loaded-libs* lib) - loaded? (native/raw "__value = make_box(__rt_ctx.module_loader.is_loaded(runtime::detail::to_string(#{ lib }#)));") + loaded? (native/raw "__value = make_box(__rt_ctx.module_loader.is_loaded(runtime::detail::to_string(~{ lib })));") need-ns? (or as use) load (cond reload-all load-all reload load-one @@ -1704,7 +1704,7 @@ (apply load-lib (prependss arg opts))))) (defn compile [path] - (native/raw "__rt_ctx.compile_module(runtime::detail::to_string(#{ path }#)).expect_ok();")) + (native/raw "__rt_ctx.compile_module(runtime::detail::to_string(~{ path })).expect_ok();")) (defn require [& args] (apply load-libs :require args)) @@ -1737,7 +1737,7 @@ (list 'if (list '= (list 'quote name) (list 'quote 'clojure.core)) 'nil (list 'let (vector 'name (list 'quote name)) - (list 'native/raw "__rt_ctx.module_loader.set_loaded(runtime::detail::to_string(#{ name }#));") + (list 'native/raw "__rt_ctx.module_loader.set_loaded(runtime::detail::to_string(~{ name }));") ; TODO: Dynamic vars ;(dosync (commute @#'*loaded-libs* conj '~name)) 'nil)) diff --git a/test/cpp/jank/read/parse.cpp b/test/cpp/jank/read/parse.cpp index 7e8a0128d..5393ddc0f 100644 --- a/test/cpp/jank/read/parse.cpp +++ b/test/cpp/jank/read/parse.cpp @@ -14,6 +14,7 @@ #include #include #include +#include /* This must go last; doctest and glog both define CHECK and family. */ #include @@ -29,7 +30,7 @@ namespace jank::read::parse processor p{ rt_ctx, lp.begin(), lp.end() }; auto const r(p.next()); CHECK(r.is_ok()); - CHECK(r.expect_ok() == nullptr); + CHECK(r.expect_ok().is_none()); } TEST_CASE("Nil") @@ -39,7 +40,9 @@ namespace jank::read::parse processor p{ rt_ctx, lp.begin(), lp.end() }; auto const r(p.next()); CHECK(r.is_ok()); - CHECK(runtime::detail::equal(r.expect_ok(), make_box(nullptr))); + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, runtime::obj::nil::nil_const())); + CHECK(r.expect_ok().unwrap().start == lex::token{ 0, 3, lex::token_kind::nil }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); } TEST_CASE("Boolean") @@ -49,9 +52,14 @@ namespace jank::read::parse processor p{ rt_ctx, lp.begin(), lp.end() }; auto const t(p.next()); CHECK(t.is_ok()); - CHECK(runtime::detail::equal(t.expect_ok(), make_box(true))); + CHECK(runtime::detail::equal(t.expect_ok().unwrap().ptr, make_box(true))); + CHECK(t.expect_ok().unwrap().start == lex::token{ 0, 4, lex::token_kind::boolean, true }); + CHECK(t.expect_ok().unwrap().end == t.expect_ok().unwrap().start); + auto const f(p.next()); - CHECK(runtime::detail::equal(f.expect_ok(), make_box(false))); + CHECK(runtime::detail::equal(f.expect_ok().unwrap().ptr, make_box(false))); + CHECK(f.expect_ok().unwrap().start == lex::token{ 5, 5, lex::token_kind::boolean, false }); + CHECK(f.expect_ok().unwrap().end == f.expect_ok().unwrap().start); } TEST_CASE("Integer") @@ -61,7 +69,9 @@ namespace jank::read::parse processor p{ rt_ctx, lp.begin(), lp.end() }; auto const r(p.next()); CHECK(r.is_ok()); - CHECK(runtime::detail::equal(r.expect_ok(), make_box(1234))); + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, make_box(1234))); + CHECK(r.expect_ok().unwrap().start == lex::token{ 0, 4, lex::token_kind::integer, 1234ll }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); } TEST_CASE("Comments") @@ -71,11 +81,11 @@ namespace jank::read::parse processor p{ rt_ctx, lp.begin(), lp.end() }; auto const i(p.next()); CHECK(i.is_ok()); - CHECK(runtime::detail::equal(i.expect_ok(), make_box(1234))); + CHECK(runtime::detail::equal(i.expect_ok().unwrap().ptr, make_box(1234))); auto const eof(p.next()); CHECK(eof.is_ok()); - CHECK(eof.expect_ok() == nullptr); + CHECK(eof.expect_ok().is_none()); } TEST_CASE("Real") @@ -85,7 +95,9 @@ namespace jank::read::parse processor p{ rt_ctx, lp.begin(), lp.end() }; auto const r(p.next()); CHECK(r.is_ok()); - CHECK(runtime::detail::equal(r.expect_ok(), make_box(12.34l))); + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, make_box(12.34l))); + CHECK(r.expect_ok().unwrap().start == lex::token{ 0, 5, lex::token_kind::real, 12.34l }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); } TEST_CASE("String") @@ -95,11 +107,21 @@ namespace jank::read::parse lex::processor lp{ R"("foo" "bar")" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(auto const &s : { "foo", "bar" }) { auto const r(p.next()); CHECK(r.is_ok()); - CHECK(runtime::detail::equal(r.expect_ok(), make_box(s))); + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, make_box(s))); + + /* We add 2 for the surrounding quotes. */ + auto const len(strlen(s) + 2); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, len, lex::token_kind::string, s }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); + /* Each string is 1 space apart. */ + offset += len + 1; } } @@ -108,11 +130,22 @@ namespace jank::read::parse lex::processor lp{ R"("foo\n" "\t\"bar\"")" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(auto const &s : { "foo\n", "\t\"bar\"" }) { auto const r(p.next()); CHECK(r.is_ok()); - CHECK(runtime::detail::equal(r.expect_ok(), make_box(s))); + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, make_box(s))); + + /* We add 2 for the surrounding quotes. */ + auto const escaped(util::escape(s)); + auto const len(escaped.size() + 2); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, len, lex::token_kind::escaped_string, escaped }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); + /* Each string is 1 space apart. */ + offset += len + 1; } } } @@ -124,11 +157,21 @@ namespace jank::read::parse lex::processor lp{ "foo bar spam" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(auto const &s : { "foo", "bar", "spam" }) { auto const r(p.next()); CHECK(r.is_ok()); - CHECK(runtime::detail::equal(r.expect_ok(), make_box("", s))); + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, + make_box("", s))); + + auto const len(strlen(s)); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, len, lex::token_kind::symbol, s }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); + /* Each symbol is 1 space apart. */ + offset += len + 1; } } @@ -139,7 +182,10 @@ namespace jank::read::parse processor p{ rt_ctx, lp.begin(), lp.end() }; auto const r(p.next()); CHECK(r.is_ok()); - CHECK(runtime::detail::equal(r.expect_ok(), make_box("", "/"))); + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, + make_box("", "/"))); + CHECK(r.expect_ok().unwrap().start == lex::token{ 0, 1, lex::token_kind::symbol, "/" }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); } SUBCASE("Qualified") @@ -150,14 +196,27 @@ namespace jank::read::parse rt_ctx.intern_ns(make_box("foo.bar")); rt_ctx.intern_ns(make_box("spam.bar")); processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(auto const &s : { std::make_pair("foo", "foo"), std::make_pair("foo.bar", "bar"), std::make_pair("spam.bar", "spam") }) { auto const r(p.next()); CHECK(r.is_ok()); - CHECK(runtime::detail::equal(r.expect_ok(), + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, make_box(s.first, s.second))); + + /* We add one for the slash. */ + auto const len(strlen(s.first) + strlen(s.second) + 1); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, + len, + lex::token_kind::symbol, + fmt::format("{}/{}", s.first, s.second) }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); + /* Each symbol is 1 space apart. */ + offset += len + 1; } } @@ -172,7 +231,7 @@ namespace jank::read::parse { auto const r(p.next()); CHECK(r.is_ok()); - CHECK(runtime::detail::equal(r.expect_ok(), + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, make_box(s.first, s.second))); } } @@ -191,16 +250,33 @@ namespace jank::read::parse lex::processor lp{ "'foo 'bar/spam 'foo.bar/bar" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(auto const &s : { std::make_pair("", "foo"), std::make_pair("bar", "spam"), std::make_pair("foo.bar", "bar") }) { auto const r(p.next()); CHECK(r.is_ok()); - CHECK(runtime::detail::equal(r.expect_ok(), + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, make_box( make_box("quote"), make_box(s.first, s.second)))); + + auto const ns_len(strlen(s.first)); + /* We add one for the slash. */ + auto const len(strlen(s.first) + strlen(s.second) + (ns_len == 0 ? 0 : 1)); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, 1, lex::token_kind::single_quote }); + offset += 1; + CHECK(r.expect_ok().unwrap().end + == lex::token{ offset, + len, + lex::token_kind::symbol, + (ns_len == 0 ? fmt::format("{}", s.second) + : fmt::format("{}/{}", s.first, s.second)) }); + /* Each symbol is 1 space apart. */ + offset += len + 1; } } } @@ -212,13 +288,23 @@ namespace jank::read::parse lex::processor lp{ ":foo :bar :spam" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(auto const &s : { "foo", "bar", "spam" }) { auto const r(p.next()); CHECK(r.is_ok()); CHECK(runtime::detail::equal( - r.expect_ok(), + r.expect_ok().unwrap().ptr, rt_ctx.intern_keyword(runtime::obj::symbol{ "", s }, true).expect_ok())); + + /* We add one for the colon. */ + auto const len(strlen(s) + 1); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, len, lex::token_kind::keyword, s }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); + /* Each symbol is 1 space apart. */ + offset += len + 1; } } @@ -227,6 +313,8 @@ namespace jank::read::parse lex::processor lp{ ":foo/foo :foo.bar/bar :spam.bar/spam" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(auto const &s : { std::make_pair("foo", "foo"), std::make_pair("foo.bar", "bar"), std::make_pair("spam.bar", "spam") }) @@ -234,8 +322,19 @@ namespace jank::read::parse auto const r(p.next()); CHECK(r.is_ok()); CHECK(runtime::detail::equal( - r.expect_ok(), + r.expect_ok().unwrap().ptr, rt_ctx.intern_keyword(runtime::obj::symbol{ s.first, s.second }, true).expect_ok())); + + /* We add one for the colon and one for the slash. */ + auto const len(strlen(s.first) + strlen(s.second) + 2); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, + len, + lex::token_kind::keyword, + fmt::format("{}/{}", s.first, s.second) }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); + /* Each symbol is 1 space apart. */ + offset += len + 1; } } @@ -244,14 +343,24 @@ namespace jank::read::parse lex::processor lp{ "::foo ::spam" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(auto const &s : { "foo", "spam" }) { auto const r(p.next()); CHECK(r.is_ok()); CHECK(runtime::detail::equal( - r.expect_ok(), + r.expect_ok().unwrap().ptr, rt_ctx.intern_keyword(runtime::obj::symbol{ "", native_persistent_string{ s } }, false) .expect_ok())); + + /* We add one for each colon. */ + auto const len(strlen(s) + 2); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, len, lex::token_kind::keyword, fmt::format(":{}", s) }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); + /* Each keyword is 1 space apart. */ + offset += len + 1; } } @@ -275,8 +384,11 @@ namespace jank::read::parse auto const r(p.next()); CHECK(r.is_ok()); CHECK(runtime::detail::equal( - r.expect_ok(), + r.expect_ok().unwrap().ptr, rt_ctx.intern_keyword(runtime::obj::symbol{ "foo.bar.spam", "foo" }, true).expect_ok())); + CHECK(r.expect_ok().unwrap().start + == lex::token{ 0, 9, lex::token_kind::keyword, ":foo/foo" }); + CHECK(r.expect_ok().unwrap().end == r.expect_ok().unwrap().start); } } @@ -284,33 +396,55 @@ namespace jank::read::parse { SUBCASE("Empty") { - lex::processor lp{ "() ( ) ( )" }; + lex::processor lp{ "() ( ) ( )" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(size_t i{}; i < 3; ++i) { auto const r(p.next()); CHECK(r.is_ok()); - CHECK(r.expect_ok() != nullptr); - CHECK(runtime::detail::equal(r.expect_ok(), make_box())); + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, + runtime::obj::persistent_list::empty())); + + /* We add one for each paren. */ + auto const len(2 + i); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, 1, lex::token_kind::open_paren }); + CHECK(r.expect_ok().unwrap().end + == lex::token{ offset + i + 1, 1, lex::token_kind::close_paren }); + /* Each list is 1 space apart. */ + offset += len + 1; } } SUBCASE("Non-empty") { - lex::processor lp{ "(1 2 3 4) ( 2, 4 6, 8 )" }; + lex::processor lp{ "(1, 2, 3, 4) ( 2, 4, 6, 8 )" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(size_t i{ 1 }; i < 3; ++i) { auto const r(p.next()); CHECK(r.is_ok()); CHECK(runtime::detail::equal( - r.expect_ok(), + r.expect_ok().unwrap().ptr, make_box(make_box(1 * i), make_box(2 * i), make_box(3 * i), make_box(4 * i)))); + + /* Parens, nums, spaces, commas, and optionally more spaces. */ + auto const len(2 + 4 + 3 + 3 + (i == 2 ? 2 : 0)); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, 1, lex::token_kind::open_paren }); + CHECK(r.expect_ok().unwrap().end + == lex::token{ offset + len - 1, 1, lex::token_kind::close_paren }); + /* Each list is 1 space apart. */ + offset += len + 1; } } @@ -322,13 +456,14 @@ namespace jank::read::parse auto const r1(p.next()); CHECK(r1.is_ok()); CHECK(runtime::detail::equal( - r1.expect_ok(), + r1.expect_ok().unwrap().ptr, make_box(make_box("def"), make_box("foo-bar"), make_box(1)))); auto const r2(p.next()); CHECK(r2.is_ok()); - CHECK(runtime::detail::equal(r2.expect_ok(), make_box("foo-bar"))); + CHECK(runtime::detail::equal(r2.expect_ok().unwrap().ptr, + make_box("foo-bar"))); } SUBCASE("Extra close") @@ -338,7 +473,7 @@ namespace jank::read::parse processor p{ rt_ctx, lp.begin(), lp.end() }; auto const r1(p.next()); CHECK(r1.is_ok()); - CHECK(runtime::detail::equal(r1.expect_ok(), make_box(1))); + CHECK(runtime::detail::equal(r1.expect_ok().unwrap().ptr, make_box(1))); auto const r2(p.next()); CHECK(r2.is_err()); } @@ -357,35 +492,57 @@ namespace jank::read::parse { SUBCASE("Empty") { - lex::processor lp{ "[] [ ] [ ]" }; + lex::processor lp{ "[] [ ] [ ]" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(size_t i{}; i < 3; ++i) { auto const r(p.next()); CHECK(r.is_ok()); - CHECK(r.expect_ok() != nullptr); - CHECK(runtime::detail::equal(r.expect_ok(), make_box())); + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, + make_box())); + + /* We add one for each bracket. */ + auto const len(2 + i); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, 1, lex::token_kind::open_square_bracket }); + CHECK(r.expect_ok().unwrap().end + == lex::token{ offset + i + 1, 1, lex::token_kind::close_square_bracket }); + /* Each vector is 1 space apart. */ + offset += len + 1; } } SUBCASE("Non-empty") { - lex::processor lp{ "[1 2 3 4] [ 2, 4 6, 8 ]" }; + lex::processor lp{ "[1, 2, 3, 4] [ 2, 4, 6, 8 ]" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(size_t i{ 1 }; i < 3; ++i) { auto const r(p.next()); CHECK(r.is_ok()); CHECK(runtime::detail::equal( - r.expect_ok(), + r.expect_ok().unwrap().ptr, make_box(runtime::detail::native_persistent_vector{ make_box(1 * i), make_box(2 * i), make_box(3 * i), make_box(4 * i), }))); + + /* Brackets, nums, spaces, commas, and optionally more spaces. */ + auto const len(2 + 4 + 3 + 3 + (i == 2 ? 2 : 0)); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, 1, lex::token_kind::open_square_bracket }); + CHECK(r.expect_ok().unwrap().end + == lex::token{ offset + len - 1, 1, lex::token_kind::close_square_bracket }); + /* Each vector is 1 space apart. */ + offset += len + 1; } } @@ -396,7 +553,7 @@ namespace jank::read::parse processor p{ rt_ctx, lp.begin(), lp.end() }; auto const r1(p.next()); CHECK(r1.is_ok()); - CHECK(runtime::detail::equal(r1.expect_ok(), make_box(1))); + CHECK(runtime::detail::equal(r1.expect_ok().unwrap().ptr, make_box(1))); auto const r2(p.next()); CHECK(r2.is_err()); } @@ -415,30 +572,42 @@ namespace jank::read::parse { SUBCASE("Empty") { - lex::processor lp{ "{} { } {,,,}" }; + lex::processor lp{ "{} { } {,,}" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(size_t i{}; i < 3; ++i) { auto const r(p.next()); CHECK(r.is_ok()); - CHECK(r.expect_ok() != nullptr); - CHECK( - runtime::detail::equal(r.expect_ok(), make_box())); + CHECK(runtime::detail::equal(r.expect_ok().unwrap().ptr, + make_box())); + + /* We add one for each bracket. */ + auto const len(2 + i); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, 1, lex::token_kind::open_curly_bracket }); + CHECK(r.expect_ok().unwrap().end + == lex::token{ offset + i + 1, 1, lex::token_kind::close_curly_bracket }); + /* Each map is 1 space apart. */ + offset += len + 1; } } SUBCASE("Non-empty") { - lex::processor lp{ "{1 2 3 4} { 2, 4 6, 8 }" }; + lex::processor lp{ "{1 2, 3 4} { 2 4, 6 8 }" }; runtime::context rt_ctx; processor p{ rt_ctx, lp.begin(), lp.end() }; + + size_t offset{}; for(size_t i{ 1 }; i < 3; ++i) { auto const r(p.next()); CHECK(r.is_ok()); CHECK(runtime::detail::equal( - r.expect_ok(), + r.expect_ok().unwrap().ptr, make_box( runtime::detail::in_place_unique{}, make_array_box(make_box(1 * i), @@ -446,6 +615,15 @@ namespace jank::read::parse make_box(3 * i), make_box(4 * i)), 4))); + + /* Brackets, nums, spaces, commas, and optionally more spaces. */ + auto const len(2 + 4 + 3 + 1 + (i == 2 ? 2 : 0)); + CHECK(r.expect_ok().unwrap().start + == lex::token{ offset, 1, lex::token_kind::open_curly_bracket }); + CHECK(r.expect_ok().unwrap().end + == lex::token{ offset + len - 1, 1, lex::token_kind::close_curly_bracket }); + /* Each map is 1 space apart. */ + offset += len + 1; } } @@ -456,9 +634,8 @@ namespace jank::read::parse processor p{ rt_ctx, lp.begin(), lp.end() }; auto const r(p.next()); CHECK(r.is_ok()); - CHECK(r.expect_ok() != nullptr); CHECK(runtime::detail::equal( - r.expect_ok(), + r.expect_ok().unwrap().ptr, make_box( runtime::detail::in_place_unique{}, make_array_box( @@ -469,6 +646,10 @@ namespace jank::read::parse make_box("meow"), make_box("meow")), 6))); + CHECK(r.expect_ok().unwrap().start + == lex::token{ 0, 1, lex::token_kind::open_curly_bracket }); + CHECK(r.expect_ok().unwrap().end + == lex::token{ 31, 1, lex::token_kind::close_curly_bracket }); } SUBCASE("Odd elements") @@ -488,7 +669,7 @@ namespace jank::read::parse auto const r1(p.next()); CHECK(r1.is_ok()); CHECK(runtime::detail::equal( - r1.expect_ok(), + r1.expect_ok().unwrap().ptr, rt_ctx.intern_keyword(runtime::obj::symbol{ "foo" }, true).expect_ok())); auto const r2(p.next()); CHECK(r2.is_err()); diff --git a/test/jank/form/native_raw/interpolate/fail-invalid-code.jank b/test/jank/form/native_raw/interpolate/fail-invalid-code.jank index 24757426a..712062556 100644 --- a/test/jank/form/native_raw/interpolate/fail-invalid-code.jank +++ b/test/jank/form/native_raw/interpolate/fail-invalid-code.jank @@ -1 +1 @@ -(native/raw "__value = #{ meow }#;") +(native/raw "__value = ~{ meow };") diff --git a/test/jank/form/native_raw/interpolate/fail-multiple-expresssions.jank b/test/jank/form/native_raw/interpolate/fail-multiple-expresssions.jank index 493b55853..fcbca4fc7 100644 --- a/test/jank/form/native_raw/interpolate/fail-multiple-expresssions.jank +++ b/test/jank/form/native_raw/interpolate/fail-multiple-expresssions.jank @@ -1 +1 @@ -(native/raw "__value = #{ 1 2 }#;") +(native/raw "__value = ~{ 1 2 };") diff --git a/test/jank/form/native_raw/interpolate/pass-symbol.jank b/test/jank/form/native_raw/interpolate/pass-symbol.jank index 2ab2b386e..18f60bcf6 100644 --- a/test/jank/form/native_raw/interpolate/pass-symbol.jank +++ b/test/jank/form/native_raw/interpolate/pass-symbol.jank @@ -1,4 +1,4 @@ (def a 1) -(assert (= a (native/raw "__value = #{ a }#;"))) +(assert (= a (native/raw "__value = ~{ a };"))) :success diff --git a/test/jank/form/native_raw/pass-practical.jank b/test/jank/form/native_raw/pass-practical.jank index 524f06176..934f61d48 100644 --- a/test/jank/form/native_raw/pass-practical.jank +++ b/test/jank/form/native_raw/pass-practical.jank @@ -4,7 +4,7 @@ ; Interpolation will often by used for parameters. (def keyword? (fn* [o] - (native/raw "__value = make_box(#{ o }#->type == object_type::keyword)"))) + (native/raw "__value = make_box(~{ o }->type == object_type::keyword)"))) (assert (= (keyword? :meow) true)) (assert (= (keyword? 1) false)) @@ -14,7 +14,7 @@ (fn* [o] ; Interpolation can contain arbitrary jank code. ; It can also capture vars and locals, which lifts and captures them accordingly. - (native/raw "__value = #{ (keyword? o) }#"))) + (native/raw "__value = ~{ (keyword? o) };"))) (assert (= (also-keyword? :meow) true)) (assert (= (also-keyword? 1) false))