diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt index 6c823e33bf..00dcd110dc 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES Compiler/GenericASTPass.cpp Compiler/IfBranchMergingPass.cpp Compiler/ReferenceResolvingPass.cpp + Parser/CppASTConverter.cpp Parser/Lexer.cpp Parser/ParseError.cpp Parser/SpecParser.cpp @@ -14,6 +15,6 @@ set(SOURCES main.cpp ) -lagom_tool(JSSpecCompiler LIBS LibMain LibXML) +lagom_tool(JSSpecCompiler LIBS LibCpp LibMain LibXML) target_include_directories(JSSpecCompiler PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_options(JSSpecCompiler PRIVATE -Wno-missing-field-initializers) diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/CppASTConverter.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/CppASTConverter.cpp new file mode 100644 index 0000000000..ace76bba91 --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/CppASTConverter.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Parser/CppASTConverter.h" +#include "Function.h" +#include "Parser/SpecParser.h" + +namespace JSSpecCompiler { + +NonnullRefPtr CppASTConverter::convert() +{ + StringView name = m_function->name()->full_name(); + + Vector toplevel_statements; + for (auto const& statement : m_function->definition()->statements()) { + auto maybe_tree = as_nullable_tree(statement); + if (maybe_tree) + toplevel_statements.append(maybe_tree.release_nonnull()); + } + auto tree = make_ref_counted(move(toplevel_statements)); + + return make_ref_counted(name, tree); +} + +template<> +NullableTree CppASTConverter::convert_node(Cpp::VariableDeclaration const& variable_declaration) +{ + static Tree variable_declaration_present_error + = make_ref_counted("Encountered variable declaration with initial value"sv); + + if (variable_declaration.initial_value() != nullptr) + return variable_declaration_present_error; + return nullptr; +} + +template<> +NullableTree CppASTConverter::convert_node(Cpp::ReturnStatement const& return_statement) +{ + return make_ref_counted(as_tree(return_statement.value())); +} + +template<> +NullableTree CppASTConverter::convert_node(Cpp::FunctionCall const& function_call) +{ + Vector arguments; + for (auto const& argument : function_call.arguments()) + arguments.append(as_tree(argument)); + + return make_ref_counted(as_tree(function_call.callee()), move(arguments)); +} + +template<> +NullableTree CppASTConverter::convert_node(Cpp::Name const& name) +{ + return make_ref_counted(name.full_name()); +} + +template<> +NullableTree CppASTConverter::convert_node(Cpp::IfStatement const& if_statement) +{ + // NOTE: This is so complicated since we probably want to test IfBranchMergingPass, which + // expects standalone `IfBranch` and `ElseIfBranch` nodes. + + Vector trees; + Cpp::IfStatement const* current = &if_statement; + + while (true) { + auto predicate = as_tree(current->predicate()); + auto then_branch = as_possibly_empty_tree(current->then_statement()); + + if (trees.is_empty()) + trees.append(make_ref_counted(predicate, then_branch)); + else + trees.append(make_ref_counted(predicate, then_branch)); + + auto else_statement = dynamic_cast(current->else_statement()); + if (else_statement) + current = else_statement; + else + break; + } + + auto else_statement = current->else_statement(); + if (else_statement) + trees.append(make_ref_counted( + nullptr, as_possibly_empty_tree(else_statement))); + + return make_ref_counted(move(trees)); +} + +template<> +NullableTree CppASTConverter::convert_node(Cpp::BlockStatement const& block) +{ + Vector statements; + for (auto const& statement : block.statements()) { + auto maybe_tree = as_nullable_tree(statement); + if (maybe_tree) + statements.append(maybe_tree.release_nonnull()); + } + return make_ref_counted(move(statements)); +} + +template<> +NullableTree CppASTConverter::convert_node(Cpp::AssignmentExpression const& assignment) +{ + // NOTE: Later stages of the compilation process basically treat `BinaryOperator::Declaration` + // the same as `BinaryOperator::Assignment`, so variable shadowing is impossible. The only + // difference in their semantics is that "declarations" define names of local variables. + // Since we are effectively ignoring actual C++ variable declarations, we need to define + // locals somewhere else. Using "declarations" instead of "assignments" here does this job + // cleanly. + return make_ref_counted( + BinaryOperator::Declaration, as_tree(assignment.lhs()), as_tree(assignment.rhs())); +} + +template<> +NullableTree CppASTConverter::convert_node(Cpp::NumericLiteral const& literal) +{ + // TODO: Numerical literals are not limited to i64. + return make_ref_counted(literal.value().to_int().value()); +} + +NullableTree CppASTConverter::as_nullable_tree(Cpp::Statement const* statement) +{ + static Tree unknown_ast_node_error + = make_ref_counted("Encountered unknown C++ AST node"sv); + + Optional result; + + auto dispatch_convert_if_one_of = [&] { + (([&] { + if (result.has_value()) + return; + auto casted_ptr = dynamic_cast(statement); + if (casted_ptr != nullptr) + result = convert_node(*casted_ptr); + }).template operator()(), + ...); + }; + + dispatch_convert_if_one_of.operator()< + Cpp::VariableDeclaration, + Cpp::ReturnStatement, + Cpp::FunctionCall, + Cpp::Name, + Cpp::IfStatement, + Cpp::BlockStatement, + Cpp::AssignmentExpression, + Cpp::NumericLiteral>(); + + if (result.has_value()) + return *result; + return unknown_ast_node_error; +} + +Tree CppASTConverter::as_tree(Cpp::Statement const* statement) +{ + static Tree empty_tree_error + = make_ref_counted("AST conversion unexpectedly produced empty tree"sv); + + auto result = as_nullable_tree(statement); + if (result) + return result.release_nonnull(); + return empty_tree_error; +} + +Tree CppASTConverter::as_possibly_empty_tree(Cpp::Statement const* statement) +{ + auto result = as_nullable_tree(statement); + if (result) + return result.release_nonnull(); + return make_ref_counted(Vector {}); +} + +} diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/CppASTConverter.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/CppASTConverter.h new file mode 100644 index 0000000000..b1e0be5aea --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/CppASTConverter.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +#include "Forward.h" + +namespace JSSpecCompiler { + +class CppASTConverter { +public: + CppASTConverter(RefPtr const& function) + : m_function(function) + { + } + + NonnullRefPtr convert(); + +private: + template + NullableTree convert_node(T const&); + NullableTree as_nullable_tree(Cpp::Statement const* statement); + Tree as_tree(Cpp::Statement const* statement); + Tree as_possibly_empty_tree(Cpp::Statement const* statement); + + RefPtr m_function; +}; + +}