diff --git a/compiler+runtime/CMakeLists.txt b/compiler+runtime/CMakeLists.txt index 90d572dd..5907806d 100644 --- a/compiler+runtime/CMakeLists.txt +++ b/compiler+runtime/CMakeLists.txt @@ -65,7 +65,8 @@ set( -Wno-implicit-fallthrough -Wno-covered-switch-default -Wno-invalid-offsetof - # TODO: Ignore deprecations + -Wno-deprecated-declarations + -Wno-c++23-extensions -fno-common -fno-rtti -fexceptions @@ -154,7 +155,9 @@ add_library( src/cpp/jank/util/clang_format.cpp src/cpp/jank/util/string_builder.cpp src/cpp/jank/profile/time.cpp + src/cpp/jank/ui/highlight.cpp src/cpp/jank/error.cpp + src/cpp/jank/error/report.cpp src/cpp/jank/read/source.cpp src/cpp/jank/read/lex.cpp src/cpp/jank/read/parse.cpp diff --git a/compiler+runtime/include/cpp/jank/error/report.hpp b/compiler+runtime/include/cpp/jank/error/report.hpp new file mode 100644 index 00000000..2f31754c --- /dev/null +++ b/compiler+runtime/include/cpp/jank/error/report.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace jank::error +{ + void report(error_ptr e); +} diff --git a/compiler+runtime/include/cpp/jank/runtime/context.hpp b/compiler+runtime/include/cpp/jank/runtime/context.hpp index a4d00a8d..f0617223 100644 --- a/compiler+runtime/include/cpp/jank/runtime/context.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/context.hpp @@ -142,6 +142,7 @@ namespace jank::runtime native_persistent_string output_dir; module::loader module_loader; + var_ptr current_file_var{}; var_ptr current_ns_var{}; var_ptr in_ns_var{}; var_ptr compile_files_var{}; diff --git a/compiler+runtime/include/cpp/jank/ui/highlight.hpp b/compiler+runtime/include/cpp/jank/ui/highlight.hpp new file mode 100644 index 00000000..1f160a62 --- /dev/null +++ b/compiler+runtime/include/cpp/jank/ui/highlight.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace ftxui +{ + using Element = std::shared_ptr; +} + +namespace jank +{ + struct native_persistent_string; + + namespace ui + { + ftxui::Element + highlight(native_persistent_string const &code, size_t const line_start, size_t const line_end); + } +} diff --git a/compiler+runtime/src/cpp/jank/analyze/processor.cpp b/compiler+runtime/src/cpp/jank/analyze/processor.cpp index f15daa13..d7143922 100644 --- a/compiler+runtime/src/cpp/jank/analyze/processor.cpp +++ b/compiler+runtime/src/cpp/jank/analyze/processor.cpp @@ -26,7 +26,6 @@ namespace jank native_persistent_string const &message, read::source const &source) { - /* TODO: File name. */ return runtime::make_box(gc{}, kind, message, source); } } @@ -1018,7 +1017,7 @@ namespace jank::analyze if(found_var.is_none()) { return make_error(error::kind::analysis_unresolved_var, - fmt::format("unable to resolve var '{}'", qualified_sym->to_string()), + fmt::format("Unable to resolve var '{}'", qualified_sym->to_string()), meta_source(o)); } diff --git a/compiler+runtime/src/cpp/jank/error.cpp b/compiler+runtime/src/cpp/jank/error.cpp index 5c6585fa..52421d5d 100644 --- a/compiler+runtime/src/cpp/jank/error.cpp +++ b/compiler+runtime/src/cpp/jank/error.cpp @@ -1,4 +1,6 @@ #include +#include +#include namespace jank::error { @@ -22,17 +24,22 @@ namespace jank { error_ptr make_error(error::kind const kind, native_persistent_string const &message) { - /* TODO: File name. */ - return runtime::make_box(gc{}, kind, message, read::source{ "", {}, {} }); + auto const file{ runtime::__rt_ctx->current_file_var->deref() }; + return runtime::make_box(gc{}, + kind, + message, + read::source{ runtime::to_string(file), {}, {} }); } error_ptr make_error(error::kind const kind, native_persistent_string const &message, read::source_position const &start) { - /* TODO: File name. */ - /* NOLINTNEXTLINE(cppcoreguidelines-slicing) */ - return runtime::make_box(gc{}, kind, message, read::source{ "", start, start }); + auto const file{ runtime::__rt_ctx->current_file_var->deref() }; + return runtime::make_box(gc{}, + kind, + message, + read::source{ runtime::to_string(file), start, start }); } error_ptr make_error(error::kind const kind, @@ -40,7 +47,10 @@ namespace jank read::source_position const &start, read::source_position const &end) { - /* NOLINTNEXTLINE(cppcoreguidelines-slicing) */ - return runtime::make_box(gc{}, kind, message, read::source{ "", start, end }); + auto const file{ runtime::__rt_ctx->current_file_var->deref() }; + return runtime::make_box(gc{}, + kind, + message, + read::source{ runtime::to_string(file), start, end }); } } diff --git a/compiler+runtime/src/cpp/jank/error/report.cpp b/compiler+runtime/src/cpp/jank/error/report.cpp new file mode 100644 index 00000000..4bbb0aaf --- /dev/null +++ b/compiler+runtime/src/cpp/jank/error/report.cpp @@ -0,0 +1,138 @@ +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace jank::error +{ + using namespace jank; + using namespace jank::runtime; + using namespace ftxui; + + Element snippet(error_ptr const e) + { + static constexpr size_t max_body_lines{ 5 }; + static constexpr size_t min_body_lines{ 1 }; + + static constexpr size_t max_top_margin_lines{ 2 }; + + /* Top margin: + * Min: 1 line + * Max: 2 lines + * + * If the count goes negative, use one blank line. + * + * Bottom margin: + * Always 0 + * + * Code body: + * Min: 1 line + * Max: 5 lines + * + * If the error spans more than the max, just show up to the max. + */ + + auto const body_range{ + std::min(std::max(e->source.end.line - e->source.start.line, min_body_lines), max_body_lines) + }; + auto const top_margin{ std::min(e->source.start.line - 1, max_top_margin_lines) }; + auto const line_start{ e->source.start.line - top_margin }; + auto const line_end{ e->source.start.line + body_range }; + + std::vector line_numbers; + for(auto i{ static_cast(line_start) }; i < static_cast(line_end); ++i) + { + if(i < 1) + { + line_numbers.emplace_back(text(" ")); + } + else + { + line_numbers.emplace_back(text(std::to_string(i))); + } + } + + auto const file(util::map_file(e->source.file_path)); + if(file.is_err()) + { + /* TODO: Return result. */ + throw std::runtime_error{ fmt::format("unable to map file {} due to error: {}", + e->source.file_path, + file.expect_err()) }; + } + + return window( + text( + fmt::format(" {}:{}:{} ", e->source.file_path, e->source.start.line, e->source.start.col)), + hbox( + { vbox(line_numbers) | color(Color::GrayLight), + separator(), + ui::highlight({ file.expect_ok().head, file.expect_ok().size }, line_start, line_end) })); + } + + void report(error_ptr const e) + { + static constexpr size_t max_width{ 80 }; + + auto header{ [&](std::string const &title) { + auto const padding_count(max_width - 2 - title.size()); + std::string padding; + for(size_t i{}; i < padding_count; ++i) + { + padding.insert(padding.size(), "─"); + } + return hbox({ + text("─ "), + text(title) | color(Color::BlueLight), + text(" "), + text(padding), + }); + } }; + + auto error{ vbox({ header(kind_str(e->kind)), + hbox({ + text("error: ") | bold | color(Color::Red), + text(e->message) | bold, + }) }) }; + + /* TODO: Context. */ + //auto context{ vbox( + // { header("context"), + // vbox({ + // hbox({ text("compiling module "), text("kitten.main") | color(Color::Yellow) }), + // hbox({ + // text("└─ requiring module "), + // text("kitten.nap") | color(Color::Yellow), + + // }), + // hbox({ + // text(" └─ compiling function "), + // text("kitten.nap/") | color(Color::Yellow), + // text("nap") | color(Color::Green), + // }), + // }) }) }; + + auto document{ vbox({ + error, + text("\n"), + snippet(e), + text("\n"), + //context(), + }) }; + + document = document | size(WIDTH, LESS_THAN, max_width); + + auto screen{ Screen::Create(Dimension::Full(), Dimension::Fit(document)) }; + Render(screen, document); + std::cout << screen.ToString() << '\0' << std::endl; + } +} diff --git a/compiler+runtime/src/cpp/jank/read/lex.cpp b/compiler+runtime/src/cpp/jank/read/lex.cpp index 8c38ba3a..fbac70e3 100644 --- a/compiler+runtime/src/cpp/jank/read/lex.cpp +++ b/compiler+runtime/src/cpp/jank/read/lex.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include using namespace std::string_view_literals; @@ -284,9 +286,12 @@ namespace jank::read::lex native_persistent_string const &message, movable_position const &start) { - /* TODO: File name. */ - /* NOLINTNEXTLINE(cppcoreguidelines-slicing) */ - return runtime::make_box(gc{}, kind, message, source{ "", start, start }); + auto const file{ runtime::__rt_ctx->current_file_var->deref() }; + return runtime::make_box(gc{}, + kind, + message, + /* NOLINTNEXTLINE(cppcoreguidelines-slicing) */ + source{ runtime::to_string(file), start, start }); } static error_ptr make_error(error::kind const kind, @@ -294,8 +299,16 @@ namespace jank::read::lex movable_position const &start, movable_position const &end) { - /* NOLINTNEXTLINE(cppcoreguidelines-slicing) */ - return runtime::make_box(gc{}, kind, message, source{ "", start, end }); + auto const file{ runtime::__rt_ctx->current_file_var->deref() }; + fmt::println("make_error message '{}' file '{}'", + message.c_str(), + runtime::to_string(file).c_str()); + __builtin_debugtrap(); + return runtime::make_box(gc{}, + kind, + message, + /* NOLINTNEXTLINE(cppcoreguidelines-slicing) */ + source{ runtime::to_string(file), start, end }); } option processor::check_whitespace(native_bool const found_space) diff --git a/compiler+runtime/src/cpp/jank/read/parse.cpp b/compiler+runtime/src/cpp/jank/read/parse.cpp index 8aac19b1..ce3c047b 100644 --- a/compiler+runtime/src/cpp/jank/read/parse.cpp +++ b/compiler+runtime/src/cpp/jank/read/parse.cpp @@ -295,7 +295,7 @@ namespace jank::read::parse ->push_thread_bindings(obj::persistent_hash_map::create_unique( std::make_pair(splicing_allowed_var, obj::boolean::true_const()))) .expect_ok(); - util::scope_exit const finally{ [&]() { __rt_ctx->pop_thread_bindings().expect_ok(); } }; + util::scope_exit const finally{ [] { __rt_ctx->pop_thread_bindings().expect_ok(); } }; runtime::detail::native_transient_vector ret; for(auto it(begin()); it != end(); ++it) @@ -314,6 +314,7 @@ namespace jank::read::parse } expected_closer = prev_expected_closer; + return object_source_info{ make_box(std::in_place, ret.rbegin(), ret.rend()), start_token, @@ -332,7 +333,7 @@ namespace jank::read::parse ->push_thread_bindings(obj::persistent_hash_map::create_unique( std::make_pair(splicing_allowed_var, obj::boolean::true_const()))) .expect_ok(); - util::scope_exit const finally{ [&]() { __rt_ctx->pop_thread_bindings().expect_ok(); } }; + util::scope_exit const finally{ [] { __rt_ctx->pop_thread_bindings().expect_ok(); } }; runtime::detail::native_transient_vector ret; for(auto it(begin()); it != end(); ++it) @@ -368,7 +369,7 @@ namespace jank::read::parse ->push_thread_bindings(obj::persistent_hash_map::create_unique( std::make_pair(splicing_allowed_var, obj::boolean::true_const()))) .expect_ok(); - util::scope_exit const finally{ [&]() { __rt_ctx->pop_thread_bindings().expect_ok(); } }; + util::scope_exit const finally{ [] { __rt_ctx->pop_thread_bindings().expect_ok(); } }; runtime::detail::native_persistent_array_map ret; for(auto it(begin()); it != end(); ++it) @@ -605,7 +606,7 @@ namespace jank::read::parse ->push_thread_bindings(obj::persistent_hash_map::create_unique( std::make_pair(splicing_allowed_var, obj::boolean::true_const()))) .expect_ok(); - util::scope_exit const finally{ [&]() { __rt_ctx->pop_thread_bindings().expect_ok(); } }; + util::scope_exit const finally{ [] { __rt_ctx->pop_thread_bindings().expect_ok(); } }; runtime::detail::native_transient_hash_set ret; for(auto it(begin()); it != end(); ++it) @@ -930,7 +931,7 @@ namespace jank::read::parse (splice ? "clojure.core/unquote-splicing" : "clojure.core/unquote")) ->equal(*item); }, - []() { return false; }, + [] { return false; }, form); } diff --git a/compiler+runtime/src/cpp/jank/runtime/context.cpp b/compiler+runtime/src/cpp/jank/runtime/context.cpp index 8a38743f..48a0b389 100644 --- a/compiler+runtime/src/cpp/jank/runtime/context.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/context.cpp @@ -43,6 +43,12 @@ namespace jank::runtime , module_loader{ *this, opts.module_path } { auto const core(intern_ns(make_box("clojure.core"))); + + auto const file_sym(make_box("clojure.core/*file*")); + current_file_var = core->intern_var(file_sym); + current_file_var->bind_root(make_box("NO_SOURCE_PATH")); + current_file_var->dynamic.store(true); + auto const ns_sym(make_box("clojure.core/*ns*")); current_ns_var = core->intern_var(ns_sym); current_ns_var->bind_root(core); @@ -139,6 +145,11 @@ namespace jank::runtime fmt::format("unable to map file {} due to error: {}", path, file.expect_err()) }; } + + binding_scope const preserve{ *this, + obj::persistent_hash_map::create_unique( + std::make_pair(current_file_var, make_box(path))) }; + return eval_string({ file.expect_ok().head, file.expect_ok().size }); } diff --git a/compiler+runtime/src/cpp/jank/ui/highlight.cpp b/compiler+runtime/src/cpp/jank/ui/highlight.cpp new file mode 100644 index 00000000..5d69483b --- /dev/null +++ b/compiler+runtime/src/cpp/jank/ui/highlight.cpp @@ -0,0 +1,133 @@ +#include +#include + +#include + +#include + +namespace jank::ui +{ + using namespace ftxui; + + static std::set const specials{ + "def", "fn*", "fn", "let*", "let", "loop*", "loop", "do", + "if", "quote", "var", "try", "catch", "finally", "throw", + }; + + /* TODO: Also support core fns? */ + + Element symbol_color(Element const &e, native_persistent_string_view const &sym) + { + if(specials.find(sym) != specials.end()) + { + return e | color(Color::CyanLight) | bold; + } + + return e | color(Color::Default); + } + + Element token_color(Element const &e, read::lex::token const &token) + { + switch(token.kind) + { + case read::lex::token_kind::open_paren: + case read::lex::token_kind::close_paren: + case read::lex::token_kind::open_square_bracket: + case read::lex::token_kind::close_square_bracket: + case read::lex::token_kind::open_curly_bracket: + case read::lex::token_kind::close_curly_bracket: + case read::lex::token_kind::single_quote: + case read::lex::token_kind::meta_hint: + case read::lex::token_kind::reader_macro: + case read::lex::token_kind::reader_macro_comment: + case read::lex::token_kind::reader_macro_conditional: + case read::lex::token_kind::reader_macro_conditional_splice: + case read::lex::token_kind::syntax_quote: + case read::lex::token_kind::unquote: + case read::lex::token_kind::unquote_splice: + case read::lex::token_kind::deref: + case read::lex::token_kind::nil: + return e | color(Color::DarkOrange); + case read::lex::token_kind::keyword: + return e | color(Color::BlueLight); + case read::lex::token_kind::comment: + return e | color(Color::GrayLight); + case read::lex::token_kind::integer: + case read::lex::token_kind::real: + case read::lex::token_kind::ratio: + case read::lex::token_kind::boolean: + case read::lex::token_kind::character: + return e | color(Color::MagentaLight); + case read::lex::token_kind::string: + case read::lex::token_kind::escaped_string: + return e | color(Color::GreenLight); + case read::lex::token_kind::symbol: + return symbol_color(e, boost::get(token.data)); + case read::lex::token_kind::eof: + return e | color(Color::Default); + break; + } + } + + /* TODO: Center horizontally if the line is too long. */ + Element + highlight(native_persistent_string const &code, size_t const line_start, size_t const line_end) + { + read::lex::processor l_prc{ code }; + size_t last_offset{}, last_line{ 1 }; + std::vector lines; + std::vector current_line; + + for(auto it(l_prc.begin()); it != l_prc.end(); ++it) + { + /* TODO: Handle lex errors by showing the rest of the lines without highlighting. */ + + auto const &token(it.latest.unwrap().expect_ok()); + if(token.start.line > line_end) + { + break; + } + + auto const token_size(std::max(token.end.offset - token.start.offset, 1zu)); + auto const skip(token.start.line < line_start); + std::string_view space{ code.data() + last_offset, token.start.offset - last_offset }; + size_t last_newline{}; + for(auto it(space.find('\n')); it != decltype(space)::npos; it = space.find('\n', it + 1)) + { + /* We add a space since this could be an empty line and ftxui will only make it take + * space if it's non-empty. */ + if(!skip && last_line >= line_start) + { + current_line.emplace_back(text(" ")); + //current_line.emplace_back(text("")); + lines.emplace_back(hbox(std::move(current_line))); + current_line.clear(); + } + last_newline = it + 1; + ++last_line; + } + //fmt::println("space '{}' size {} last_newline {}", space, space.size(), last_newline); + if(!skip && last_newline < space.size()) + { + current_line.emplace_back(text(std::string{ space.substr(last_newline) })); + //current_line.emplace_back( + // text(fmt::format("|{}|", std::string{ space.substr(last_newline) }))); + } + + if(!skip) + { + current_line.emplace_back( + token_color(text(std::string{ code.data() + token.start.offset, token_size }), token)); + //current_line.emplace_back(token_color( + // text(fmt::format("'{}'", std::string{ code.data() + token.start.offset, token_size })), + // token)); + } + last_offset = token.start.offset + token_size; + //fmt::println("last_offset {}", last_offset); + } + + lines.emplace_back(hbox(std::move(current_line))); + + return vbox(std::move(lines)); + } +} diff --git a/compiler+runtime/src/cpp/main.cpp b/compiler+runtime/src/cpp/main.cpp index 6d8384d7..7ec7a1af 100644 --- a/compiler+runtime/src/cpp/main.cpp +++ b/compiler+runtime/src/cpp/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -11,10 +12,6 @@ #include -#include -#include -#include - #include #include #include @@ -27,6 +24,8 @@ #include #include #include +#include +#include #include #include @@ -38,101 +37,11 @@ namespace jank { using namespace jank; using namespace jank::runtime; - using namespace ftxui; - //auto header = [&](std::string const &title) { - // auto const padding_count(80 - 2 - title.size()); - // std::string padding; - // for(size_t i{}; i < padding_count; ++i) - // { - // padding.insert(padding.size(), "─"); - // } - // return hbox({ - // text("─ "), - // text(title) | color(Color::BlueLight), - // text(" "), - // text(padding), - // }); - //}; - - //auto error = [&] { - // return vbox({ header("analysis/unresolved-symbol"), - // hbox({ - // text("error: ") | bold | color(Color::Red), - // text("Unable to resolve symbol 'sleep'") | bold, - // }) }); - //}; - - //auto snippet = [&] { - // auto content - // = hbox({ vbox({ - // text("3"), - // text("4"), - // text("5"), - // text(" "), - // }) | color(Color::GrayLight), - // separator(), - // vbox(hbox({ - // text("("), - // text("defn ") | color(Color::Orange1), - // text("nap ") | color(Color::Green), - // text("[]"), - // }), - // hbox({ - // text(" ("), - // text("let ") | color(Color::Orange1), - // text("["), - // text("minutes "), - // text("5") | color(Color::Magenta), - // text("]"), - // }), - // hbox({ - // text(" ("), - // text("sleep "), - // text("minutes)))"), - // }), - // text(" ^^^^^ unable to resolve symbol") | color(Color::Red)) }); - // return window(text(" src/kitten/nap.jank:5:6 "), content); - //}; - - //auto context = [&] { - // return vbox( - // { header("context"), - // vbox({ - // hbox({ text("compiling module "), text("kitten.main") | color(Color::Yellow) }), - // hbox({ - // text("└─ requiring module "), - // text("kitten.nap") | color(Color::Yellow), - - // }), - // hbox({ - // text(" └─ compiling function "), - // text("kitten.nap/") | color(Color::Yellow), - // text("nap") | color(Color::Green), - // }), - // }) }); - //}; - - //auto document = vbox({ - // error(), - // text("\n"), - // snippet(), - // text("\n"), - // context(), - //}); - - //// Limit the size of the document to 80 char. - //document = document | size(WIDTH, LESS_THAN, 80); - - //auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); - //Render(screen, document); - - //std::cout << screen.ToString() << '\0' << std::endl; - - //{ - // profile::timer const timer{ "load clojure.core" }; - // __rt_ctx->load_module("/clojure.core", module::origin::latest).expect_ok(); - //} + { + profile::timer const timer{ "load clojure.core" }; + __rt_ctx->load_module("/clojure.core", module::origin::latest).expect_ok(); + } { profile::timer const timer{ "eval user code" }; @@ -232,6 +141,9 @@ namespace jank le.setPrompt(get_prompt("=> ")); native_transient_string input{}; + auto const tmp{ boost::filesystem::temp_directory_path() }; + auto const path{ tmp / boost::filesystem::unique_path("jank-repl-%%%%.jank") }; + /* TODO: Completion. */ /* TODO: Syntax highlighting. */ while(auto buf = le.readLine()) @@ -242,15 +154,22 @@ namespace jank if(line.ends_with("\\")) { input.append(line.substr(0, line.size() - 1)); + input.append("\n"); le.setPrompt(get_prompt("=>... ")); continue; } input += line; + util::scope_exit const finally{ [&] { boost::filesystem::remove(path); } }; try { - auto const res(__rt_ctx->eval_string(input)); + { + std::ofstream ofs{ path.string() }; + ofs << input; + } + + auto const res(__rt_ctx->eval_file(path.c_str())); fmt::println("{}", runtime::to_code_string(res)); } /* TODO: Unify error handling. JEEZE! */ @@ -268,10 +187,11 @@ namespace jank } catch(jank::error_ptr const &e) { - fmt::println("Exception: {}", e->message); + jank::error::report(e); } input.clear(); + std::cout << "\n"; le.setPrompt(get_prompt("=> ")); } } @@ -335,7 +255,7 @@ namespace jank } catch(jank::error_ptr const &e) { - fmt::println("Exception: {}", e->message); + jank::error::report(e); } input.clear(); @@ -423,7 +343,7 @@ catch(jank::native_persistent_string const &s) } catch(jank::error_ptr const &e) { - fmt::println("Exception: {}", e->message); + jank::error::report(e); } catch(...) {