mirror of
https://github.com/RGBCube/serenity
synced 2025-05-17 19:05:07 +00:00
JSSpecCompiler: Add converter from LibCpp's AST
This will effectively allow us to use C++ code as an input for the compiler. This would be useful for testing, since otherwise we would have had to specify tests as a spec-like XML, which is not exactly the most developer-friendly experience.
This commit is contained in:
parent
567b1f6e7c
commit
75fd28014c
3 changed files with 214 additions and 1 deletions
|
@ -5,6 +5,7 @@ set(SOURCES
|
||||||
Compiler/GenericASTPass.cpp
|
Compiler/GenericASTPass.cpp
|
||||||
Compiler/IfBranchMergingPass.cpp
|
Compiler/IfBranchMergingPass.cpp
|
||||||
Compiler/ReferenceResolvingPass.cpp
|
Compiler/ReferenceResolvingPass.cpp
|
||||||
|
Parser/CppASTConverter.cpp
|
||||||
Parser/Lexer.cpp
|
Parser/Lexer.cpp
|
||||||
Parser/ParseError.cpp
|
Parser/ParseError.cpp
|
||||||
Parser/SpecParser.cpp
|
Parser/SpecParser.cpp
|
||||||
|
@ -14,6 +15,6 @@ set(SOURCES
|
||||||
main.cpp
|
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_include_directories(JSSpecCompiler PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
target_compile_options(JSSpecCompiler PRIVATE -Wno-missing-field-initializers)
|
target_compile_options(JSSpecCompiler PRIVATE -Wno-missing-field-initializers)
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Parser/CppASTConverter.h"
|
||||||
|
#include "Function.h"
|
||||||
|
#include "Parser/SpecParser.h"
|
||||||
|
|
||||||
|
namespace JSSpecCompiler {
|
||||||
|
|
||||||
|
NonnullRefPtr<FunctionDefinition> CppASTConverter::convert()
|
||||||
|
{
|
||||||
|
StringView name = m_function->name()->full_name();
|
||||||
|
|
||||||
|
Vector<Tree> 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<TreeList>(move(toplevel_statements));
|
||||||
|
|
||||||
|
return make_ref_counted<FunctionDefinition>(name, tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
NullableTree CppASTConverter::convert_node(Cpp::VariableDeclaration const& variable_declaration)
|
||||||
|
{
|
||||||
|
static Tree variable_declaration_present_error
|
||||||
|
= make_ref_counted<ErrorNode>("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<ReturnNode>(as_tree(return_statement.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
NullableTree CppASTConverter::convert_node(Cpp::FunctionCall const& function_call)
|
||||||
|
{
|
||||||
|
Vector<Tree> arguments;
|
||||||
|
for (auto const& argument : function_call.arguments())
|
||||||
|
arguments.append(as_tree(argument));
|
||||||
|
|
||||||
|
return make_ref_counted<FunctionCall>(as_tree(function_call.callee()), move(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
NullableTree CppASTConverter::convert_node(Cpp::Name const& name)
|
||||||
|
{
|
||||||
|
return make_ref_counted<UnresolvedReference>(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<Tree> 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<IfBranch>(predicate, then_branch));
|
||||||
|
else
|
||||||
|
trees.append(make_ref_counted<ElseIfBranch>(predicate, then_branch));
|
||||||
|
|
||||||
|
auto else_statement = dynamic_cast<Cpp::IfStatement const*>(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<ElseIfBranch>(
|
||||||
|
nullptr, as_possibly_empty_tree(else_statement)));
|
||||||
|
|
||||||
|
return make_ref_counted<TreeList>(move(trees));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
NullableTree CppASTConverter::convert_node(Cpp::BlockStatement const& block)
|
||||||
|
{
|
||||||
|
Vector<Tree> 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<TreeList>(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<BinaryOperation>(
|
||||||
|
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<MathematicalConstant>(literal.value().to_int<i64>().value());
|
||||||
|
}
|
||||||
|
|
||||||
|
NullableTree CppASTConverter::as_nullable_tree(Cpp::Statement const* statement)
|
||||||
|
{
|
||||||
|
static Tree unknown_ast_node_error
|
||||||
|
= make_ref_counted<ErrorNode>("Encountered unknown C++ AST node"sv);
|
||||||
|
|
||||||
|
Optional<NullableTree> result;
|
||||||
|
|
||||||
|
auto dispatch_convert_if_one_of = [&]<typename... Ts> {
|
||||||
|
(([&]<typename T> {
|
||||||
|
if (result.has_value())
|
||||||
|
return;
|
||||||
|
auto casted_ptr = dynamic_cast<T const*>(statement);
|
||||||
|
if (casted_ptr != nullptr)
|
||||||
|
result = convert_node<T>(*casted_ptr);
|
||||||
|
}).template operator()<Ts>(),
|
||||||
|
...);
|
||||||
|
};
|
||||||
|
|
||||||
|
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<ErrorNode>("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<TreeList>(Vector<Tree> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibCpp/AST.h>
|
||||||
|
|
||||||
|
#include "Forward.h"
|
||||||
|
|
||||||
|
namespace JSSpecCompiler {
|
||||||
|
|
||||||
|
class CppASTConverter {
|
||||||
|
public:
|
||||||
|
CppASTConverter(RefPtr<Cpp::FunctionDeclaration> const& function)
|
||||||
|
: m_function(function)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NonnullRefPtr<FunctionDefinition> convert();
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<typename T>
|
||||||
|
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<Cpp::FunctionDeclaration> m_function;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue