diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt index d99056b74a1..59f4133df83 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt @@ -17,6 +17,7 @@ set(SOURCES Parser/SpecParser.cpp Parser/TextParser.cpp Parser/XMLUtils.cpp + DiagnosticEngine.cpp Function.cpp main.cpp ) diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/DiagnosticEngine.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/DiagnosticEngine.cpp new file mode 100644 index 00000000000..0c65852b8a7 --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/DiagnosticEngine.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "DiagnosticEngine.h" + +namespace JSSpecCompiler { + +bool DiagnosticEngine::has_fatal_errors() const +{ + return m_has_fatal_errors; +} + +void DiagnosticEngine::print_diagnostics() +{ + auto use_color = isatty(STDERR_FILENO) ? UseColor::Yes : UseColor::No; + + StringBuilder builder; + for (auto const& diagnostic : m_diagnostics) + diagnostic.format_into(builder, use_color); + + out(stderr, "{}", builder.string_view()); +} + +void DiagnosticEngine::Diagnostic::format_into(StringBuilder& builder, UseColor use_color) const +{ + if (!location.filename.is_empty()) + builder.appendff("{}:{}:{}: ", location.filename, location.line + 1, location.column + 1); + + static constexpr Array colored_diagnostic_levels = { { + "\e[1mnote\e[0m"sv, + "\e[1;33mwarning\e[0m"sv, + "\e[1;31merror\e[0m"sv, + "\e[1;31mfatal error\e[0m"sv, + } }; + static constexpr Array diagnostic_levels = { { + "note"sv, + "warning"sv, + "error"sv, + "fatal error"sv, + } }; + + auto diagnostic_level_text = (use_color == UseColor::Yes ? colored_diagnostic_levels : diagnostic_levels); + builder.appendff("{}: ", diagnostic_level_text[to_underlying(level)]); + + if (auto logical_location = location.logical_location) { + if (!logical_location->section.is_empty()) { + builder.appendff("in {}", logical_location->section); + if (!logical_location->step.is_empty()) + builder.appendff(" step {}", logical_location->step); + builder.appendff(": "); + } + } + + builder.append(message); + builder.append('\n'); + + for (auto const& note : notes) + note.format_into(builder, use_color); +} + +void DiagnosticEngine::add_diagnostic(Diagnostic&& diagnostic) +{ + if (diagnostic.level == DiagnosticLevel::FatalError) + m_has_fatal_errors = true; + if (diagnostic.level != DiagnosticLevel::Note) + m_diagnostics.append(move(diagnostic)); + else + m_diagnostics.last().notes.append(move(diagnostic)); +} + +} diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/DiagnosticEngine.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/DiagnosticEngine.h new file mode 100644 index 00000000000..3b6f2b29c6b --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/DiagnosticEngine.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace JSSpecCompiler { + +struct LogicalLocation : RefCounted { + String section; + String step; +}; + +struct Location { + StringView filename; + size_t line = 0; + size_t column = 0; + RefPtr logical_location; + + static Location global_scope() { return {}; } +}; + +class DiagnosticEngine { + AK_MAKE_NONCOPYABLE(DiagnosticEngine); + AK_MAKE_NONMOVABLE(DiagnosticEngine); + +public: + DiagnosticEngine() = default; + +#define DEFINE_DIAGNOSTIC_FUNCTION(name_, level_) \ + template \ + void name_(Location const& location, AK::CheckedFormatString&& fmtstr, Parameters const&... parameters) \ + { \ + add_diagnostic({ \ + .location = location, \ + .level = DiagnosticLevel::level_, \ + .message = MUST(String::formatted(move(fmtstr), parameters...)), \ + }); \ + } + + DEFINE_DIAGNOSTIC_FUNCTION(note, Note) + DEFINE_DIAGNOSTIC_FUNCTION(warn, Warning) + DEFINE_DIAGNOSTIC_FUNCTION(error, Error) + DEFINE_DIAGNOSTIC_FUNCTION(fatal_error, FatalError) + +#undef DEFINE_DIAGNOSTIC_FUNCTION + + bool has_fatal_errors() const; + void print_diagnostics(); + +private: + enum class DiagnosticLevel { + Note, + Warning, + Error, + FatalError, + }; + + enum class UseColor { + No, + Yes, + }; + + struct Diagnostic { + Location location; + DiagnosticLevel level; + String message; + + Vector notes; + + bool operator<(Diagnostic const& other) const; + + void format_into(StringBuilder& builder, UseColor) const; + }; + + void add_diagnostic(Diagnostic&& diagnostic); + + Vector m_diagnostics; + bool m_has_fatal_errors = false; +}; + +} diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h index c79edde2ec0..0623764ad54 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h @@ -64,6 +64,11 @@ class AlgorithmStepList; class Algorithm; class SpecFunction; +// DiagnosticEngine.h +struct LogicalLocation; +struct Location; +class DiagnosticEngine; + // Function.h class TranslationUnit; using TranslationUnitRef = TranslationUnit*; diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Function.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Function.h index 7e88d452f0c..ec6259b63f7 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Function.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Function.h @@ -11,6 +11,7 @@ #include #include +#include "DiagnosticEngine.h" #include "Forward.h" namespace JSSpecCompiler { @@ -26,10 +27,12 @@ public: FunctionDeclarationRef find_declaration_by_name(StringView name) const; StringView filename() const { return m_filename; } + DiagnosticEngine& diag() { return m_diagnostic_engine; } Vector functions_to_compile() const { return m_functions_to_compile; } private: StringView m_filename; + DiagnosticEngine m_diagnostic_engine; Vector m_functions_to_compile; Vector> m_declarations_owner; HashMap m_function_index; diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.cpp index e840d7cda0d..0898ade0202 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.cpp @@ -16,6 +16,38 @@ namespace JSSpecCompiler { +DiagnosticEngine& SpecificationParsingContext::diag() +{ + return m_translation_unit->diag(); +} + +template +auto SpecificationParsingContext::with_new_logical_scope(Func&& func) +{ + TemporaryChange> change(m_current_logical_scope, make_ref_counted()); + return func(); +} + +LogicalLocation& SpecificationParsingContext::current_logical_scope() +{ + return *m_current_logical_scope; +} + +Location SpecificationParsingContext::file_scope() const +{ + return { .filename = m_translation_unit->filename() }; +} + +Location SpecificationParsingContext::location_from_xml_offset(XML::Offset offset) const +{ + return { + .filename = m_translation_unit->filename(), + .line = offset.line, + .column = offset.column, + .logical_location = m_current_logical_scope, + }; +} + ParseErrorOr AlgorithmStep::create(XML::Node const* node) { VERIFY(node->as_element().name == tag_li); @@ -185,14 +217,35 @@ SpecParsingStep::~SpecParsingStep() = default; void SpecParsingStep::run(TranslationUnitRef translation_unit) { + SpecificationParsingContext ctx(translation_unit); auto filename = translation_unit->filename(); - auto file = Core::File::open_file_or_standard_stream(filename, Core::File::OpenMode::Read).release_value_but_fixme_should_propagate_errors(); - m_input = file->read_until_eof().release_value_but_fixme_should_propagate_errors(); + auto file_or_error = Core::File::open_file_or_standard_stream(filename, Core::File::OpenMode::Read); + if (file_or_error.is_error()) { + ctx.diag().fatal_error(Location::global_scope(), + "unable to open '{}': {}", filename, file_or_error.error()); + return; + } + + auto input_or_error = file_or_error.value()->read_until_eof(); + if (input_or_error.is_error()) { + ctx.diag().fatal_error(Location::global_scope(), + "unable to read '{}': {}", filename, input_or_error.error()); + return; + } + m_input = input_or_error.release_value(); XML::Parser parser { m_input }; - auto document = parser.parse().release_value_but_fixme_should_propagate_errors(); - m_document = AK::adopt_own_if_nonnull(new XML::Document(move(document))); + auto document_or_error = parser.parse(); + if (document_or_error.is_error()) { + ctx.diag().fatal_error(ctx.file_scope(), + "XML::Parser failed to parse input: {}", document_or_error.error()); + ctx.diag().note(ctx.file_scope(), + "since XML::Parser backtracks on error, the message above is likely to point to the " + "first tag in the input - use external XML verifier to find out the exact cause of error"); + return; + } + m_document = make(document_or_error.release_value()); auto spec_function = SpecFunction::create(&m_document->root()).release_value_but_fixme_should_propagate_errors(); diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.h index 4f00e103021..7e6df0d1d1d 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.h @@ -15,6 +15,31 @@ #include "Parser/Token.h" namespace JSSpecCompiler { + +class SpecificationParsingContext { + AK_MAKE_NONCOPYABLE(SpecificationParsingContext); + AK_MAKE_NONMOVABLE(SpecificationParsingContext); + +public: + SpecificationParsingContext(TranslationUnitRef translation_unit) + : m_translation_unit(translation_unit) + { + } + + DiagnosticEngine& diag(); + + template + auto with_new_logical_scope(Func&& func); + LogicalLocation& current_logical_scope(); + + Location file_scope() const; + Location location_from_xml_offset(XML::Offset offset) const; + +private: + TranslationUnitRef m_translation_unit; + RefPtr m_current_logical_scope; +}; + class AlgorithmStepList { public: static ParseErrorOr create(XML::Node::Element const& element); diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp index 814c4afa75e..e39225325ac 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp @@ -136,6 +136,11 @@ ErrorOr serenity_main(Main::Arguments arguments) for (auto const& step : pipeline.pipeline()) { step.step->run(&translation_unit); + if (translation_unit.diag().has_fatal_errors()) { + translation_unit.diag().print_diagnostics(); + return 1; + } + if (step.dump_ast) { outln(stderr, "===== AST after {} =====", step.step->name()); for (auto const& function : translation_unit.functions_to_compile()) { @@ -152,5 +157,7 @@ ErrorOr serenity_main(Main::Arguments arguments) } } + translation_unit.diag().print_diagnostics(); + return 0; }