Everywhere: Remove a lot more things we don't need

This commit is contained in:
Andreas Kling 2024-06-02 20:44:05 +02:00
commit e70d96e4e7
Notes: sideshowbarker 2024-07-17 04:09:56 +09:00
779 changed files with 3 additions and 122585 deletions

5
.github/CODEOWNERS vendored
View file

@ -3,20 +3,15 @@
/AK/*Stream.* @timschumi /AK/*Stream.* @timschumi
/Lagom/Tools/CodeGenerators/LibWeb @AtkinsSJ /Lagom/Tools/CodeGenerators/LibWeb @AtkinsSJ
/Tests/LibCompress @timschumi /Tests/LibCompress @timschumi
/Toolchain @BertalanD
/Userland/Libraries/LibArchive @timschumi /Userland/Libraries/LibArchive @timschumi
/Userland/Libraries/LibCompress @timschumi /Userland/Libraries/LibCompress @timschumi
/Userland/Libraries/LibCore/File.* @timschumi /Userland/Libraries/LibCore/File.* @timschumi
/Userland/Libraries/LibCore/Socket.* @timschumi /Userland/Libraries/LibCore/Socket.* @timschumi
/Userland/Libraries/LibCrypto @alimpfard /Userland/Libraries/LibCrypto @alimpfard
/Userland/Libraries/LibELF @BertalanD
/Userland/Libraries/LibGL @GMTA
/Userland/Libraries/LibGPU @GMTA
/Userland/Libraries/LibHTTP @alimpfard /Userland/Libraries/LibHTTP @alimpfard
/Userland/Libraries/LibJS/Runtime/Intl @trflynn89 /Userland/Libraries/LibJS/Runtime/Intl @trflynn89
/Userland/Libraries/LibLocale @trflynn89 /Userland/Libraries/LibLocale @trflynn89
/Userland/Libraries/LibRegex @alimpfard /Userland/Libraries/LibRegex @alimpfard
/Userland/Libraries/LibSoftGPU @GMTA
/Userland/Libraries/LibSQL @GMTA @trflynn89 /Userland/Libraries/LibSQL @GMTA @trflynn89
/Userland/Libraries/LibTLS @alimpfard /Userland/Libraries/LibTLS @alimpfard
/Userland/Libraries/LibTimeZone @trflynn89 /Userland/Libraries/LibTimeZone @trflynn89

View file

@ -1,121 +0,0 @@
/*
* Copyright (c) 2023, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Array.h>
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <AK/TypedTransfer.h>
#include <AK/Userspace.h>
#ifdef KERNEL
# include <Kernel/Arch/SafeMem.h>
# include <Kernel/Arch/SmapDisabler.h>
# include <Kernel/Memory/MemorySections.h>
#endif
namespace AK {
template<size_t Size>
class FixedStringBuffer {
public:
[[nodiscard]] static ErrorOr<FixedStringBuffer<Size>> vformatted(StringView fmtstr, AK::TypeErasedFormatParams& params)
requires(Size < StringBuilder::inline_capacity)
{
StringBuilder builder { StringBuilder::UseInlineCapacityOnly::Yes };
TRY(AK::vformat(builder, fmtstr, params));
FixedStringBuffer<Size> buffer {};
buffer.store_characters(builder.string_view());
return buffer;
}
template<typename... Parameters>
[[nodiscard]] static ErrorOr<FixedStringBuffer<Size>> formatted(CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters)
requires(Size < StringBuilder::inline_capacity)
{
AK::VariadicFormatParams<AK::AllowDebugOnlyFormatters::No, Parameters...> variadic_format_parameters { parameters... };
return vformatted(fmtstr.view(), variadic_format_parameters);
}
void store_characters(StringView characters)
{
// NOTE: Only store the characters up to the first null terminator
// because we don't care about any further characters.
// This matches some expected behavior in the Kernel code, because
// technically userspace programs could send a syscall argument with
// multiple null terminators - we only care about the *first* chunk up to
// the first null terminator, if present at all.
size_t stored_length = 0;
for (; stored_length < min(Size, characters.length()); stored_length++) {
if (characters[stored_length] == '\0')
break;
m_storage[stored_length] = characters[stored_length];
}
m_stored_length = stored_length;
// NOTE: Fill the rest of the array bytes with zeroes, just to be
// on the safe side.
// Technically, it means that a sent StringView could occupy the
// entire storage without any null terminators and that's OK as well.
for (size_t index = m_stored_length; index < Size; index++)
m_storage[index] = '\0';
}
#ifdef KERNEL
ErrorOr<void> copy_characters_from_user(Userspace<char const*> user_str, size_t user_str_size)
{
if (user_str_size > Size)
return EFAULT;
bool is_user = Kernel::Memory::is_user_range(user_str.vaddr(), user_str_size);
if (!is_user)
return EFAULT;
Kernel::SmapDisabler disabler;
void* fault_at;
ssize_t length = Kernel::safe_strnlen(user_str.unsafe_userspace_ptr(), user_str_size, fault_at);
if (length < 0) {
dbgln("FixedStringBuffer::copy_characters_into_storage({:p}, {}) failed at {} (strnlen)", static_cast<void const*>(user_str.unsafe_userspace_ptr()), user_str_size, VirtualAddress { fault_at });
return EFAULT;
}
if (!Kernel::safe_memcpy(m_storage.data(), user_str.unsafe_userspace_ptr(), (size_t)length, fault_at)) {
dbgln("FixedStringBuffer::copy_characters_into_storage({:p}, {}) failed at {} (memcpy)", static_cast<void const*>(user_str.unsafe_userspace_ptr()), user_str_size, VirtualAddress { fault_at });
return EFAULT;
}
m_stored_length = (size_t)length;
for (size_t index = m_stored_length; index < Size; index++)
m_storage[index] = '\0';
return {};
}
#endif
Span<u8> storage()
{
return m_storage.span();
}
StringView representable_view() const { return StringView(m_storage.data(), m_stored_length); }
Span<u8 const> span_view_ensuring_ending_null_char()
{
VERIFY(m_stored_length + 1 <= Size);
m_storage[m_stored_length] = '\0';
return Span<u8 const>(m_storage.data(), m_stored_length + 1);
}
size_t stored_length() const { return m_stored_length; }
FixedStringBuffer()
{
m_storage.fill(0);
}
private:
Array<u8, Size> m_storage;
size_t m_stored_length { 0 };
};
}
#if USING_AK_GLOBALLY
using AK::FixedStringBuffer;
#endif

View file

@ -1,162 +0,0 @@
/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Atomic.h>
#include <AK/Noncopyable.h>
#include <AK/StdLibExtras.h>
#include <AK/Types.h>
namespace AK::UBSanitizer {
extern Atomic<bool> g_ubsan_is_deadly;
typedef void* ValueHandle;
class SourceLocation {
AK_MAKE_NONCOPYABLE(SourceLocation);
public:
char const* filename() const { return m_filename; }
u32 line() const { return m_line; }
u32 column() const { return m_column; }
// Replace the location information in the .data segment with one that won't be logged in the future
// Using this method prevents log spam when sanitizers are not deadly by not logging the exact same
// code paths multiple times.
SourceLocation permanently_clear() { return move(*this); }
bool needs_logging() const { return !(m_filename == nullptr); }
SourceLocation() = default;
SourceLocation(SourceLocation&& other)
: m_filename(other.m_filename)
, m_line(other.m_line)
, m_column(other.m_column)
{
other = {};
}
SourceLocation& operator=(SourceLocation&& other)
{
if (this != &other) {
m_filename = exchange(other.m_filename, nullptr);
m_line = exchange(other.m_line, 0);
m_column = exchange(other.m_column, 0);
}
return *this;
}
private:
char const* m_filename { nullptr };
u32 m_line { 0 };
u32 m_column { 0 };
};
enum TypeKind : u16 {
Integer = 0,
Float = 1,
Unknown = 0xffff,
};
class TypeDescriptor {
public:
char const* name() const { return m_name; }
TypeKind kind() const { return (TypeKind)m_kind; }
bool is_integer() const { return kind() == TypeKind::Integer; }
bool is_signed() const { return m_info & 1; }
bool is_unsigned() const { return !is_signed(); }
size_t bit_width() const { return 1 << (m_info >> 1); }
private:
u16 m_kind;
u16 m_info;
char m_name[1];
};
struct InvalidValueData {
SourceLocation location;
TypeDescriptor const& type;
};
struct NonnullArgData {
SourceLocation location;
SourceLocation attribute_location;
int argument_index;
};
struct NonnullReturnData {
SourceLocation attribute_location;
};
struct OverflowData {
SourceLocation location;
TypeDescriptor const& type;
};
struct VLABoundData {
SourceLocation location;
TypeDescriptor const& type;
};
struct ShiftOutOfBoundsData {
SourceLocation location;
TypeDescriptor const& lhs_type;
TypeDescriptor const& rhs_type;
};
struct OutOfBoundsData {
SourceLocation location;
TypeDescriptor const& array_type;
TypeDescriptor const& index_type;
};
struct TypeMismatchData {
SourceLocation location;
TypeDescriptor const& type;
u8 log_alignment;
u8 type_check_kind;
};
struct AlignmentAssumptionData {
SourceLocation location;
SourceLocation assumption_location;
TypeDescriptor const& type;
};
struct UnreachableData {
SourceLocation location;
};
struct ImplicitConversionData {
SourceLocation location;
TypeDescriptor const& from_type;
TypeDescriptor const& to_type;
/* ImplicitConversionCheckKind */ unsigned char kind;
};
struct InvalidBuiltinData {
SourceLocation location;
unsigned char kind;
};
struct PointerOverflowData {
SourceLocation location;
};
struct FunctionTypeMismatchData {
SourceLocation location;
TypeDescriptor const& type;
};
struct FloatCastOverflowData {
SourceLocation location;
TypeDescriptor const& from_type;
TypeDescriptor const& to_type;
};
}

View file

@ -1,79 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Assertions.h>
#include <AK/Types.h>
#ifdef KERNEL
# include <Kernel/Memory/VirtualAddress.h>
#endif
namespace AK {
template<typename T>
concept PointerTypeName = IsPointer<T>;
template<PointerTypeName T>
class Userspace {
public:
Userspace() = default;
// Disable default implementations that would use surprising integer promotion.
bool operator==(Userspace const&) const = delete;
bool operator<=(Userspace const&) const = delete;
bool operator>=(Userspace const&) const = delete;
bool operator<(Userspace const&) const = delete;
bool operator>(Userspace const&) const = delete;
#ifdef KERNEL
Userspace(FlatPtr ptr)
: m_ptr(ptr)
{
}
explicit operator bool() const { return m_ptr != 0; }
FlatPtr ptr() const { return m_ptr; }
VirtualAddress vaddr() const { return VirtualAddress(m_ptr); }
T unsafe_userspace_ptr() const { return reinterpret_cast<T>(m_ptr); }
#else
Userspace(T ptr)
: m_ptr(ptr)
{
}
explicit operator bool() const { return m_ptr != nullptr; }
T ptr() const { return m_ptr; }
#endif
private:
#ifdef KERNEL
FlatPtr m_ptr { 0 };
#else
T m_ptr { nullptr };
#endif
};
template<typename T, typename U>
inline Userspace<T> static_ptr_cast(Userspace<U> const& ptr)
{
#ifdef KERNEL
auto casted_ptr = static_cast<T>(ptr.unsafe_userspace_ptr());
#else
auto casted_ptr = static_cast<T>(ptr.ptr());
#endif
return Userspace<T>(reinterpret_cast<FlatPtr>(casted_ptr));
}
}
#if USING_AK_GLOBALLY
using AK::static_ptr_cast;
using AK::Userspace;
#endif

View file

@ -1,23 +0,0 @@
/*
* Copyright (c) 2024, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/FileSystem/CustodyBase.h>
#include <Kernel/FileSystem/VirtualFileSystem.h>
#include <Kernel/Library/KLexicalPath.h>
#include <Kernel/Tasks/Process.h>
namespace Kernel {
ErrorOr<NonnullRefPtr<Custody>> CustodyBase::resolve() const
{
if (m_base)
return *m_base;
if (KLexicalPath::is_absolute(m_path))
return VirtualFileSystem::the().root_custody();
return Process::current().custody_for_dirfd({}, m_dirfd);
}
}

View file

@ -1,47 +0,0 @@
/*
* Copyright (c) 2024, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
#include <AK/RefPtr.h>
#include <AK/StringView.h>
#include <Kernel/FileSystem/Custody.h>
namespace Kernel {
class CustodyBase {
public:
CustodyBase(int dirfd, StringView path)
: m_path(path)
, m_dirfd(dirfd)
{
}
CustodyBase(NonnullRefPtr<Custody> base)
: m_base(base)
{
}
CustodyBase(Custody& base)
: m_base(base)
{
}
CustodyBase(Custody const& base)
: m_base(base)
{
}
ErrorOr<NonnullRefPtr<Custody>> resolve() const;
private:
RefPtr<Custody> const m_base;
StringView m_path;
int m_dirfd { -1 };
};
}

View file

@ -1,61 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Format.h>
#include <AK/Types.h>
class VirtualAddress {
public:
VirtualAddress() = default;
constexpr explicit VirtualAddress(FlatPtr address)
: m_address(address)
{
}
explicit VirtualAddress(void const* address)
: m_address((FlatPtr)address)
{
}
[[nodiscard]] constexpr bool is_null() const { return m_address == 0; }
[[nodiscard]] constexpr bool is_page_aligned() const { return (m_address & 0xfff) == 0; }
[[nodiscard]] constexpr VirtualAddress offset(FlatPtr o) const { return VirtualAddress(m_address + o); }
[[nodiscard]] constexpr FlatPtr get() const { return m_address; }
void set(FlatPtr address) { m_address = address; }
void mask(FlatPtr m) { m_address &= m; }
bool operator<=(VirtualAddress const& other) const { return m_address <= other.m_address; }
bool operator>=(VirtualAddress const& other) const { return m_address >= other.m_address; }
bool operator>(VirtualAddress const& other) const { return m_address > other.m_address; }
bool operator<(VirtualAddress const& other) const { return m_address < other.m_address; }
bool operator==(VirtualAddress const& other) const { return m_address == other.m_address; }
bool operator!=(VirtualAddress const& other) const { return m_address != other.m_address; }
// NOLINTNEXTLINE(readability-make-member-function-const) const VirtualAddress shouldn't be allowed to modify the underlying memory
[[nodiscard]] u8* as_ptr() { return reinterpret_cast<u8*>(m_address); }
[[nodiscard]] u8 const* as_ptr() const { return reinterpret_cast<u8 const*>(m_address); }
[[nodiscard]] VirtualAddress page_base() const { return VirtualAddress(m_address & ~(FlatPtr)0xfffu); }
private:
FlatPtr m_address { 0 };
};
inline VirtualAddress operator-(VirtualAddress const& a, VirtualAddress const& b)
{
return VirtualAddress(a.get() - b.get());
}
template<>
struct AK::Formatter<VirtualAddress> : AK::Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder& builder, VirtualAddress const& value)
{
return AK::Formatter<FormatString>::format(builder, "V{}"sv, value.as_ptr());
}
};

View file

@ -29,22 +29,6 @@ function(stringify_gml source output string_name)
embed_as_string_view(${output_name} ${source} ${output} ${string_name}) embed_as_string_view(${output_name} ${source} ${output} ${string_name})
endfunction() endfunction()
function(compile_gml source output)
set(source ${CMAKE_CURRENT_SOURCE_DIR}/${source})
add_custom_command(
OUTPUT ${output}
COMMAND $<TARGET_FILE:Lagom::GMLCompiler> ${source} > ${output}.tmp
COMMAND "${CMAKE_COMMAND}" -E copy_if_different ${output}.tmp ${output}
COMMAND "${CMAKE_COMMAND}" -E remove ${output}.tmp
VERBATIM
DEPENDS Lagom::GMLCompiler
MAIN_DEPENDENCY ${source}
)
get_filename_component(output_name ${output} NAME)
add_custom_target(generate_${output_name} DEPENDS ${output})
add_dependencies(all_generated generate_${output_name})
endfunction()
function(compile_ipc source output) function(compile_ipc source output)
if (NOT IS_ABSOLUTE ${source}) if (NOT IS_ABSOLUTE ${source})
set(source ${CMAKE_CURRENT_SOURCE_DIR}/${source}) set(source ${CMAKE_CURRENT_SOURCE_DIR}/${source})

View file

@ -1,14 +0,0 @@
function (generate_libgl_implementation)
set(LIBGL_INPUT_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}")
invoke_generator(
"GLAPI.cpp"
Lagom::GenerateGLAPIWrapper
"${LIBGL_INPUT_FOLDER}/GLAPI.json"
"GL/glapi.h"
"GLAPI.cpp"
arguments -j "${LIBGL_INPUT_FOLDER}/GLAPI.json"
)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/GL/glapi.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/LibGL/GL/" OPTIONAL)
endfunction()

View file

@ -386,17 +386,6 @@ install(TARGETS LibTimeZone EXPORT LagomTargets)
# This is used by the BindingsGenerator so needs to always be built. # This is used by the BindingsGenerator so needs to always be built.
add_serenity_subdirectory(Userland/Libraries/LibIDL) add_serenity_subdirectory(Userland/Libraries/LibIDL)
# LibGUI - only GML
# This is used by the GML compiler and therefore always needed.
set(LIBGUI_GML_SOURCES
GML/Lexer.cpp
GML/Parser.cpp
)
list(TRANSFORM LIBGUI_GML_SOURCES PREPEND "${SERENITY_PROJECT_ROOT}/Userland/Libraries/LibGUI/")
lagom_lib(LibGUI_GML gui_gml
SOURCES ${LIBGUI_GML_SOURCES}
)
# Manually install AK headers # Manually install AK headers
install( install(
DIRECTORY "${SERENITY_PROJECT_ROOT}/AK" DIRECTORY "${SERENITY_PROJECT_ROOT}/AK"
@ -444,27 +433,20 @@ if (BUILD_LAGOM)
Archive Archive
Audio Audio
Compress Compress
Cpp
Crypto Crypto
Diff Diff
Gemini Gemini
Gfx Gfx
GL
GLSL
GPU
HTTP HTTP
ImageDecoderClient ImageDecoderClient
IPC IPC
JIT
JS JS
Line Line
Locale Locale
Markdown Markdown
PDF
Protocol Protocol
Regex Regex
RIFF RIFF
SoftGPU
SQL SQL
Syntax Syntax
TextCodec TextCodec
@ -489,10 +471,6 @@ if (BUILD_LAGOM)
compile_ipc(${SERENITY_PROJECT_ROOT}/Userland/Services/WebContent/WebDriverServer.ipc Userland/Services/WebContent/WebDriverServerEndpoint.h) compile_ipc(${SERENITY_PROJECT_ROOT}/Userland/Services/WebContent/WebDriverServer.ipc Userland/Services/WebContent/WebDriverServerEndpoint.h)
endif() endif()
if (NOT EMSCRIPTEN)
list(APPEND lagom_standard_libraries ELF X86)
endif()
foreach(lib IN LISTS lagom_standard_libraries) foreach(lib IN LISTS lagom_standard_libraries)
add_serenity_subdirectory("Userland/Libraries/Lib${lib}") add_serenity_subdirectory("Userland/Libraries/Lib${lib}")
endforeach() endforeach()
@ -518,10 +496,6 @@ if (BUILD_LAGOM)
add_serenity_subdirectory(Ladybird) add_serenity_subdirectory(Ladybird)
endif() endif()
if (APPLE)
add_serenity_subdirectory(Meta/Lagom/Contrib/MacPDF)
endif()
find_package(SDL2 QUIET) find_package(SDL2 QUIET)
if (SDL2_FOUND) if (SDL2_FOUND)
add_serenity_subdirectory(Meta/Lagom/Contrib/VideoPlayerSDL) add_serenity_subdirectory(Meta/Lagom/Contrib/VideoPlayerSDL)
@ -544,7 +518,6 @@ if (BUILD_LAGOM)
lagom_utility(lzcat SOURCES ../../Userland/Utilities/lzcat.cpp LIBS LibCompress LibMain) lagom_utility(lzcat SOURCES ../../Userland/Utilities/lzcat.cpp LIBS LibCompress LibMain)
lagom_utility(pdf SOURCES ../../Userland/Utilities/pdf.cpp LIBS LibGfx LibPDF LibMain)
lagom_utility(sql SOURCES ../../Userland/Utilities/sql.cpp LIBS LibFileSystem LibIPC LibLine LibMain LibSQL) lagom_utility(sql SOURCES ../../Userland/Utilities/sql.cpp LIBS LibFileSystem LibIPC LibLine LibMain LibSQL)
lagom_utility(tar SOURCES ../../Userland/Utilities/tar.cpp LIBS LibArchive LibCompress LibFileSystem LibMain) lagom_utility(tar SOURCES ../../Userland/Utilities/tar.cpp LIBS LibArchive LibCompress LibFileSystem LibMain)
lagom_utility(test262-runner SOURCES ../../Tests/LibJS/test262-runner.cpp LIBS LibJS LibFileSystem) lagom_utility(test262-runner SOURCES ../../Tests/LibJS/test262-runner.cpp LIBS LibJS LibFileSystem)
@ -591,14 +564,11 @@ if (BUILD_LAGOM)
# LibTest tests from Tests/ # LibTest tests from Tests/
set(TEST_DIRECTORIES set(TEST_DIRECTORIES
AK AK
JSSpecCompiler
LibCrypto LibCrypto
LibCompress LibCompress
LibGL
LibGfx LibGfx
LibLocale LibLocale
LibMarkdown LibMarkdown
LibPDF
LibSQL LibSQL
LibTest LibTest
LibTextCodec LibTextCodec

View file

@ -1,15 +0,0 @@
/*
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
// Several AK types conflict with MacOS types.
#define FixedPoint FixedPointMacOS
#import <Cocoa/Cocoa.h>
#undef FixedPoint
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end

View file

@ -1,47 +0,0 @@
/*
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#import "AppDelegate.h"
#include <LibCore/ResourceImplementationFile.h>
@interface AppDelegate ()
@property (strong) IBOutlet NSWindow* window;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
{
// FIXME: Copy fonts and icc file to the bundle or something
// Get from `Build/lagom/bin/MacPDF.app/Contents/MacOS/MacPDF` to `Build/lagom/Root/res`.
NSString* source_root = [[NSBundle mainBundle] executablePath];
for (int i = 0; i < 5; ++i)
source_root = [source_root stringByDeletingLastPathComponent];
auto source_root_string = ByteString([source_root UTF8String]);
Core::ResourceImplementation::install(make<Core::ResourceImplementationFile>(MUST(String::formatted("{}/Root/res", source_root_string))));
}
- (void)applicationWillTerminate:(NSNotification*)aNotification
{
}
- (BOOL)applicationSupportsSecureRestorableState:(NSApplication*)app
{
return YES;
}
- (BOOL)application:(NSApplication*)sender openFile:(NSString*)filename
{
[[NSDocumentController sharedDocumentController]
openDocumentWithContentsOfURL:[NSURL fileURLWithPath:filename]
display:YES
completionHandler:^(NSDocument*, BOOL, NSError*) {}];
return YES;
}
@end

View file

@ -1,58 +0,0 @@
# This has the effect of making LC_RPATH absolute.
# Since the binary is in Build/lagom/bin/MacPDF.app/Contents/MacOS/MacPDF,
# the default "@executable_path/../lib" doesn't work to get from the binary
# to Build/lagom/lib.
# FIXME: Pass "-Wl,-rpath,@executable_path/../../../../lib" instead for a relative path?
# Long-term, probably want to copy the dylibs into the bundle instead.
set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
add_compile_options(-DAK_DONT_REPLACE_STD)
set(RESOURCES
MainMenu.xib
)
add_executable(MacPDF MACOSX_BUNDLE
main.mm
AppDelegate.mm
MacPDFDocument.mm
MacPDFOutlineViewDataSource.mm
MacPDFView.mm
MacPDFWindowController.mm
)
target_compile_options(MacPDF PRIVATE
-fobjc-arc
)
target_link_libraries(MacPDF PRIVATE AK LibCore LibGfx LibPDF)
target_link_libraries(MacPDF PRIVATE
"-framework Cocoa"
"-framework UniformTypeIdentifiers"
)
set_target_properties(MacPDF PROPERTIES
MACOSX_BUNDLE TRUE
# FIXME: Apparently the Info.plist is only copied when the binary relinks,
# not if only the Info.plist contents changes and you rebuild?
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist"
)
# Normally you'd set `RESOURCE "${RESOURCES}"` on the MacPDF target properties
# and add `"${RESOURCES}" to the sources in add_executable()
# and CMake would add build steps to compile the xib files to nib files and
# add them to the bundle.
# But with CMake's ninja generator that seems to not work, so do it manually.
# See also https://github.com/dolphin-emu/dolphin/blob/2e39c79984490e/Source/Core/MacUpdater/CMakeLists.txt#L49-L56
find_program(IBTOOL ibtool HINTS "/usr/bin" "${OSX_DEVELOPER_ROOT}/usr/bin")
foreach(xib ${RESOURCES})
string(REGEX REPLACE "[.]xib$" ".nib" nib "${xib}")
# FIXME: This is gross! It makes the link at least as slow as compiling all xib files.
# Better to have a separate command for the compiles and to only do the copying in the postbuild.
add_custom_command(TARGET MacPDF POST_BUILD
COMMAND ${IBTOOL} --errors --warnings --notices --output-format human-readable-text
--compile "$<TARGET_BUNDLE_DIR:MacPDF>/Contents/Resources/${nib}"
"${CMAKE_CURRENT_SOURCE_DIR}/${xib}"
COMMENT "Compiling ${CMAKE_CURRENT_SOURCE_DIR}/${xib}.xib")
endforeach()

View file

@ -1,12 +0,0 @@
/*
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
// Several AK types conflict with MacOS types.
#define FixedPoint FixedPointMacOS
#import <Cocoa/Cocoa.h>
#undef FixedPoint

View file

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>PDF</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>com.adobe.pdf</string>
</array>
<key>NSDocumentClass</key>
<string>MacPDFDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>MacPDF</string>
<key>CFBundleIdentifier</key>
<string>org.serenityos.MacPDF</string>
<key>CFBundleName</key>
<string>MacPDF</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>LSMinimumSystemVersion</key>
<string>13.3</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticTermination</key>
<true/>
<key>NSSupportsSuddenTermination</key>
<true/>
</dict>
</plist>

View file

@ -1,22 +0,0 @@
/*
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "CocoaWrapper.h"
#import "MacPDFWindowController.h"
NS_ASSUME_NONNULL_BEGIN
@interface MacPDFDocument : NSDocument
- (PDF::Document*)pdf;
- (void)windowIsReady;
@end
NS_ASSUME_NONNULL_END

View file

@ -1,148 +0,0 @@
/*
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#import "MacPDFDocument.h"
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import "MacPDFWindowController.h"
#include <LibPDF/Document.h>
@interface MacPDFDocument ()
{
NSData* _data; // Strong, _doc refers to it.
RefPtr<PDF::Document> _doc;
MacPDFWindowController* _windowController;
}
@end
@implementation MacPDFDocument
- (PDF::Document*)pdf
{
return _doc;
}
- (void)promptForPassword:(NSWindow*)window
{
auto alert = [[NSAlert alloc] init];
alert.messageText = @"Password";
[alert addButtonWithTitle:@"OK"];
[alert addButtonWithTitle:@"Cancel"];
auto textField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)];
alert.accessoryView = textField;
alert.window.initialFirstResponder = textField;
// Without this, the window's not visible yet and the sheet can't attach to it.
// FIXME: This causes the window to change position after restoring, so this isn't quite right either.
// Probably nicest to put the password prompt right in the window, instead of in a sheet.
[window orderFront:self];
[alert beginSheetModalForWindow:window
completionHandler:^(NSModalResponse response) {
if (response == NSAlertFirstButtonReturn) {
NSString* password = [textField stringValue];
StringView password_view { [password UTF8String], strlen([password UTF8String]) };
if (!self->_doc->security_handler()->try_provide_user_password(password_view)) {
warnln("invalid password '{}'", password);
[self performSelector:@selector(promptForPassword:) withObject:window];
return;
}
[self initializePDF];
} else if (response == NSAlertSecondButtonReturn) {
[self close];
}
}];
}
- (PDF::PDFErrorOr<NonnullRefPtr<PDF::Document>>)load:(NSData*)data
{
// Runs on background thread, can't interact with UI.
auto document = TRY(PDF::Document::create(ReadonlyBytes { [data bytes], [data length] }));
return document;
}
- (void)initializePDF
{
// FIXME: on background thread?
if (auto err = _doc->initialize(); err.is_error()) {
// FIXME: show error?
NSLog(@"failed to load 2: %@", @(err.error().message().characters()));
} else {
[_windowController pdfDidInitialize];
}
}
- (void)makeWindowControllers
{
_windowController = [[MacPDFWindowController alloc] initWithDocument:self];
[self addWindowController:_windowController];
[self windowIsReady];
}
- (void)windowIsReady
{
if (_doc) {
if (auto handler = _doc->security_handler(); handler && !handler->has_user_password()) {
[self promptForPassword:_windowController.window];
return;
}
[self initializePDF];
}
}
- (NSData*)dataOfType:(NSString*)typeName error:(NSError**)outError
{
if (outError) {
*outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:nil];
}
return nil;
}
- (BOOL)readFromData:(NSData*)data ofType:(NSString*)typeName error:(NSError**)outError
{
if (![[UTType typeWithIdentifier:typeName] conformsToType:UTTypePDF]) {
if (outError) {
*outError = [NSError errorWithDomain:NSOSStatusErrorDomain
code:unimpErr
userInfo:nil];
}
return NO;
}
if (auto doc_or = [self load:data]; !doc_or.is_error()) {
_doc = doc_or.value();
_data = data;
return YES;
} else {
NSLog(@"failed to load: %@", @(doc_or.error().message().characters()));
if (outError) {
*outError = [NSError errorWithDomain:NSOSStatusErrorDomain
code:unimpErr
userInfo:nil];
}
return NO;
}
}
+ (BOOL)autosavesInPlace
{
return YES;
}
+ (BOOL)canConcurrentlyReadDocumentsOfType:(NSString*)typeName
{
// Run readFromData:ofType:error: on background thread:
return YES;
}
- (BOOL)isEntireFileLoaded
{
return NO;
}
@end

View file

@ -1,29 +0,0 @@
/*
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "CocoaWrapper.h"
#include <LibPDF/Document.h>
NS_ASSUME_NONNULL_BEGIN
// Objective-C wrapper of PDF::OutlineItem, to launder it through the NSOutlineViewDataSource protocol.
@interface OutlineItemWrapper : NSObject
- (BOOL)isGroupItem;
- (Optional<u32>)page;
@end
@interface MacPDFOutlineViewDataSource : NSObject <NSOutlineViewDataSource>
- (instancetype)initWithOutline:(RefPtr<PDF::OutlineDict>)outline;
@end
NS_ASSUME_NONNULL_END

View file

@ -1,121 +0,0 @@
/*
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#import "MacPDFOutlineViewDataSource.h"
@interface OutlineItemWrapper ()
{
// Only one of those two is set.
RefPtr<PDF::OutlineItem> _item;
NSString* _groupName;
}
@end
@implementation OutlineItemWrapper
- (instancetype)initWithItem:(NonnullRefPtr<PDF::OutlineItem>)item
{
if (self = [super init]; !self)
return nil;
_item = move(item);
_groupName = nil;
return self;
}
- (instancetype)initWithGroupName:(nonnull NSString*)groupName
{
if (self = [super init]; !self)
return nil;
_groupName = groupName;
return self;
}
- (BOOL)isGroupItem
{
return _groupName != nil;
}
- (Optional<u32>)page
{
if ([self isGroupItem])
return {};
return _item->dest.page.map([](u32 page_index) { return page_index + 1; });
}
- (OutlineItemWrapper*)child:(NSInteger)index
{
return [[OutlineItemWrapper alloc] initWithItem:_item->children[index]];
}
- (NSInteger)numberOfChildren
{
if ([self isGroupItem])
return 0;
return _item->children.size();
}
- (NSString*)objectValue
{
if (_groupName)
return _groupName;
NSString* title = [NSString stringWithUTF8String:_item->title.characters()];
// Newlines confuse NSOutlineView, at least in sidebar style (even with `usesSingleLineMode` set to YES on the cell view's text field).
title = [[title componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] componentsJoinedByString:@" "];
return title;
}
@end
@interface MacPDFOutlineViewDataSource ()
{
RefPtr<PDF::OutlineDict> _outline;
}
@end
@implementation MacPDFOutlineViewDataSource
- (instancetype)initWithOutline:(RefPtr<PDF::OutlineDict>)outline
{
if (self = [super init]; !self)
return nil;
_outline = move(outline);
return self;
}
#pragma mark - NSOutlineViewDataSource
- (id)outlineView:(NSOutlineView*)outlineView child:(NSInteger)index ofItem:(nullable id)item
{
if (item)
return [(OutlineItemWrapper*)item child:index];
if (index == 0) {
bool has_outline = _outline && !_outline->children.is_empty();
// FIXME: Maybe put filename here instead?
return [[OutlineItemWrapper alloc] initWithGroupName:has_outline ? @"Outline" : @"(No outline)"];
}
return [[OutlineItemWrapper alloc] initWithItem:_outline->children[index - 1]];
}
- (BOOL)outlineView:(NSOutlineView*)outlineView isItemExpandable:(id)item
{
return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
}
- (NSInteger)outlineView:(NSOutlineView*)outlineView numberOfChildrenOfItem:(nullable id)item
{
if (item)
return [(OutlineItemWrapper*)item numberOfChildren];
return 1 + (_outline ? _outline->children.size() : 0);
}
- (id)outlineView:(NSOutlineView*)outlineView objectValueForTableColumn:(nullable NSTableColumn*)tableColumn byItem:(nullable id)item
{
return [(OutlineItemWrapper*)item objectValue];
}
@end

View file

@ -1,36 +0,0 @@
/*
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "CocoaWrapper.h"
#include <AK/WeakPtr.h>
#include <LibPDF/Document.h>
@protocol MacPDFViewDelegate
- (void)pageChanged;
@end
@interface MacPDFView : NSView
- (void)setDocument:(WeakPtr<PDF::Document>)doc;
- (void)goToPage:(int)page;
- (int)page;
- (void)setDelegate:(id<MacPDFViewDelegate>)delegate;
- (IBAction)goToNextPage:(id)sender;
- (IBAction)goToPreviousPage:(id)sender;
- (IBAction)toggleShowClippingPaths:(id)sender;
- (IBAction)toggleClipImages:(id)sender;
- (IBAction)toggleClipPaths:(id)sender;
- (IBAction)toggleClipText:(id)sender;
- (IBAction)toggleShowImages:(id)sender;
- (IBAction)toggleShowHiddenText:(id)sender;
@end

View file

@ -1,292 +0,0 @@
/*
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#import "MacPDFView.h"
#include <LibGfx/Bitmap.h>
#include <LibPDF/Document.h>
#include <LibPDF/Renderer.h>
@interface MacPDFView ()
{
WeakPtr<PDF::Document> _doc;
NSBitmapImageRep* _cachedBitmap;
int _page_index;
__weak id<MacPDFViewDelegate> _delegate;
PDF::RenderingPreferences _preferences;
}
@end
static PDF::PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> render(PDF::Document& document, int page_index, NSSize size, PDF::RenderingPreferences const& preferences)
{
auto page = TRY(document.get_page(page_index));
auto page_size = Gfx::IntSize { size.width, size.height };
if (int rotation_count = (page.rotate / 90) % 4; rotation_count % 2 == 1)
page_size = Gfx::IntSize { page_size.height(), page_size.width() };
auto bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, page_size));
auto errors = PDF::Renderer::render(document, page, bitmap, Color::White, preferences);
if (errors.is_error()) {
for (auto const& error : errors.error().errors())
NSLog(@"warning: %@", @(error.message().characters()));
}
return TRY(PDF::Renderer::apply_page_rotation(bitmap, page));
}
static NSBitmapImageRep* ns_from_gfx(NonnullRefPtr<Gfx::Bitmap> bitmap_p)
{
auto& bitmap = bitmap_p.leak_ref();
CGBitmapInfo info = kCGBitmapByteOrder32Little | (CGBitmapInfo)kCGImageAlphaFirst;
auto data = CGDataProviderCreateWithData(
&bitmap, bitmap.begin(), bitmap.size_in_bytes(),
[](void* p, void const*, size_t) {
(void)adopt_ref(*reinterpret_cast<Gfx::Bitmap*>(p));
});
auto space = CGColorSpaceCreateDeviceRGB();
auto cgbmp = CGImageCreate(bitmap.width(), bitmap.height(), 8,
32, bitmap.pitch(), space,
info, data, nullptr, false, kCGRenderingIntentDefault);
CGColorSpaceRelease(space);
CGDataProviderRelease(data);
auto* bmp = [[NSBitmapImageRep alloc] initWithCGImage:cgbmp];
CGImageRelease(cgbmp);
return bmp;
}
@implementation MacPDFView
// Called from MacPDFDocument.
- (void)setDocument:(WeakPtr<PDF::Document>)doc
{
_doc = move(doc);
_page_index = 0;
[self addObserver:self
forKeyPath:@"safeAreaRect"
options:NSKeyValueObservingOptionNew
context:nil];
[self invalidateCachedBitmap];
}
- (void)observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id>*)change
context:(void*)context
{
// AppKit by default doesn't invalidate a view if safeAreaRect changes but the view's bounds don't change.
// This happens for example when toggling the visibility of the toolbar with a full-size content view.
// We do want a repaint in this case.
VERIFY([keyPath isEqualToString:@"safeAreaRect"]);
VERIFY(object == self);
[self setNeedsDisplay:YES];
}
- (void)goToPage:(int)page
{
if (!_doc)
return;
int new_index = max(0, min(page - 1, _doc->get_page_count() - 1));
if (new_index == _page_index)
return;
_page_index = new_index;
[self invalidateRestorableState];
[self invalidateCachedBitmap];
[_delegate pageChanged];
}
- (int)page
{
return _page_index + 1;
}
- (void)setDelegate:(id<MacPDFViewDelegate>)delegate
{
_delegate = delegate;
}
#pragma mark - Drawing
- (void)invalidateCachedBitmap
{
_cachedBitmap = nil;
[self setNeedsDisplay:YES];
}
- (void)ensureCachedBitmapIsUpToDate
{
if (!_doc || _doc->get_page_count() == 0)
return;
NSSize pixel_size = [self convertSizeToBacking:self.safeAreaRect.size];
if (NSEqualSizes([_cachedBitmap size], pixel_size))
return;
if (auto bitmap_or = render(*_doc, _page_index, pixel_size, _preferences); !bitmap_or.is_error())
_cachedBitmap = ns_from_gfx(bitmap_or.value());
}
- (void)drawRect:(NSRect)rect
{
[self ensureCachedBitmapIsUpToDate];
[_cachedBitmap drawInRect:self.safeAreaRect];
}
#pragma mark - Keyboard handling
- (BOOL)acceptsFirstResponder
{
return YES;
}
- (IBAction)goToNextPage:(id)sender
{
int current_page = _page_index + 1;
[self goToPage:current_page + 1];
}
- (IBAction)goToPreviousPage:(id)sender
{
int current_page = _page_index + 1;
[self goToPage:current_page - 1];
}
- (BOOL)validateMenuItem:(NSMenuItem*)item
{
if ([item action] == @selector(goToNextPage:))
return _doc ? (_page_index < (int)_doc->get_page_count() - 1) : NO;
if ([item action] == @selector(goToPreviousPage:))
return _doc ? (_page_index > 0) : NO;
if ([item action] == @selector(toggleShowClippingPaths:)) {
[item setState:_preferences.show_clipping_paths ? NSControlStateValueOn : NSControlStateValueOff];
return _doc ? YES : NO;
}
if ([item action] == @selector(toggleClipImages:)) {
[item setState:_preferences.clip_images ? NSControlStateValueOn : NSControlStateValueOff];
return _doc ? YES : NO;
}
if ([item action] == @selector(toggleClipPaths:)) {
[item setState:_preferences.clip_paths ? NSControlStateValueOn : NSControlStateValueOff];
return _doc ? YES : NO;
}
if ([item action] == @selector(toggleClipText:)) {
[item setState:_preferences.clip_text ? NSControlStateValueOn : NSControlStateValueOff];
return _doc ? YES : NO;
}
if ([item action] == @selector(toggleShowImages:)) {
[item setState:_preferences.show_images ? NSControlStateValueOn : NSControlStateValueOff];
return _doc ? YES : NO;
}
if ([item action] == @selector(toggleShowHiddenText:)) {
[item setState:_preferences.show_hidden_text ? NSControlStateValueOn : NSControlStateValueOff];
return _doc ? YES : NO;
}
return NO;
}
- (IBAction)toggleShowClippingPaths:(id)sender
{
if (_doc) {
_preferences.show_clipping_paths = !_preferences.show_clipping_paths;
[self invalidateCachedBitmap];
}
}
- (IBAction)toggleClipImages:(id)sender
{
if (_doc) {
_preferences.clip_images = !_preferences.clip_images;
[self invalidateCachedBitmap];
}
}
- (IBAction)toggleClipPaths:(id)sender
{
if (_doc) {
_preferences.clip_paths = !_preferences.clip_paths;
[self invalidateCachedBitmap];
}
}
- (IBAction)toggleClipText:(id)sender
{
if (_doc) {
_preferences.clip_text = !_preferences.clip_text;
[self invalidateCachedBitmap];
}
}
- (IBAction)toggleShowImages:(id)sender
{
if (_doc) {
_preferences.show_images = !_preferences.show_images;
[self invalidateCachedBitmap];
}
}
- (IBAction)toggleShowHiddenText:(id)sender
{
if (_doc) {
_preferences.show_hidden_text = !_preferences.show_hidden_text;
[self invalidateCachedBitmap];
}
}
- (void)keyDown:(NSEvent*)event
{
// Calls moveLeft: or moveRight: below.
[self interpretKeyEvents:@[ event ]];
}
// Called on down arrow.
- (IBAction)moveDown:(id)sender
{
[self goToNextPage:self];
}
// Called on left arrow.
- (IBAction)moveLeft:(id)sender
{
[self goToPreviousPage:self];
}
// Called on right arrow.
- (IBAction)moveRight:(id)sender
{
[self goToNextPage:self];
}
// Called on up arrow.
- (IBAction)moveUp:(id)sender
{
[self goToPreviousPage:self];
}
#pragma mark - State restoration
- (void)encodeRestorableStateWithCoder:(NSCoder*)coder
{
[coder encodeInt:_page_index forKey:@"PageIndex"];
NSLog(@"encodeRestorableStateWithCoder encoded %d", _page_index);
}
- (void)restoreStateWithCoder:(NSCoder*)coder
{
if ([coder containsValueForKey:@"PageIndex"]) {
int page_index = [coder decodeIntForKey:@"PageIndex"];
_page_index = min(max(0, page_index), _doc->get_page_count() - 1);
NSLog(@"encodeRestorableStateWithCoder restored %d", _page_index);
[self invalidateCachedBitmap];
[_delegate pageChanged];
}
}
@end

View file

@ -1,36 +0,0 @@
/*
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "CocoaWrapper.h"
#import "MacPDFView.h"
#include <LibPDF/Document.h>
NS_ASSUME_NONNULL_BEGIN
@class MacPDFDocument;
@interface MacPDFWindowController : NSWindowController <MacPDFViewDelegate, NSOutlineViewDelegate, NSToolbarDelegate>
- (instancetype)initWithDocument:(MacPDFDocument*)document;
- (IBAction)goToNextPage:(id)sender;
- (IBAction)goToPreviousPage:(id)sender;
- (IBAction)toggleShowClippingPaths:(id)sender;
- (IBAction)toggleClipImages:(id)sender;
- (IBAction)toggleClipPaths:(id)sender;
- (IBAction)toggleClipText:(id)sender;
- (IBAction)toggleShowImages:(id)sender;
- (IBAction)toggleShowHiddenText:(id)sender;
- (IBAction)showGoToPageDialog:(id)sender;
- (void)pdfDidInitialize;
@end
NS_ASSUME_NONNULL_END

View file

@ -1,296 +0,0 @@
/*
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#import "MacPDFWindowController.h"
#import "MacPDFDocument.h"
#import "MacPDFOutlineViewDataSource.h"
@interface MacPDFWindowController ()
{
MacPDFDocument* _pdfDocument;
IBOutlet MacPDFView* _pdfView;
MacPDFOutlineViewDataSource* _outlineDataSource;
NSOutlineView* _outlineView;
}
@end
@implementation MacPDFWindowController
- (instancetype)initWithDocument:(MacPDFDocument*)document
{
auto const style_mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskFullSizeContentView;
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 600, 800)
styleMask:style_mask
backing:NSBackingStoreBuffered
defer:YES];
if (self = [super initWithWindow:window]; !self)
return nil;
_pdfView = [[MacPDFView alloc] initWithFrame:NSZeroRect];
_pdfView.identifier = @"PDFView"; // To make state restoration work.
[_pdfView setDelegate:self];
NSSplitViewController* split_view = [[NSSplitViewController alloc] initWithNibName:nil bundle:nil];
[split_view addSplitViewItem:[self makeSidebarSplitItem]];
[split_view addSplitViewItem:[NSSplitViewItem splitViewItemWithViewController:[self viewControllerForView:_pdfView]]];
// Autosave if the sidebar is open or not, and how far.
// autosaveName only works if identifier is set too.
// identifier docs: "For programmatically created views, you typically set this value
// after creating the item but before adding it to a window. [...] For views and controls
// in a window, the value you specify for this string must be unique on a per-window basis."
split_view.splitView.autosaveName = @"MacPDFSplitView";
split_view.splitView.identifier = @"MacPDFSplitViewId";
window.contentViewController = split_view;
NSToolbar* toolbar = [[NSToolbar alloc] initWithIdentifier:@"MacPDFToolbar"];
toolbar.delegate = self;
toolbar.displayMode = NSToolbarDisplayModeIconOnly;
[window setToolbar:toolbar];
_pdfDocument = document;
return self;
}
- (NSViewController*)viewControllerForView:(NSView*)view
{
NSViewController* view_controller = [[NSViewController alloc] initWithNibName:nil bundle:nil];
view_controller.view = view;
return view_controller;
}
- (NSSplitViewItem*)makeSidebarSplitItem
{
_outlineView = [[NSOutlineView alloc] initWithFrame:NSZeroRect];
_outlineView.floatsGroupRows = NO;
_outlineView.focusRingType = NSFocusRingTypeNone;
_outlineView.headerView = nil;
// FIXME: Implement data source support for autosaveExpandedItems and use that.
// rowSizeStyle does not default to NSTableViewRowSizeStyleDefault, but needs to be set to it for outline views in sourcelist style.
_outlineView.rowSizeStyle = NSTableViewRowSizeStyleDefault;
NSTableColumn* column = [[NSTableColumn alloc] initWithIdentifier:@"OutlineColumn"];
column.editable = NO;
[_outlineView addTableColumn:column];
NSScrollView* scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect];
scrollView.hasVerticalScroller = YES;
scrollView.drawsBackground = NO;
scrollView.documentView = _outlineView;
// The scroll view knows to put things only in the safe area, but it doesn't clip to it.
// So momentum scrolling would let things draw above it, which looks weird.
// Put the scroll view in a containing view and make the containing view limit the scroll view to
// the safe area, so that it gets clipped.
NSView* view = [[NSView alloc] initWithFrame:NSZeroRect];
[view addSubview:scrollView];
[scrollView.topAnchor constraintEqualToAnchor:view.safeAreaLayoutGuide.topAnchor].active = YES;
[scrollView.leftAnchor constraintEqualToAnchor:view.safeAreaLayoutGuide.leftAnchor].active = YES;
[scrollView.rightAnchor constraintEqualToAnchor:view.safeAreaLayoutGuide.rightAnchor].active = YES;
[scrollView.bottomAnchor constraintEqualToAnchor:view.safeAreaLayoutGuide.bottomAnchor].active = YES;
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
NSSplitViewItem* item = [NSSplitViewItem sidebarWithViewController:[self viewControllerForView:view]];
item.collapseBehavior = NSSplitViewItemCollapseBehaviorPreferResizingSplitViewWithFixedSiblings;
// This only has an effect on the very first run.
// Later, the collapsed state is loaded from the sidebar's autosave data.
item.collapsed = YES;
return item;
}
- (void)pdfDidInitialize
{
[_pdfView setDocument:_pdfDocument.pdf->make_weak_ptr()];
[self pageChanged];
// FIXME: Only set data source when sidebar is open.
_outlineDataSource = [[MacPDFOutlineViewDataSource alloc] initWithOutline:_pdfDocument.pdf->outline()];
_outlineView.dataSource = _outlineDataSource;
_outlineView.delegate = self;
}
- (IBAction)goToNextPage:(id)sender
{
[_pdfView goToNextPage:sender];
}
- (IBAction)goToPreviousPage:(id)sender
{
[_pdfView goToPreviousPage:sender];
}
- (BOOL)validateMenuItem:(NSMenuItem*)item
{
if ([_pdfView validateMenuItem:item])
return YES;
return [super validateMenuItem:item];
}
- (IBAction)toggleShowClippingPaths:(id)sender
{
[_pdfView toggleShowClippingPaths:sender];
}
- (IBAction)toggleClipImages:(id)sender
{
[_pdfView toggleClipImages:sender];
}
- (IBAction)toggleClipPaths:(id)sender
{
[_pdfView toggleClipPaths:sender];
}
- (IBAction)toggleClipText:(id)sender
{
[_pdfView toggleClipText:sender];
}
- (IBAction)toggleShowImages:(id)sender
{
[_pdfView toggleShowImages:sender];
}
- (IBAction)toggleShowHiddenText:(id)sender
{
[_pdfView toggleShowHiddenText:sender];
}
- (IBAction)showGoToPageDialog:(id)sender
{
auto alert = [[NSAlert alloc] init];
alert.messageText = @"Page Number";
[alert addButtonWithTitle:@"Go"];
[alert addButtonWithTitle:@"Cancel"];
auto textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 24)];
NSNumberFormatter* formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterNoStyle; // Integers only.
[textField setFormatter:formatter];
[textField setIntValue:[_pdfView page]];
alert.accessoryView = textField;
alert.window.initialFirstResponder = textField;
[alert beginSheetModalForWindow:self.window
completionHandler:^(NSModalResponse response) {
if (response == NSAlertFirstButtonReturn)
[self->_pdfView goToPage:[textField intValue]];
}];
}
#pragma mark - MacPDFViewDelegate
- (void)pageChanged
{
[self.window setSubtitle:
[NSString stringWithFormat:@"Page %d of %d", [_pdfView page], _pdfDocument.pdf->get_page_count()]];
}
#pragma mark - NSToolbarDelegate
- (NSArray<NSToolbarItemIdentifier>*)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar
{
return [self toolbarDefaultItemIdentifiers:toolbar];
}
- (NSArray<NSToolbarItemIdentifier>*)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
{
// NSToolbarToggleSidebarItemIdentifier sends toggleSidebar: along the responder chain,
// which NSSplitViewController conveniently implements.
return @[
NSToolbarToggleSidebarItemIdentifier,
NSToolbarSidebarTrackingSeparatorItemIdentifier,
];
}
- (NSToolbarItem*)toolbar:(NSToolbar*)toolbar
itemForItemIdentifier:(NSToolbarItemIdentifier)itemIdentifier
willBeInsertedIntoToolbar:(BOOL)flag
{
// Not called for standard identifiers, but the implementation of the method must exist, or else:
// ERROR: invalid delegate <MacPDFWindowController: 0x600003054c80> (does not implement all required methods)
return nil;
}
#pragma mark - NSOutlineViewDelegate
- (BOOL)outlineView:(NSOutlineView*)outlineView isGroupItem:(id)item
{
return [item isGroupItem];
}
- (BOOL)outlineView:(NSOutlineView*)outlineView shouldSelectItem:(id)item
{
return ![self outlineView:outlineView isGroupItem:item];
}
// "This method is required if you wish to turn on the use of NSViews instead of NSCells."
- (NSView*)outlineView:(NSOutlineView*)outlineView viewForTableColumn:(NSTableColumn*)tableColumn item:(id)item
{
// "The implementation of this method will usually call -[tableView makeViewWithIdentifier:[tableColumn identifier] owner:self]
// in order to reuse a previous view, or automatically unarchive an associated prototype view for that identifier."
// Figure 1-5 in "Understanding Table Views" at
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TableView/TableViewOverview/TableViewOverview.html
// describes what makeViewWithIdentifier:owner: does: It tries to cache views, so that if an item scrolls out of view
// and then back in again, the old view can be reused, without having to allocate a new one.
// It also tries to load the view from a xib if it doesn't exist. We don't use a xib though, so we have
// to create the view in code if it's not already cached.
// After calling this method to create a view, the framework assigns its objectValue to what's
// returned by outlineView:objectValueForTableColumn:byItem: from the data source.
// NSTableCellView implements objectValue, but it doesn't do anything with it. We have to manually
// bind assignment to its objectValue field to update concrete views.
// This is done here using Cocoa bindings.
// Alternatively, we could also get the data from the data model directly and assign it to
// the text field's stringValue, but then we'd call outlineView:objectValueForTableColumn:byItem:
// twice, and this somewhat roundabout method here seems to be how the framework wants to be used.
NSTableCellView* cellView = [outlineView makeViewWithIdentifier:tableColumn.identifier owner:self];
if (!cellView) {
cellView = [[NSTableCellView alloc] init];
cellView.identifier = tableColumn.identifier;
NSTextField* textField = [NSTextField labelWithString:@""];
textField.lineBreakMode = NSLineBreakByTruncatingTail;
textField.allowsExpansionToolTips = YES;
// https://stackoverflow.com/a/29725553/551986
// "If your cell view is an NSTableCellView, that class also responds to -setObjectValue:. [...]
// However, an NSTableCellView does not inherently do anything with the object value. It just holds it.
// What you can then do is have the subviews bind to it through the objectValue property."
[textField bind:@"objectValue" toObject:cellView withKeyPath:@"objectValue" options:nil];
[cellView addSubview:textField];
cellView.textField = textField;
}
return cellView;
}
- (void)outlineViewSelectionDidChange:(NSNotification*)notification
{
NSInteger row = _outlineView.selectedRow;
if (row == -1)
return;
OutlineItemWrapper* item = [_outlineView itemAtRow:row];
if (auto page = [item page]; page.has_value())
[_pdfView goToPage:page.value()];
}
@end

View file

@ -1,747 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="MacPDF" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="MacPDF" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About MacPDF" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide MacPDF" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit MacPDF" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="dMs-cI-mzQ">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="File" id="bib-Uj-vzu">
<items>
<menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
<connections>
<action selector="newDocument:" target="-1" id="4Si-XN-c54"/>
</connections>
</menuItem>
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
<connections>
<action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
</connections>
</menuItem>
<menuItem title="Open Recent" id="tXI-mr-wws">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
<items>
<menuItem title="Clear Menu" id="vNY-rz-j42">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="-1" id="Daa-9d-B3U"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections>
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
</connections>
</menuItem>
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
<connections>
<action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
</connections>
</menuItem>
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
<connections>
<action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
</connections>
</menuItem>
<menuItem title="Revert to Saved" keyEquivalent="r" id="KaW-ft-85H">
<connections>
<action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
<menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
</connections>
</menuItem>
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
<connections>
<action selector="print:" target="-1" id="qaZ-4w-aoO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Format" id="jxT-CU-nIS">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Format" id="GEO-Iw-cKr">
<items>
<menuItem title="Font" id="Gi5-1S-RQB">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
<items>
<menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
<connections>
<action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
</connections>
</menuItem>
<menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
</connections>
</menuItem>
<menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
</connections>
</menuItem>
<menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
<connections>
<action selector="underline:" target="-1" id="FYS-2b-JAY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
</connections>
</menuItem>
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
<menuItem title="Kern" id="jBQ-r6-VK2">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Kern" id="tlD-Oa-oAM">
<items>
<menuItem title="Use Default" id="GUa-eO-cwY">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardKerning:" target="-1" id="6dk-9l-Ckg"/>
</connections>
</menuItem>
<menuItem title="Use None" id="cDB-IK-hbR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffKerning:" target="-1" id="U8a-gz-Maa"/>
</connections>
</menuItem>
<menuItem title="Tighten" id="46P-cB-AYj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="tightenKerning:" target="-1" id="hr7-Nz-8ro"/>
</connections>
</menuItem>
<menuItem title="Loosen" id="ogc-rX-tC1">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="loosenKerning:" target="-1" id="8i4-f9-FKE"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Ligatures" id="o6e-r0-MWq">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
<items>
<menuItem title="Use Default" id="agt-UL-0e3">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardLigatures:" target="-1" id="7uR-wd-Dx6"/>
</connections>
</menuItem>
<menuItem title="Use None" id="J7y-lM-qPV">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffLigatures:" target="-1" id="iX2-gA-Ilz"/>
</connections>
</menuItem>
<menuItem title="Use All" id="xQD-1f-W4t">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useAllLigatures:" target="-1" id="KcB-kA-TuK"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Baseline" id="OaQ-X3-Vso">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Baseline" id="ijk-EB-dga">
<items>
<menuItem title="Use Default" id="3Om-Ey-2VK">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unscript:" target="-1" id="0vZ-95-Ywn"/>
</connections>
</menuItem>
<menuItem title="Superscript" id="Rqc-34-cIF">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="superscript:" target="-1" id="3qV-fo-wpU"/>
</connections>
</menuItem>
<menuItem title="Subscript" id="I0S-gh-46l">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="subscript:" target="-1" id="Q6W-4W-IGz"/>
</connections>
</menuItem>
<menuItem title="Raise" id="2h7-ER-AoG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="raiseBaseline:" target="-1" id="4sk-31-7Q9"/>
</connections>
</menuItem>
<menuItem title="Lower" id="1tx-W0-xDw">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowerBaseline:" target="-1" id="OF1-bc-KW4"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
<menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
<connections>
<action selector="orderFrontColorPanel:" target="-1" id="mSX-Xz-DV3"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
<menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="copyFont:" target="-1" id="GJO-xA-L4q"/>
</connections>
</menuItem>
<menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteFont:" target="-1" id="JfD-CL-leO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Text" id="Fal-I4-PZk">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Text" id="d9c-me-L2H">
<items>
<menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
<connections>
<action selector="alignLeft:" target="-1" id="zUv-R1-uAa"/>
</connections>
</menuItem>
<menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
<connections>
<action selector="alignCenter:" target="-1" id="spX-mk-kcS"/>
</connections>
</menuItem>
<menuItem title="Justify" id="J5U-5w-g23">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="alignJustified:" target="-1" id="ljL-7U-jND"/>
</connections>
</menuItem>
<menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
<connections>
<action selector="alignRight:" target="-1" id="r48-bG-YeY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
<menuItem title="Writing Direction" id="H1b-Si-o9J">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
<items>
<menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="YGs-j5-SAR">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionNatural:" target="-1" id="qtV-5e-UBP"/>
</connections>
</menuItem>
<menuItem id="Lbh-J2-qVU">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionLeftToRight:" target="-1" id="S0X-9S-QSf"/>
</connections>
</menuItem>
<menuItem id="jFq-tB-4Kx">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionRightToLeft:" target="-1" id="5fk-qB-AqJ"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
<menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="Nop-cj-93Q">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionNatural:" target="-1" id="lPI-Se-ZHp"/>
</connections>
</menuItem>
<menuItem id="BgM-ve-c93">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionLeftToRight:" target="-1" id="caW-Bv-w94"/>
</connections>
</menuItem>
<menuItem id="RB4-Sm-HuC">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionRightToLeft:" target="-1" id="EXD-6r-ZUu"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
<menuItem title="Show Ruler" id="vLm-3I-IUL">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleRuler:" target="-1" id="FOx-HJ-KwY"/>
</connections>
</menuItem>
<menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="copyRuler:" target="-1" id="71i-fW-3W2"/>
</connections>
</menuItem>
<menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="pasteRuler:" target="-1" id="cSh-wd-qM2"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="toggleToolbarShown:" target="-1" id="BXY-wc-z0C"/>
</connections>
</menuItem>
<menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="runToolbarCustomizationPalette:" target="-1" id="pQI-g3-MTW"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="hB3-LF-h0Y"/>
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleSidebar:" target="-1" id="iwa-gc-5KM"/>
</connections>
</menuItem>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Go" id="XZ6-XO-pVc">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Go" id="u8F-oH-oMu">
<items>
<menuItem title="Previous Page" keyEquivalent="" id="Ou1-5M-LzJ">
<modifierMask key="keyEquivalentModifierMask" option="YES"/>
<connections>
<action selector="goToPreviousPage:" target="-1" id="e1c-zc-WR6"/>
</connections>
</menuItem>
<menuItem title="Next Page" keyEquivalent="" id="mfm-mG-pLT">
<modifierMask key="keyEquivalentModifierMask" option="YES"/>
<connections>
<action selector="goToNextPage:" target="-1" id="lt2-m9-Iyp"/>
</connections>
</menuItem>
<menuItem title="Go to Page…" keyEquivalent="g" id="pzP-g1-BeT">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="showGoToPageDialog:" target="-1" id="fPI-BN-18g"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Debug" id="jWy-In-lcG">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Debug" id="9JC-3n-6oc">
<items>
<menuItem title="Show Clipping Paths" id="mNt-xL-mVw">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleShowClippingPaths:" target="-1" id="ZXz-gM-52n"/>
</connections>
</menuItem>
<menuItem title="Clip Images" state="on" id="os0-En-UkL">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleClipImages:" target="-1" id="bHz-O3-V8K"/>
</connections>
</menuItem>
<menuItem title="Clip Paths" state="on" id="KB8-Ld-jv8">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleClipPaths:" target="-1" id="pZu-tJ-RFh"/>
</connections>
</menuItem>
<menuItem title="Clip Text" state="on" id="u58-eB-op8">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleClipText:" target="-1" id="qxg-tH-KXd"/>
</connections>
</menuItem>
<menuItem title="Show Images" state="on" id="ArW-nr-ktv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleShowImages:" target="-1" id="mNE-9J-Nle"/>
</connections>
</menuItem>
<menuItem title="Show Hidden Text" id="PhM-XC-ExK">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleShowHiddenText:" target="-1" id="7iT-L2-Jd1"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="rRF-Br-Pu3">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="ipJ-MA-vaP">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="iQm-go-526">
<connections>
<action selector="performMiniaturize:" target="-1" id="ysV-jh-lhh"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="3fA-VK-sIE">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="3eR-Yk-WOl"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="fk3-NL-Gg9"/>
<menuItem title="Bring All to Front" id="q3x-yl-EEv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="9GN-Lx-lIK"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="MacPDF Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="-1" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
<point key="canvasLocation" x="200" y="121"/>
</menu>
</objects>
</document>

View file

@ -1,12 +0,0 @@
/*
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#import <Cocoa/Cocoa.h>
int main(int argc, char const* argv[])
{
return NSApplicationMain(argc, argv);
}

View file

@ -1,16 +0,0 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibELF/Image.h>
#include <stddef.h>
#include <stdint.h>
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{
AK::set_debug_enabled(false);
ELF::Image elf(data, size, /*verbose_logging=*/false);
return 0;
}

View file

@ -1,26 +0,0 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibPDF/Document.h>
#include <stdint.h>
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{
AK::set_debug_enabled(false);
ReadonlyBytes bytes { data, size };
if (auto maybe_document = PDF::Document::create(bytes); !maybe_document.is_error()) {
auto document = maybe_document.release_value();
(void)document->initialize();
auto pages = document->get_page_count();
for (size_t i = 0; i < pages; ++i) {
(void)document->get_page(i);
}
}
return 0;
}

View file

@ -7,7 +7,6 @@ set(FUZZER_TARGETS
DDSLoader DDSLoader
DeflateCompression DeflateCompression
DeflateDecompression DeflateDecompression
ELF
FlacLoader FlacLoader
Gemini Gemini
GIFLoader GIFLoader
@ -30,7 +29,6 @@ set(FUZZER_TARGETS
MP3Loader MP3Loader
PAMLoader PAMLoader
PBMLoader PBMLoader
PDF
PEM PEM
PGMLoader PGMLoader
PNGLoader PNGLoader
@ -99,7 +97,6 @@ set(FUZZER_DEPENDENCIES_MD5 LibCrypto)
set(FUZZER_DEPENDENCIES_MP3Loader LibAudio) set(FUZZER_DEPENDENCIES_MP3Loader LibAudio)
set(FUZZER_DEPENDENCIES_PAMLoader LibGfx) set(FUZZER_DEPENDENCIES_PAMLoader LibGfx)
set(FUZZER_DEPENDENCIES_PBMLoader LibGfx) set(FUZZER_DEPENDENCIES_PBMLoader LibGfx)
set(FUZZER_DEPENDENCIES_PDF LibPDF)
set(FUZZER_DEPENDENCIES_PEM LibCrypto) set(FUZZER_DEPENDENCIES_PEM LibCrypto)
set(FUZZER_DEPENDENCIES_PGMLoader LibGfx) set(FUZZER_DEPENDENCIES_PGMLoader LibGfx)
set(FUZZER_DEPENDENCIES_PNGLoader LibGfx) set(FUZZER_DEPENDENCIES_PNGLoader LibGfx)

View file

@ -1,9 +1,4 @@
add_subdirectory(GMLCompiler)
add_subdirectory(IPCCompiler) add_subdirectory(IPCCompiler)
if (BUILD_LAGOM)
add_subdirectory(JSSpecCompiler)
endif()
add_subdirectory(LibGL)
add_subdirectory(LibLocale) add_subdirectory(LibLocale)
add_subdirectory(LibTextCodec) add_subdirectory(LibTextCodec)
add_subdirectory(LibTimeZone) add_subdirectory(LibTimeZone)

View file

@ -1,5 +0,0 @@
set(SOURCES
main.cpp
)
lagom_tool(GMLCompiler LIBS LibMain LibCore LibGUI_GML)

View file

@ -1,446 +0,0 @@
/*
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Forward.h>
#include <AK/HashTable.h>
#include <AK/LexicalPath.h>
#include <AK/SourceGenerator.h>
#include <AK/String.h>
#include <AK/Try.h>
#include <AK/Utf8View.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/File.h>
#include <LibGUI/GML/Parser.h>
#include <LibGUI/UIDimensions.h>
#include <LibMain/Main.h>
enum class UseObjectConstructor : bool {
No,
Yes,
};
// Classes whose header doesn't have the same name as the class.
static Optional<StringView> map_class_to_file(StringView class_)
{
static HashMap<StringView, StringView> class_file_mappings {
{ "GUI::HorizontalSplitter"sv, "GUI/Splitter"sv },
{ "GUI::VerticalSplitter"sv, "GUI/Splitter"sv },
{ "GUI::HorizontalSeparator"sv, "GUI/SeparatorWidget"sv },
{ "GUI::VerticalSeparator"sv, "GUI/SeparatorWidget"sv },
{ "GUI::HorizontalBoxLayout"sv, "GUI/BoxLayout"sv },
{ "GUI::VerticalBoxLayout"sv, "GUI/BoxLayout"sv },
{ "GUI::HorizontalProgressbar"sv, "GUI/Progressbar"sv },
{ "GUI::VerticalProgressbar"sv, "GUI/Progressbar"sv },
{ "GUI::DialogButton"sv, "GUI/Button"sv },
{ "GUI::PasswordBox"sv, "GUI/TextBox"sv },
{ "GUI::HorizontalOpacitySlider"sv, "GUI/OpacitySlider"sv },
// Map Layout::Spacer to the Layout header even though it's a pseudo class.
{ "GUI::Layout::Spacer"sv, "GUI/Layout"sv },
};
return class_file_mappings.get(class_);
}
// Properties which don't take a direct JSON-like primitive (StringView, int, bool, Array etc) as arguments and need the arguments to be wrapped in a constructor call.
static Optional<StringView> map_property_to_type(StringView property)
{
static HashMap<StringView, StringView> property_to_type_mappings {
{ "container_margins"sv, "GUI::Margins"sv },
{ "margins"sv, "GUI::Margins"sv },
};
return property_to_type_mappings.get(property);
}
// Properties which take a UIDimension which can handle JSON directly.
static bool is_ui_dimension_property(StringView property)
{
static HashTable<StringView> ui_dimension_properties;
if (ui_dimension_properties.is_empty()) {
ui_dimension_properties.set("min_width"sv);
ui_dimension_properties.set("max_width"sv);
ui_dimension_properties.set("preferred_width"sv);
ui_dimension_properties.set("min_height"sv);
ui_dimension_properties.set("max_height"sv);
ui_dimension_properties.set("preferred_height"sv);
}
return ui_dimension_properties.contains(property);
}
// FIXME: Since normal string-based properties take either String or StringView (and the latter can be implicitly constructed from the former),
// we need to special-case ByteString property setters while those still exist.
// Please remove a setter from this list once it uses StringView or String.
static bool takes_byte_string(StringView property)
{
static HashTable<StringView> byte_string_properties;
if (byte_string_properties.is_empty()) {
byte_string_properties.set("icon_from_path"sv);
byte_string_properties.set("name"sv);
}
return byte_string_properties.contains(property);
}
static ErrorOr<String> include_path_for(StringView class_name, LexicalPath const& gml_file_name)
{
String pathed_name;
if (auto mapping = map_class_to_file(class_name); mapping.has_value())
pathed_name = TRY(String::from_utf8(mapping.value()));
else
pathed_name = TRY(TRY(String::from_utf8(class_name)).replace("::"sv, "/"sv, ReplaceMode::All));
if (class_name.starts_with("GUI::"sv) || class_name.starts_with("WebView::"sv))
return String::formatted("<Lib{}.h>", pathed_name);
// We assume that all other paths are within the current application, for now.
// To figure out what kind of userland program this is (application, service, ...) we consider the path to the original GML file.
auto const& paths = gml_file_name.parts_view();
auto path_iter = paths.find("Userland"sv);
path_iter++;
auto const userland_subdirectory = (path_iter == paths.end()) ? "Applications"_string : TRY(String::from_utf8(*path_iter));
return String::formatted("<{}/{}.h>", userland_subdirectory, pathed_name);
}
// Each entry is an include path, without the "#include" itself.
static ErrorOr<HashTable<String>> extract_necessary_includes(GUI::GML::Object const& gml_hierarchy, LexicalPath const& gml_file_name, bool is_root = false)
{
HashTable<String> necessary_includes;
if (!is_root)
TRY(necessary_includes.try_set(TRY(include_path_for(gml_hierarchy.name(), gml_file_name))));
if (gml_hierarchy.layout_object() != nullptr)
TRY(necessary_includes.try_set(TRY(include_path_for(gml_hierarchy.layout_object()->name(), gml_file_name))));
TRY(gml_hierarchy.try_for_each_child_object([&](auto const& object) -> ErrorOr<void> {
auto necessary_child_includes = TRY(extract_necessary_includes(object, gml_file_name));
for (auto const& include : necessary_child_includes)
TRY(necessary_includes.try_set(include));
return {};
}));
return necessary_includes;
}
static char const header[] = R"~~~(
/*
* Auto-generated by the GML compiler
*/
)~~~";
static char const class_declaration[] = R"~~~(
// A barebones definition of @main_class_name@ used to emit the symbol try_create.
// Requirements:
// - Inherits from GUI::Widget (indirectly, is declared as 'class')
// - Has a default ctor
// - Has declared a compatible static ErrorOr<NonnullRefPtr<@pure_class_name@>> try_create().
namespace @class_namespace@ {
class @pure_class_name@ : public GUI::Widget {
public:
@pure_class_name@();
static ErrorOr<NonnullRefPtr<@pure_class_name@>> try_create();
};
}
)~~~";
static char const function_start[] = R"~~~(
// Creates a @main_class_name@ and initializes it.
// This function was auto-generated by the GML compiler.
ErrorOr<NonnullRefPtr<@main_class_name@>> @main_class_name@::try_create()
{
RefPtr<::@main_class_name@> main_object;
)~~~";
static char const footer[] = R"~~~(
return main_object.release_nonnull();
}
)~~~";
static ErrorOr<String> escape_string(JsonValue to_escape)
{
auto string = TRY(String::from_byte_string(to_escape.as_string()));
// All C++ simple escape sequences; see https://en.cppreference.com/w/cpp/language/escape
// Other commonly-escaped characters are hard-to-type Unicode and therefore fine to include verbatim in UTF-8 coded strings.
static HashMap<StringView, StringView> escape_sequences = {
{ "\\"sv, "\\\\"sv }, // This needs to be the first because otherwise the the backslashes of other items will be double escaped
{ "\0"sv, "\\0"sv },
{ "\'"sv, "\\'"sv },
{ "\""sv, "\\\""sv },
{ "\a"sv, "\\a"sv },
{ "\b"sv, "\\b"sv },
{ "\f"sv, "\\f"sv },
{ "\n"sv, "\\n"sv },
{ "\r"sv, "\\r"sv },
{ "\t"sv, "\\t"sv },
{ "\v"sv, "\\v"sv },
};
for (auto const& entries : escape_sequences)
string = TRY(string.replace(entries.key, entries.value, ReplaceMode::All));
return string;
}
// This function assumes that the string is already the same as its enum constant's name.
// Therefore, it does not handle UI dimensions.
static ErrorOr<Optional<String>> generate_enum_initializer_for(StringView property_name, JsonValue value)
{
// The value is the enum's type name.
static HashMap<StringView, StringView> enum_properties = {
{ "background_role"sv, "Gfx::ColorRole"sv },
{ "button_style"sv, "Gfx::ButtonStyle"sv },
{ "checkbox_position"sv, "GUI::CheckBox::CheckBoxPosition"sv },
{ "focus_policy"sv, "GUI::FocusPolicy"sv },
{ "font_weight"sv, "Gfx::FontWeight"sv },
{ "foreground_role"sv, "Gfx::ColorRole"sv },
{ "frame_style"sv, "Gfx::FrameStyle"sv },
{ "mode"sv, "GUI::TextEditor::Mode"sv },
{ "opportunistic_resizee"sv, "GUI::Splitter::OpportunisticResizee"sv },
{ "orientation"sv, "Gfx::Orientation"sv },
{ "text_alignment"sv, "Gfx::TextAlignment"sv },
{ "text_wrapping"sv, "Gfx::TextWrapping"sv },
};
auto const& enum_type_name = enum_properties.get(property_name);
if (!enum_type_name.has_value())
return Optional<String> {};
return String::formatted("{}::{}", *enum_type_name, value.as_string());
}
// FIXME: In case of error, propagate the precise array+property that triggered the error.
static ErrorOr<String> generate_initializer_for(Optional<StringView> property_name, JsonValue value)
{
if (value.is_string()) {
if (property_name.has_value()) {
if (takes_byte_string(*property_name))
return String::formatted(R"~~~("{}"sv)~~~", TRY(escape_string(value)));
if (auto const enum_value = TRY(generate_enum_initializer_for(*property_name, value)); enum_value.has_value())
return String::formatted("{}", *enum_value);
if (*property_name == "bitmap"sv)
return String::formatted(R"~~~(TRY(Gfx::Bitmap::load_from_file("{}"sv)))~~~", TRY(escape_string(value)));
}
return String::formatted(R"~~~("{}"_string)~~~", TRY(escape_string(value)));
}
if (value.is_bool())
return String::formatted("{}", value.as_bool());
if (value.is_number()) {
return value.as_number().visit(
// NOTE: Passing by mutable reference here in order to disallow implicit casts.
[](u64& value) { return String::formatted("static_cast<u64>({})", value); },
[](i64& value) { return String::formatted("static_cast<i64>({})", value); },
[](double& value) { return String::formatted("static_cast<double>({})", value); });
}
if (value.is_array()) {
auto const& array = value.as_array();
auto child_type = Optional<StringView> {};
for (auto const& child_value : array.values()) {
if (child_value.is_array())
return Error::from_string_view("Nested arrays are not supported"sv);
#define HANDLE_TYPE(type_name, is_type) \
if (child_value.is_type() && (!child_type.has_value() || child_type.value() == #type_name##sv)) \
child_type = #type_name##sv; \
else
HANDLE_TYPE(StringView, is_string)
HANDLE_TYPE(i64, is_integer<i64>)
HANDLE_TYPE(u64, is_integer<u64>)
HANDLE_TYPE(bool, is_bool)
// FIXME: Do we want to allow precision loss when C++ compiler parses these doubles?
HANDLE_TYPE(double, is_number)
return Error::from_string_view("Inconsistent contained type in JSON array"sv);
#undef HANDLE_TYPE
}
if (!child_type.has_value())
return Error::from_string_view("Empty JSON array; cannot deduce type."sv);
StringBuilder initializer;
initializer.appendff("Array<{}, {}> {{ "sv, child_type.release_value(), array.size());
for (auto const& child_value : array.values())
initializer.appendff("{}, ", TRY(generate_initializer_for({}, child_value)));
initializer.append("}"sv);
return initializer.to_string();
}
return Error::from_string_view("Unsupported JSON value"sv);
}
// Loads an object and assigns it to the RefPtr<Widget> variable named object_name.
// All loading happens in a separate block.
static ErrorOr<void> generate_loader_for_object(GUI::GML::Object const& gml_object, SourceGenerator generator, String object_name, size_t indentation, UseObjectConstructor use_object_constructor)
{
generator.set("object_name", object_name.to_byte_string());
generator.set("class_name", gml_object.name());
auto append = [&]<size_t N>(auto& generator, char const(&text)[N]) -> ErrorOr<void> {
generator.append(TRY(String::repeated(' ', indentation * 4)));
generator.appendln(text);
return {};
};
generator.append(TRY(String::repeated(' ', (indentation - 1) * 4)));
generator.appendln("{");
if (use_object_constructor == UseObjectConstructor::Yes)
TRY(append(generator, "@object_name@ = TRY(@class_name@::try_create());"));
else
TRY(append(generator, "@object_name@ = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) ::@class_name@()));"));
// Properties
TRY(gml_object.try_for_each_property([&](StringView key, NonnullRefPtr<GUI::GML::JsonValueNode> value) -> ErrorOr<void> {
auto value_code = TRY(generate_initializer_for(key, value));
if (is_ui_dimension_property(key)) {
if (auto ui_dimension = GUI::UIDimension::construct_from_json_value(value); ui_dimension.has_value())
value_code = TRY(ui_dimension->as_cpp_source());
else
// FIXME: propagate precise error cause
return Error::from_string_view("UI dimension invalid"sv);
} else {
// Wrap value in an extra constructor call if necessary.
if (auto type = map_property_to_type(key); type.has_value())
value_code = TRY(String::formatted("{} {{ {} }}", type.release_value(), value_code));
}
auto property_generator = generator.fork();
property_generator.set("key", key);
property_generator.set("value", value_code.bytes_as_string_view());
TRY(append(property_generator, R"~~~(@object_name@->set_@key@(@value@);)~~~"));
return {};
}));
generator.appendln("");
// Object properties
size_t current_object_property_index = 0;
auto next_object_property_name = [&]() {
return String::formatted("{}_property_{}", object_name, current_object_property_index++);
};
TRY(gml_object.try_for_each_object_property([&](StringView key, NonnullRefPtr<GUI::GML::Object> value) -> ErrorOr<void> {
if (key == "layout"sv)
return {}; // Layout is handled separately.
auto property_generator = generator.fork();
auto property_variable_name = TRY(next_object_property_name());
property_generator.set("property_variable_name", property_variable_name.bytes_as_string_view());
property_generator.set("property_class_name", value->name());
property_generator.set("key", key);
TRY(append(property_generator, "RefPtr<::@property_class_name@> @property_variable_name@;"));
TRY(generate_loader_for_object(*value, property_generator.fork(), property_variable_name, indentation + 1, UseObjectConstructor::Yes));
// Set the property on the object.
TRY(append(property_generator, "@object_name@->set_@key@(*@property_variable_name@);"));
property_generator.appendln("");
return {};
}));
// Layout
if (gml_object.layout_object() != nullptr) {
TRY(append(generator, "RefPtr<GUI::Layout> layout;"));
TRY(generate_loader_for_object(*gml_object.layout_object(), generator.fork(), "layout"_string, indentation + 1, UseObjectConstructor::Yes));
TRY(append(generator, "@object_name@->set_layout(layout.release_nonnull());"));
generator.appendln("");
}
// Children
size_t current_child_index = 0;
auto next_child_name = [&]() {
return String::formatted("{}_child_{}", object_name, current_child_index++);
};
TRY(gml_object.try_for_each_child_object([&](auto const& child) -> ErrorOr<void> {
// Spacer is a pseudo-class that insteads causes a call to `Widget::add_spacer` on the parent object.
if (child.name() == "GUI::Layout::Spacer"sv) {
TRY(append(generator, "@object_name@->add_spacer();"));
return {};
}
auto child_generator = generator.fork();
auto child_variable_name = TRY(next_child_name());
child_generator.set("child_variable_name", child_variable_name.bytes_as_string_view());
child_generator.set("child_class_name", child.name());
TRY(append(child_generator, "RefPtr<::@child_class_name@> @child_variable_name@;"));
TRY(generate_loader_for_object(child, child_generator.fork(), child_variable_name, indentation + 1, UseObjectConstructor::Yes));
// Handle the current special case of child adding.
// FIXME: This should be using the proper API for handling object properties.
if (gml_object.name() == "GUI::TabWidget"sv)
TRY(append(child_generator, "static_ptr_cast<GUI::TabWidget>(@object_name@)->add_widget(*@child_variable_name@);"));
else
TRY(append(child_generator, "TRY(@object_name@->try_add_child(*@child_variable_name@));"));
child_generator.appendln("");
return {};
}));
TRY(append(generator, "TRY(::GUI::initialize(*@object_name@));"));
generator.append(TRY(String::repeated(' ', (indentation - 1) * 4)));
generator.appendln("}");
return {};
}
static ErrorOr<String> generate_cpp(NonnullRefPtr<GUI::GML::GMLFile> gml, LexicalPath const& gml_file_name)
{
StringBuilder builder;
SourceGenerator generator { builder };
generator.append(header);
auto& main_class = gml->main_class();
auto necessary_includes = TRY(extract_necessary_includes(main_class, gml_file_name, true));
static String const always_necessary_includes[] = {
"<AK/Error.h>"_string,
"<AK/JsonValue.h>"_string,
"<AK/NonnullRefPtr.h>"_string,
"<AK/RefPtr.h>"_string,
"<LibGfx/Font/FontWeight.h>"_string,
// For Gfx::ColorRole
"<LibGfx/SystemTheme.h>"_string,
"<LibGUI/Widget.h>"_string,
// For Gfx::FontWeight
"<LibGfx/Font/FontDatabase.h>"_string,
};
TRY(necessary_includes.try_set_from(always_necessary_includes));
for (auto const& include : necessary_includes)
generator.appendln(TRY(String::formatted("#include {}", include)));
auto main_file_header = TRY(include_path_for(main_class.name(), gml_file_name));
generator.appendln(TRY(String::formatted("#if __has_include({})", main_file_header)));
generator.appendln(TRY(String::formatted("#include {}", main_file_header)));
generator.appendln("#else");
// FIXME: Use a UTF-8 aware function once possible.
auto ns_position = main_class.name().find_last("::"sv);
auto ns = main_class.name().substring_view(0, ns_position.value_or(0));
auto pure_class_name = main_class.name().substring_view(ns_position.map([](auto x) { return x + 2; }).value_or(0));
generator.set("class_namespace", ns);
generator.set("pure_class_name", pure_class_name);
generator.set("main_class_name", main_class.name());
generator.append(class_declaration);
generator.appendln("#endif // __has_include(...)");
generator.append(function_start);
TRY(generate_loader_for_object(main_class, generator.fork(), "main_object"_string, 2, UseObjectConstructor::No));
generator.append(footer);
return builder.to_string();
}
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
Core::ArgsParser argument_parser;
StringView gml_file_name;
argument_parser.add_positional_argument(gml_file_name, "GML file to compile", "GML_FILE", Core::ArgsParser::Required::Yes);
argument_parser.parse(arguments);
auto gml_text = TRY(TRY(Core::File::open(gml_file_name, Core::File::OpenMode::Read))->read_until_eof());
auto parsed_gml = TRY(GUI::GML::parse_gml(gml_text));
auto generated_cpp = TRY(generate_cpp(parsed_gml, LexicalPath { gml_file_name }));
outln("{}", generated_cpp);
return 0;
}

View file

@ -1,157 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/String.h>
#include "AST/AST.h"
namespace JSSpecCompiler {
Tree NodeSubtreePointer::get(Badge<RecursiveASTVisitor>)
{
return m_tree_ptr.visit(
[&](NullableTree* nullable_tree) -> Tree {
NullableTree copy = *nullable_tree;
return copy.release_nonnull();
},
[&](Tree* tree) -> Tree {
return *tree;
},
[&](VariableRef* tree) -> Tree {
return *tree;
});
}
void NodeSubtreePointer::replace_subtree(Badge<RecursiveASTVisitor>, NullableTree replacement)
{
m_tree_ptr.visit(
[&](NullableTree* nullable_tree) {
*nullable_tree = replacement;
},
[&](Tree* tree) {
*tree = replacement.release_nonnull();
},
[&](VariableRef*) {
VERIFY_NOT_REACHED();
});
}
Vector<BasicBlockRef*> ControlFlowJump::references()
{
return { &m_block };
}
Vector<BasicBlockRef*> ControlFlowBranch::references()
{
return { &m_then, &m_else };
}
Vector<NodeSubtreePointer> BinaryOperation::subtrees()
{
return { { &m_left }, { &m_right } };
}
Vector<NodeSubtreePointer> UnaryOperation::subtrees()
{
return { { &m_operand } };
}
Vector<NodeSubtreePointer> IsOneOfOperation::subtrees()
{
Vector<NodeSubtreePointer> result = { { &m_operand } };
for (auto& child : m_compare_values)
result.append({ &child });
return result;
}
Vector<NodeSubtreePointer> ReturnNode::subtrees()
{
return { { &m_return_value } };
}
Vector<NodeSubtreePointer> AssertExpression::subtrees()
{
return { { &m_condition } };
}
Vector<NodeSubtreePointer> IfBranch::subtrees()
{
return { { &m_condition }, { &m_branch } };
}
Vector<NodeSubtreePointer> ElseIfBranch::subtrees()
{
if (m_condition)
return { { &m_condition }, { &m_branch } };
return { { &m_branch } };
}
Vector<NodeSubtreePointer> IfElseIfChain::subtrees()
{
Vector<NodeSubtreePointer> result;
for (size_t i = 0; i < branches_count(); ++i) {
result.append({ &m_conditions[i] });
result.append({ &m_branches[i] });
}
if (m_else_branch)
result.append({ &m_else_branch });
return result;
}
TreeList::TreeList(Vector<Tree>&& trees)
{
for (auto const& tree : trees) {
if (tree->is_list()) {
for (auto const& nested_tree : as<TreeList>(tree)->m_trees)
m_trees.append(nested_tree);
} else {
m_trees.append(tree);
}
}
}
Vector<NodeSubtreePointer> TreeList::subtrees()
{
Vector<NodeSubtreePointer> result;
for (auto& expression : m_trees)
result.append({ &expression });
return result;
}
Vector<NodeSubtreePointer> RecordDirectListInitialization::subtrees()
{
Vector<NodeSubtreePointer> result { &m_type_reference };
for (auto& argument : m_arguments) {
result.append({ &argument.name });
result.append({ &argument.value });
}
return result;
}
Vector<NodeSubtreePointer> FunctionCall::subtrees()
{
Vector<NodeSubtreePointer> result = { { &m_name } };
for (auto& child : m_arguments)
result.append({ &child });
return result;
}
String Variable::name() const
{
if (m_ssa)
return MUST(String::formatted("{}@{}", m_name->m_name, m_ssa->m_version));
return MUST(String::from_utf8(m_name->m_name));
}
Vector<NodeSubtreePointer> List::subtrees()
{
Vector<NodeSubtreePointer> result;
for (auto& element : m_elements)
result.append({ &element });
return result;
}
}

View file

@ -1,580 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Badge.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/Vector.h>
#include <LibCrypto/BigFraction/BigFraction.h>
#include "Forward.h"
namespace JSSpecCompiler {
template<typename T>
RefPtr<T> as(NullableTree const& tree)
{
return dynamic_cast<T*>(tree.ptr());
}
class NodeSubtreePointer {
public:
NodeSubtreePointer(Tree* tree_ptr)
: m_tree_ptr(tree_ptr)
{
}
NodeSubtreePointer(NullableTree* tree_ptr)
: m_tree_ptr(tree_ptr)
{
}
NodeSubtreePointer(VariableRef* tree_ptr)
: m_tree_ptr(tree_ptr)
{
}
Tree get(Badge<RecursiveASTVisitor>);
void replace_subtree(Badge<RecursiveASTVisitor>, NullableTree replacement);
private:
Variant<Tree*, NullableTree*, VariableRef*> m_tree_ptr;
};
class VariableDeclaration : public RefCounted<VariableDeclaration> {
public:
virtual ~VariableDeclaration() = default;
};
class NamedVariableDeclaration : public VariableDeclaration {
public:
NamedVariableDeclaration(StringView name)
: m_name(name)
{
}
StringView m_name;
};
class SSAVariableDeclaration : public VariableDeclaration {
public:
SSAVariableDeclaration(u64 version)
: m_version(version)
{
}
size_t m_index = 0;
u64 m_version;
};
class Node : public RefCounted<Node> {
public:
virtual ~Node() = default;
void format_tree(StringBuilder& builder);
// For expressions, order must be the same as the evaluation order.
virtual Vector<NodeSubtreePointer> subtrees() { return {}; }
virtual bool is_list() const { return false; }
virtual bool is_statement() { VERIFY_NOT_REACHED(); }
protected:
template<typename... Parameters>
void dump_node(StringBuilder& builder, AK::CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters);
virtual void dump_tree(StringBuilder& builder) = 0;
};
// Although both statements and expressions are allowed to return value, CFG building differentiates
// between them. Expressions are not allowed to change control flow, while statements are. Special
// handling required if a statement turns out to be a descendant of an expression. Roughly speaking,
// from the CFG standpoint, something like `a = ({ b + ({ c }) }) + ({ d })` will look like
// ```
// auto tmp1 = c;
// auto tmp2 = b + tmp1;
// auto tmp3 = d;
// a = tmp1 + tmp2;
// ```.
class Statement : public Node {
public:
bool is_statement() override { return true; }
};
class Expression : public Node {
public:
bool is_statement() override { return false; }
};
class ControlFlowOperator : public Statement {
public:
bool is_statement() override { return false; }
virtual Vector<BasicBlockRef*> references() = 0;
};
class ErrorNode : public Expression {
public:
ErrorNode(StringView error = ""sv)
: m_error(error)
{
}
StringView m_error;
protected:
void dump_tree(StringBuilder& builder) override;
};
class WellKnownNode : public Expression {
public:
enum Type {
False,
NewTarget,
Null,
This,
True,
Undefined,
// Update WellKnownNode::dump_tree after adding an entry here
};
WellKnownNode(Type type)
: m_type(type)
{
}
protected:
void dump_tree(StringBuilder& builder) override;
private:
Type m_type;
};
inline Tree const error_tree = make_ref_counted<ErrorNode>();
class ControlFlowFunctionReturn : public ControlFlowOperator {
public:
ControlFlowFunctionReturn(VariableRef return_value)
: m_return_value(move(return_value))
{
}
VariableRef m_return_value;
Vector<NodeSubtreePointer> subtrees() override { return { { &m_return_value } }; }
Vector<BasicBlockRef*> references() override { return {}; }
protected:
void dump_tree(StringBuilder& builder) override;
};
class ControlFlowJump : public ControlFlowOperator {
public:
ControlFlowJump(BasicBlockRef block)
: m_block(block)
{
}
Vector<BasicBlockRef*> references() override;
BasicBlockRef m_block;
protected:
void dump_tree(StringBuilder& builder) override;
};
// This should be invalid enough to crash program on use.
inline NonnullRefPtr<ControlFlowOperator> const invalid_continuation = make_ref_counted<ControlFlowJump>(nullptr);
class ControlFlowBranch : public ControlFlowOperator {
public:
ControlFlowBranch(Tree condition, BasicBlockRef then, BasicBlockRef else_)
: m_condition(move(condition))
, m_then(then)
, m_else(else_)
{
}
Vector<NodeSubtreePointer> subtrees() override { return { { &m_condition } }; }
Vector<BasicBlockRef*> references() override;
Tree m_condition;
BasicBlockRef m_then;
BasicBlockRef m_else;
protected:
void dump_tree(StringBuilder& builder) override;
};
class MathematicalConstant : public Expression {
public:
MathematicalConstant(Crypto::BigFraction number)
: m_number(number)
{
}
protected:
void dump_tree(StringBuilder& builder) override;
private:
Crypto::BigFraction m_number;
};
class StringLiteral : public Expression {
public:
StringLiteral(StringView literal)
: m_literal(literal)
{
}
StringView m_literal;
protected:
void dump_tree(StringBuilder& builder) override;
};
#define ENUMERATE_UNARY_OPERATORS(F) \
F(Invalid) \
F(AssertCompletion) \
F(Minus) \
F(ReturnIfAbrubt)
#define ENUMERATE_BINARY_OPERATORS(F) \
F(Invalid) \
F(ArraySubscript) \
F(Assignment) \
F(Comma) \
F(CompareEqual) \
F(CompareGreater) \
F(CompareLess) \
F(CompareNotEqual) \
F(Declaration) \
F(Division) \
F(MemberAccess) \
F(Minus) \
F(Multiplication) \
F(Plus) \
F(Power)
#define NAME(name) name,
#define STRINGIFY(name) #name##sv,
enum class UnaryOperator {
ENUMERATE_UNARY_OPERATORS(NAME)
};
inline constexpr StringView unary_operator_names[] = {
ENUMERATE_UNARY_OPERATORS(STRINGIFY)
};
enum class BinaryOperator {
#define NAME(name) name,
ENUMERATE_BINARY_OPERATORS(NAME)
};
inline constexpr StringView binary_operator_names[] = {
ENUMERATE_BINARY_OPERATORS(STRINGIFY)
};
#undef NAME
#undef STRINGIFY
class BinaryOperation : public Expression {
public:
BinaryOperation(BinaryOperator operation, Tree left, Tree right)
: m_operation(operation)
, m_left(move(left))
, m_right(move(right))
{
}
Vector<NodeSubtreePointer> subtrees() override;
BinaryOperator m_operation;
Tree m_left;
Tree m_right;
protected:
void dump_tree(StringBuilder& builder) override;
};
class UnaryOperation : public Expression {
public:
UnaryOperation(UnaryOperator operation, Tree operand)
: m_operation(operation)
, m_operand(move(operand))
{
}
Vector<NodeSubtreePointer> subtrees() override;
UnaryOperator m_operation;
Tree m_operand;
protected:
void dump_tree(StringBuilder& builder) override;
};
class IsOneOfOperation : public Expression {
public:
IsOneOfOperation(Tree operand, Vector<Tree>&& compare_values)
: m_operand(move(operand))
, m_compare_values(move(compare_values))
{
}
Vector<NodeSubtreePointer> subtrees() override;
Tree m_operand;
Vector<Tree> m_compare_values;
protected:
void dump_tree(StringBuilder& builder) override;
};
class UnresolvedReference : public Expression {
public:
UnresolvedReference(StringView name)
: m_name(name)
{
}
StringView m_name;
protected:
void dump_tree(StringBuilder& builder) override;
};
class ReturnNode : public Node {
public:
ReturnNode(Tree return_value)
: m_return_value(move(return_value))
{
}
Vector<NodeSubtreePointer> subtrees() override;
Tree m_return_value;
protected:
void dump_tree(StringBuilder& builder) override;
};
// Although assert might seems a good candidate for ControlFlowOperator, we are not interested in
// tracking control flow after a failed assertion.
class AssertExpression : public Expression {
public:
AssertExpression(Tree condition)
: m_condition(move(condition))
{
}
Vector<NodeSubtreePointer> subtrees() override;
Tree m_condition;
protected:
void dump_tree(StringBuilder& builder) override;
};
class IfBranch : public Node {
public:
IfBranch(Tree condition, Tree branch)
: m_condition(move(condition))
, m_branch(move(branch))
{
}
Vector<NodeSubtreePointer> subtrees() override;
Tree m_condition;
Tree m_branch;
protected:
void dump_tree(StringBuilder& builder) override;
};
class ElseIfBranch : public Node {
public:
ElseIfBranch(NullableTree condition, Tree branch)
: m_condition(move(condition))
, m_branch(move(branch))
{
}
Vector<NodeSubtreePointer> subtrees() override;
NullableTree m_condition;
Tree m_branch;
protected:
void dump_tree(StringBuilder& builder) override;
};
class IfElseIfChain : public Statement {
public:
IfElseIfChain(Vector<Tree>&& conditions, Vector<Tree>&& branches, NullableTree else_branch)
: m_conditions(move(conditions))
, m_branches(move(branches))
, m_else_branch(move(else_branch))
{
VERIFY(m_branches.size() == m_conditions.size());
}
Vector<NodeSubtreePointer> subtrees() override;
// Excluding else branch, if one is present
size_t branches_count() const { return m_branches.size(); }
Vector<Tree> m_conditions;
Vector<Tree> m_branches;
NullableTree m_else_branch;
protected:
void dump_tree(StringBuilder& builder) override;
};
class TreeList : public Statement {
public:
TreeList(Vector<Tree>&& trees);
Vector<NodeSubtreePointer> subtrees() override;
bool is_list() const override { return true; }
Vector<Tree> m_trees;
protected:
void dump_tree(StringBuilder& builder) override;
};
class RecordDirectListInitialization : public Expression {
public:
struct Argument {
Tree name;
Tree value;
};
RecordDirectListInitialization(Tree type_reference, Vector<Argument>&& arguments)
: m_type_reference(move(type_reference))
, m_arguments(move(arguments))
{
}
Vector<NodeSubtreePointer> subtrees() override;
Tree m_type_reference;
Vector<Argument> m_arguments;
protected:
void dump_tree(StringBuilder& builder) override;
};
class FunctionCall : public Expression {
public:
FunctionCall(Tree name, Vector<Tree>&& arguments)
: m_name(move(name))
, m_arguments(move(arguments))
{
}
Vector<NodeSubtreePointer> subtrees() override;
Tree m_name;
Vector<Tree> m_arguments;
protected:
void dump_tree(StringBuilder& builder) override;
};
class SlotName : public Expression {
public:
SlotName(StringView member_name)
: m_member_name(member_name)
{
}
StringView m_member_name;
protected:
void dump_tree(StringBuilder& builder) override;
};
class Variable : public Expression {
public:
Variable(NamedVariableDeclarationRef name)
: m_name(move(name))
{
}
NamedVariableDeclarationRef m_name;
SSAVariableDeclarationRef m_ssa;
String name() const;
protected:
void dump_tree(StringBuilder& builder) override;
};
class Enumerator : public Expression {
public:
Enumerator(Badge<TranslationUnit>, StringView value)
: m_value(value)
{
}
protected:
void dump_tree(StringBuilder& builder) override;
private:
StringView m_value;
};
class FunctionPointer : public Expression {
public:
FunctionPointer(FunctionDeclarationRef declaration)
: m_declaration(declaration)
{
}
FunctionDeclarationRef m_declaration;
protected:
void dump_tree(StringBuilder& builder) override;
};
class List : public Expression {
public:
List(Vector<Tree>&& elements)
: m_elements(elements)
{
}
Vector<NodeSubtreePointer> subtrees() override;
protected:
void dump_tree(StringBuilder& builder) override;
private:
Vector<Tree> m_elements;
};
}
namespace AK {
template<>
struct Formatter<JSSpecCompiler::Tree> : Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, JSSpecCompiler::Tree const& tree)
{
tree->format_tree(builder.builder());
return {};
}
};
}

View file

@ -1,198 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <AK/TemporaryChange.h>
#include "AST/AST.h"
#include "Compiler/ControlFlowGraph.h"
#include "Function.h"
namespace JSSpecCompiler {
void Node::format_tree(StringBuilder& builder)
{
static int current_depth = -1;
TemporaryChange<int> depth_change(current_depth, current_depth + 1);
builder.append_repeated(' ', current_depth * 2);
dump_tree(builder);
}
template<typename... Parameters>
void Node::dump_node(StringBuilder& builder, AK::CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters)
{
AK::VariadicFormatParams<AK::AllowDebugOnlyFormatters::No, Parameters...> variadic_format_params { parameters... };
MUST(AK::vformat(builder, fmtstr.view(), variadic_format_params));
builder.append("\n"sv);
}
void ErrorNode::dump_tree(StringBuilder& builder)
{
dump_node(builder, "Error \"{}\"", m_error);
}
void WellKnownNode::dump_tree(StringBuilder& builder)
{
static constexpr StringView type_to_name[] = {
"False"sv,
"NewTarget"sv,
"Null"sv,
"This"sv,
"True"sv,
"Undefined"sv,
};
dump_node(builder, "WellKnownNode {}", type_to_name[m_type]);
}
void ControlFlowFunctionReturn::dump_tree(StringBuilder& builder)
{
dump_node(builder, "ControlFlowFunctionReturn");
m_return_value->format_tree(builder);
}
void ControlFlowJump::dump_tree(StringBuilder& builder)
{
dump_node(builder, "ControlFlowJump jump={}", m_block->m_index);
}
void ControlFlowBranch::dump_tree(StringBuilder& builder)
{
dump_node(builder, "ControlFlowBranch true={} false={}", m_then->m_index, m_else->m_index);
m_condition->format_tree(builder);
}
void MathematicalConstant::dump_tree(StringBuilder& builder)
{
String representation;
if (Crypto::UnsignedBigInteger { 1000 }.divided_by(m_number.denominator()).remainder == 0)
representation = MUST(String::from_byte_string(m_number.to_byte_string(3)));
else
representation = MUST(String::formatted("{}/{}", MUST(m_number.numerator().to_base(10)), MUST(m_number.denominator().to_base(10))));
dump_node(builder, "MathematicalConstant {}", representation);
}
void StringLiteral::dump_tree(StringBuilder& builder)
{
dump_node(builder, "StringLiteral {}", m_literal);
}
void BinaryOperation::dump_tree(StringBuilder& builder)
{
dump_node(builder, "BinaryOperation {}", binary_operator_names[to_underlying(m_operation)]);
m_left->format_tree(builder);
m_right->format_tree(builder);
}
void UnaryOperation::dump_tree(StringBuilder& builder)
{
dump_node(builder, "UnaryOperation {}", unary_operator_names[to_underlying(m_operation)]);
m_operand->format_tree(builder);
}
void IsOneOfOperation::dump_tree(StringBuilder& builder)
{
dump_node(builder, "IsOneOf");
m_operand->format_tree(builder);
for (auto const& compare_value : m_compare_values)
compare_value->format_tree(builder);
}
void UnresolvedReference::dump_tree(StringBuilder& builder)
{
dump_node(builder, "UnresolvedReference {}", m_name);
}
void ReturnNode::dump_tree(StringBuilder& builder)
{
dump_node(builder, "ReturnNode");
m_return_value->format_tree(builder);
}
void AssertExpression::dump_tree(StringBuilder& builder)
{
dump_node(builder, "AssertExpression");
m_condition->format_tree(builder);
}
void IfBranch::dump_tree(StringBuilder& builder)
{
dump_node(builder, "IfBranch");
m_condition->format_tree(builder);
m_branch->format_tree(builder);
}
void ElseIfBranch::dump_tree(StringBuilder& builder)
{
dump_node(builder, "ElseIfBranch {}", m_condition ? "ElseIf" : "Else");
if (m_condition)
m_condition->format_tree(builder);
m_branch->format_tree(builder);
}
void IfElseIfChain::dump_tree(StringBuilder& builder)
{
dump_node(builder, "IfElseIfChain");
for (size_t i = 0; i < branches_count(); ++i) {
m_conditions[i]->format_tree(builder);
m_branches[i]->format_tree(builder);
}
if (m_else_branch)
m_else_branch->format_tree(builder);
}
void TreeList::dump_tree(StringBuilder& builder)
{
dump_node(builder, "TreeList");
for (auto const& expression : m_trees)
expression->format_tree(builder);
}
void RecordDirectListInitialization::dump_tree(StringBuilder& builder)
{
dump_node(builder, "RecordDirectListInitialization");
m_type_reference->format_tree(builder);
for (auto const& argument : m_arguments)
builder.appendff("{}{}", argument.name, argument.value);
}
void FunctionCall::dump_tree(StringBuilder& builder)
{
dump_node(builder, "FunctionCall");
m_name->format_tree(builder);
for (auto const& argument : m_arguments)
argument->format_tree(builder);
}
void SlotName::dump_tree(StringBuilder& builder)
{
dump_node(builder, "Slot {}", m_member_name);
}
void Variable::dump_tree(StringBuilder& builder)
{
dump_node(builder, "Var {}", name());
}
void Enumerator::dump_tree(StringBuilder& builder)
{
dump_node(builder, "Enumerator {}", m_value);
}
void FunctionPointer::dump_tree(StringBuilder& builder)
{
dump_node(builder, "Func \"{}\"", m_declaration->name());
}
void List::dump_tree(StringBuilder& builder)
{
dump_node(builder, "List");
for (auto const& element : m_elements)
element->format_tree(builder);
}
}

View file

@ -1,35 +0,0 @@
set(SOURCES
AST/AST.cpp
AST/ASTPrinting.cpp
Compiler/CompilerPass.cpp
Compiler/ControlFlowGraph.cpp
Compiler/GenericASTPass.cpp
Compiler/Passes/CFGBuildingPass.cpp
Compiler/Passes/CFGSimplificationPass.cpp
Compiler/Passes/DeadCodeEliminationPass.cpp
Compiler/Passes/IfBranchMergingPass.cpp
Compiler/Passes/ReferenceResolvingPass.cpp
Compiler/Passes/SSABuildingPass.cpp
Parser/Algorithm.cpp
Parser/AlgorithmStep.cpp
Parser/AlgorithmStepList.cpp
Parser/CppASTConverter.cpp
Parser/Lexer.cpp
Parser/Specification.cpp
Parser/SpecificationClause.cpp
Parser/SpecificationFunction.cpp
Parser/SpecificationParsingContext.cpp
Parser/SpecificationParsingStep.cpp
Parser/TextParser.cpp
Parser/XMLUtils.cpp
Runtime/Object.cpp
Runtime/ObjectType.cpp
Runtime/Realm.cpp
DiagnosticEngine.cpp
Function.cpp
main.cpp
)
lagom_tool(JSSpecCompiler LIBS LibCpp LibMain LibXML LibCrypto)
target_include_directories(JSSpecCompiler PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_options(JSSpecCompiler PRIVATE -Wno-missing-field-initializers)

View file

@ -1,47 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Function.h>
#include <AK/StringView.h>
#include "Forward.h"
namespace JSSpecCompiler {
class CompilationStep {
public:
CompilationStep(StringView name)
: m_name(name)
{
}
virtual ~CompilationStep() = default;
virtual void run(TranslationUnitRef translation_unit) = 0;
StringView name() const { return m_name; }
private:
StringView m_name;
};
class NonOwningCompilationStep : public CompilationStep {
public:
template<typename Func>
NonOwningCompilationStep(StringView name, Func&& func)
: CompilationStep(name)
, m_func(func)
{
}
void run(TranslationUnitRef translation_unit) override { m_func(translation_unit); }
private:
AK::Function<void(TranslationUnitRef)> m_func;
};
}

View file

@ -1,20 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Compiler/CompilerPass.h"
#include "Function.h"
namespace JSSpecCompiler {
void IntraproceduralCompilerPass::run()
{
for (auto const& function : m_translation_unit->functions_to_compile()) {
m_function = function;
process_function();
}
}
}

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullRefPtr.h>
#include <AK/RecursionDecision.h>
#include "Forward.h"
namespace JSSpecCompiler {
class CompilerPass {
public:
CompilerPass(TranslationUnitRef translation_unit)
: m_translation_unit(translation_unit)
{
}
virtual ~CompilerPass() = default;
virtual void run() = 0;
protected:
TranslationUnitRef m_translation_unit;
};
class IntraproceduralCompilerPass : public CompilerPass {
public:
IntraproceduralCompilerPass(TranslationUnitRef translation_unit)
: CompilerPass(translation_unit)
{
}
void run() override final;
protected:
virtual void process_function() = 0;
FunctionDefinitionRef m_function;
};
}

View file

@ -1,39 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include "AST/AST.h"
#include "Compiler/ControlFlowGraph.h"
using namespace JSSpecCompiler;
ErrorOr<void> AK::Formatter<ControlFlowGraph>::format(FormatBuilder& format_builder, ControlFlowGraph const& control_flow_graph)
{
auto& builder = format_builder.builder();
for (auto const& block : control_flow_graph.blocks) {
builder.appendff("{}:\n", block->m_index);
for (auto const& phi_node : block->m_phi_nodes) {
builder.appendff("{} = phi(", phi_node.var->name());
for (auto const& branches : phi_node.branches) {
builder.appendff("{}: {}", branches.block->m_index, branches.value->name());
if (&branches != &phi_node.branches.last())
builder.appendff(", ");
}
builder.appendff(")\n");
}
for (auto const& expression : block->m_expressions)
builder.appendff("{}", expression);
builder.appendff("{}\n", Tree(block->m_continuation));
}
// Remove trailing \n
builder.trim(1);
return {};
}

View file

@ -1,63 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/Vector.h>
#include "Forward.h"
namespace JSSpecCompiler {
class BasicBlock : public RefCounted<BasicBlock> {
public:
struct PhiNode {
struct Branch {
BasicBlockRef block;
VariableRef value;
};
VariableRef var;
Vector<Branch> branches;
};
BasicBlock(size_t index, NonnullRefPtr<ControlFlowOperator> continuation)
: m_index(index)
, m_continuation(move(continuation))
, m_immediate_dominator(nullptr)
{
}
size_t m_index;
Vector<PhiNode> m_phi_nodes;
Vector<Tree> m_expressions;
NonnullRefPtr<ControlFlowOperator> m_continuation;
BasicBlockRef m_immediate_dominator;
};
class ControlFlowGraph : public RefCounted<ControlFlowGraph> {
public:
ControlFlowGraph() { }
size_t blocks_count() const { return blocks.size(); }
Vector<NonnullRefPtr<BasicBlock>> blocks;
BasicBlockRef start_block;
BasicBlockRef end_block;
};
}
namespace AK {
template<>
struct Formatter<JSSpecCompiler::ControlFlowGraph> : Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, JSSpecCompiler::ControlFlowGraph const& control_flow_graph);
};
}

View file

@ -1,69 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NumericLimits.h>
#include <AK/Vector.h>
namespace JSSpecCompiler {
struct VoidRef { };
template<typename T, typename NativeNodeRef = VoidRef>
class EnableGraphPointers {
public:
class VertexBase {
public:
VertexBase() = default;
VertexBase(size_t index)
: m_index(index)
{
}
bool is_invalid() const { return m_index == invalid_node; }
operator size_t() const { return m_index; }
explicit VertexBase(NativeNodeRef const& node)
requires(!IsSame<NativeNodeRef, VoidRef>)
: VertexBase(node->m_index)
{
}
auto& operator*() const { return m_instance->m_nodes[m_index]; }
auto* operator->() const { return &m_instance->m_nodes[m_index]; }
protected:
size_t m_index = invalid_node;
};
using Vertex = VertexBase;
inline static constexpr size_t invalid_node = NumericLimits<size_t>::max();
template<typename Func>
void with_graph(Func func)
{
m_instance = static_cast<T*>(this);
func();
m_instance = nullptr;
}
template<typename Func>
void with_graph(size_t n, Func func)
{
m_instance = static_cast<T*>(this);
m_instance->m_nodes.resize(n);
func();
m_instance->m_nodes.clear();
m_instance = nullptr;
}
protected:
inline static thread_local T* m_instance = nullptr;
};
}

View file

@ -1,61 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TemporaryChange.h>
#include "AST/AST.h"
#include "Compiler/GenericASTPass.h"
#include "Function.h"
namespace JSSpecCompiler {
void RecursiveASTVisitor::run_in_const_subtree(NullableTree nullable_tree)
{
if (nullable_tree) {
auto tree = nullable_tree.release_nonnull();
auto tree_copy = tree;
NodeSubtreePointer pointer { &tree };
recurse(tree, pointer);
VERIFY(tree == tree_copy);
}
}
void RecursiveASTVisitor::run_in_subtree(Tree& tree)
{
NodeSubtreePointer pointer { &tree };
recurse(tree, pointer);
}
void RecursiveASTVisitor::replace_current_node_with(NullableTree tree)
{
m_current_subtree_pointer->replace_subtree({}, move(tree));
}
RecursionDecision RecursiveASTVisitor::recurse(Tree root, NodeSubtreePointer& pointer)
{
TemporaryChange change { m_current_subtree_pointer, &pointer };
RecursionDecision decision = on_entry(root);
root = pointer.get({});
if (decision == RecursionDecision::Recurse) {
for (auto& child : root->subtrees()) {
if (recurse(child.get({}), child) == RecursionDecision::Break)
return RecursionDecision::Break;
}
}
on_leave(root);
return RecursionDecision::Continue;
}
void GenericASTPass::process_function()
{
run_in_subtree(m_function->m_ast);
}
}

View file

@ -1,45 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullRefPtr.h>
#include <AK/RecursionDecision.h>
#include "Compiler/CompilerPass.h"
namespace JSSpecCompiler {
class RecursiveASTVisitor {
public:
virtual ~RecursiveASTVisitor() = default;
void run_in_const_subtree(NullableTree tree);
void run_in_subtree(Tree& tree);
protected:
virtual RecursionDecision on_entry(Tree) { return RecursionDecision::Recurse; }
virtual void on_leave(Tree) { }
void replace_current_node_with(NullableTree tree);
private:
RecursionDecision recurse(Tree root, NodeSubtreePointer& pointer);
NodeSubtreePointer* m_current_subtree_pointer = nullptr;
};
class GenericASTPass
: public IntraproceduralCompilerPass
, protected RecursiveASTVisitor {
public:
using IntraproceduralCompilerPass::IntraproceduralCompilerPass;
protected:
void process_function() override;
};
}

View file

@ -1,107 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Enumerate.h>
#include "AST/AST.h"
#include "Compiler/Passes/CFGBuildingPass.h"
#include "Function.h"
namespace JSSpecCompiler {
void CFGBuildingPass::process_function()
{
m_cfg = m_function->m_cfg = make_ref_counted<ControlFlowGraph>();
m_current_block = m_cfg->start_block = create_empty_block();
m_cfg->end_block = create_empty_block();
m_cfg->end_block->m_continuation = make_ref_counted<ControlFlowFunctionReturn>(
make_ref_counted<Variable>(m_function->m_named_return_value));
m_is_expression_stack = { false };
run_in_subtree(m_function->m_ast);
// FIXME: What should we do if control flow reached the end of the function? Returning
// error_tree will 100% confuse future passes.
m_current_block->m_expressions.append(make_ref_counted<BinaryOperation>(
BinaryOperator::Assignment,
make_ref_counted<Variable>(m_function->m_named_return_value),
error_tree));
m_current_block->m_continuation = make_ref_counted<ControlFlowJump>(m_cfg->end_block);
}
RecursionDecision CFGBuildingPass::on_entry(Tree tree)
{
m_is_expression_stack.append(!as<Expression>(tree).is_null());
if (auto if_else_if_chain = as<IfElseIfChain>(tree); if_else_if_chain) {
auto* end_block = create_empty_block();
for (auto [i, current_condition] : enumerate(if_else_if_chain->m_conditions)) {
run_in_subtree(current_condition);
will_be_used_as_expression(current_condition);
auto* condition_block = exchange_current_with_empty();
auto* branch_entry = m_current_block;
run_in_subtree(if_else_if_chain->m_branches[i]);
auto* branch_return = exchange_current_with_empty();
branch_return->m_continuation = make_ref_counted<ControlFlowJump>(end_block);
condition_block->m_continuation = make_ref_counted<ControlFlowBranch>(current_condition, branch_entry, m_current_block);
}
if (if_else_if_chain->m_else_branch)
run_in_const_subtree(if_else_if_chain->m_else_branch);
m_current_block->m_continuation = make_ref_counted<ControlFlowJump>(end_block);
m_current_block = end_block;
return RecursionDecision::Continue;
}
if (auto return_node = as<ReturnNode>(tree); return_node) {
Tree return_assignment = make_ref_counted<BinaryOperation>(
BinaryOperator::Assignment,
make_ref_counted<Variable>(m_function->m_named_return_value),
return_node->m_return_value);
run_in_subtree(return_assignment);
auto* return_block = exchange_current_with_empty();
return_block->m_continuation = make_ref_counted<ControlFlowJump>(m_cfg->end_block);
return RecursionDecision::Continue;
}
return RecursionDecision::Recurse;
}
void CFGBuildingPass::on_leave(Tree tree)
{
(void)m_is_expression_stack.take_last();
if (!m_is_expression_stack.last() && as<Expression>(tree))
m_current_block->m_expressions.append(tree);
}
BasicBlockRef CFGBuildingPass::create_empty_block()
{
m_cfg->blocks.append(make_ref_counted<BasicBlock>(m_cfg->blocks_count(), invalid_continuation));
return m_cfg->blocks.last();
}
BasicBlockRef CFGBuildingPass::exchange_current_with_empty()
{
auto* new_block = create_empty_block();
swap(new_block, m_current_block);
return new_block;
}
void CFGBuildingPass::will_be_used_as_expression(Tree const& tree)
{
if (m_current_block->m_expressions.is_empty())
VERIFY(is<Statement>(tree.ptr()));
else
VERIFY(m_current_block->m_expressions.take_last() == tree);
}
}

View file

@ -1,39 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/TypeCasts.h>
#include "Compiler/ControlFlowGraph.h"
#include "Compiler/GenericASTPass.h"
namespace JSSpecCompiler {
class CFGBuildingPass
: public IntraproceduralCompilerPass
, private RecursiveASTVisitor {
public:
inline static constexpr StringView name = "cfg-building"sv;
using IntraproceduralCompilerPass::IntraproceduralCompilerPass;
protected:
void process_function() override;
RecursionDecision on_entry(Tree tree) override;
void on_leave(Tree tree) override;
private:
BasicBlockRef create_empty_block();
BasicBlockRef exchange_current_with_empty();
void will_be_used_as_expression(Tree const& tree);
ControlFlowGraph* m_cfg;
BasicBlockRef m_current_block;
Vector<bool> m_is_expression_stack;
};
}

View file

@ -1,85 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Compiler/Passes/CFGSimplificationPass.h"
#include "AST/AST.h"
#include "Function.h"
namespace JSSpecCompiler {
void CFGSimplificationPass::process_function()
{
auto& graph = *m_function->m_cfg;
m_replacement.clear();
m_replacement.resize(graph.blocks_count());
m_state.clear();
m_state.resize(graph.blocks_count());
for (auto const& block : graph.blocks) {
m_replacement[block->m_index] = block;
if (block->m_expressions.size() == 0)
if (auto jump = as<ControlFlowJump>(block->m_continuation); jump)
m_replacement[block->m_index] = jump->m_block;
}
for (size_t i = 0; i < graph.blocks_count(); ++i)
if (m_state[i] == State::NotUsed)
VERIFY(compute_replacement_block(i));
// Fixing references
graph.start_block = m_replacement[graph.start_block->m_index];
for (auto const& block : graph.blocks) {
for (auto* next_block : block->m_continuation->references())
*next_block = m_replacement[(*next_block)->m_index];
}
// Removing unused nodes
m_state.span().fill(State::NotUsed);
compute_referenced_blocks(graph.start_block);
size_t j = 0;
for (size_t i = 0; i < graph.blocks_count(); ++i) {
if (m_state[graph.blocks[i]->m_index] == State::Used) {
graph.blocks[j] = graph.blocks[i];
graph.blocks[j]->m_index = j;
++j;
}
}
graph.blocks.shrink(j);
}
bool CFGSimplificationPass::compute_replacement_block(size_t i)
{
if (m_state[i] == State::CurrentlyInside)
return false;
VERIFY(m_state[i] == State::NotUsed);
m_state[i] = State::CurrentlyInside;
size_t j = m_replacement[i]->m_index;
if (i == j)
return true;
if (m_state[j] == State::NotUsed)
if (!compute_replacement_block(j))
return false;
m_replacement[i] = m_replacement[j];
m_state[i] = State::Used;
return true;
}
void CFGSimplificationPass::compute_referenced_blocks(BasicBlockRef block)
{
if (m_state[block->m_index] == State::Used)
return;
m_state[block->m_index] = State::Used;
for (auto* next : block->m_continuation->references())
compute_referenced_blocks(*next);
}
}

View file

@ -1,39 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Compiler/CompilerPass.h"
#include "Compiler/ControlFlowGraph.h"
namespace JSSpecCompiler {
// CFGSimplificationPass removes empty `BasicBlock`s with an unconditional jump continuation. It
// also removes unreferenced blocks from the graph.
class CFGSimplificationPass : public IntraproceduralCompilerPass {
public:
inline static constexpr StringView name = "cfg-simplification"sv;
using IntraproceduralCompilerPass::IntraproceduralCompilerPass;
protected:
void process_function() override;
private:
enum class State : char {
NotUsed,
CurrentlyInside,
Used,
};
bool compute_replacement_block(size_t i);
void compute_referenced_blocks(BasicBlockRef block);
Vector<BasicBlockRef> m_replacement;
Vector<State> m_state;
};
}

View file

@ -1,84 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Compiler/Passes/DeadCodeEliminationPass.h"
#include "AST/AST.h"
#include "Compiler/ControlFlowGraph.h"
#include "Compiler/StronglyConnectedComponents.h"
#include "Function.h"
namespace JSSpecCompiler {
void DeadCodeEliminationPass::process_function()
{
with_graph(m_function->m_local_ssa_variables.size(), [&] {
remove_unused_phi_nodes();
});
m_function->reindex_ssa_variables();
}
DeadCodeEliminationPass::Vertex DeadCodeEliminationPass::as_vertex(Variable* variable)
{
return Vertex(variable->m_ssa);
}
RecursionDecision DeadCodeEliminationPass::on_entry(Tree tree)
{
if (tree->is_statement())
TODO();
return RecursionDecision::Recurse;
}
void DeadCodeEliminationPass::on_leave(Tree tree)
{
if (auto variable = as<Variable>(tree); variable)
as_vertex(variable)->is_referenced = true;
}
void DeadCodeEliminationPass::remove_unused_phi_nodes()
{
for (auto const& block : m_function->m_cfg->blocks) {
for (auto const& phi_node : block->m_phi_nodes) {
auto to = as_vertex(phi_node.var);
for (auto const& branch : phi_node.branches) {
auto from = as_vertex(branch.value);
from->outgoing_edges.append(to);
to->incoming_edges.append(from);
}
}
for (auto& expr : block->m_expressions)
run_in_subtree(expr);
run_in_const_subtree(block->m_continuation);
}
// FIXME?: There surely must be a way to do this in a linear time without finding strongly
// connected components.
for (auto const& component : find_strongly_connected_components(m_nodes)) {
bool is_referenced = false;
for (Vertex u : component)
for (Vertex v : u->outgoing_edges)
is_referenced |= v->is_referenced;
if (is_referenced)
for (Vertex u : component)
u->is_referenced = true;
}
for (auto const& block : m_function->m_cfg->blocks) {
block->m_phi_nodes.remove_all_matching([&](auto const& node) {
return !as_vertex(node.var)->is_referenced;
});
}
m_function->m_local_ssa_variables.remove_all_matching([&](auto const& variable) {
return !Vertex(variable)->is_referenced;
});
}
}

View file

@ -1,45 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Compiler/EnableGraphPointers.h"
#include "Compiler/GenericASTPass.h"
#include "Compiler/StronglyConnectedComponents.h"
namespace JSSpecCompiler {
class DeadCodeEliminationPass
: public IntraproceduralCompilerPass
, private RecursiveASTVisitor
, private EnableGraphPointers<DeadCodeEliminationPass, SSAVariableDeclarationRef> {
public:
inline static constexpr StringView name = "dce"sv;
using IntraproceduralCompilerPass::IntraproceduralCompilerPass;
protected:
void process_function() override;
private:
friend EnableGraphPointers;
static Vertex as_vertex(Variable* variable);
RecursionDecision on_entry(Tree tree) override;
void on_leave(Tree tree) override;
void remove_unused_phi_nodes();
struct NodeData {
Vector<Vertex> outgoing_edges;
Vector<Vertex> incoming_edges;
bool is_referenced = false;
};
Vector<NodeData> m_nodes;
};
}

View file

@ -1,97 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypeCasts.h>
#include "AST/AST.h"
#include "Compiler/Passes/IfBranchMergingPass.h"
namespace JSSpecCompiler {
RecursionDecision IfBranchMergingPass::on_entry(Tree tree)
{
if (auto list = as<TreeList>(tree); list) {
Vector<Tree> result;
Vector<Tree> unmerged_branches;
auto merge_if_needed = [&] {
if (!unmerged_branches.is_empty()) {
result.append(merge_branches(unmerged_branches));
unmerged_branches.clear();
}
};
for (auto const& node : list->m_trees) {
if (is<IfBranch>(node.ptr())) {
merge_if_needed();
unmerged_branches.append(node);
} else if (is<ElseIfBranch>(node.ptr())) {
unmerged_branches.append(node);
} else {
merge_if_needed();
result.append(node);
}
}
merge_if_needed();
list->m_trees = move(result);
}
return RecursionDecision::Recurse;
}
Tree IfBranchMergingPass::merge_branches(Vector<Tree> const& unmerged_branches)
{
static Tree const error = make_ref_counted<ErrorNode>("Cannot make sense of if-elseif-else chain"sv);
VERIFY(unmerged_branches.size() >= 1);
Vector<Tree> conditions;
Vector<Tree> branches;
NullableTree else_branch;
if (auto if_branch = as<IfBranch>(unmerged_branches[0]); if_branch) {
conditions.append(if_branch->m_condition);
branches.append(if_branch->m_branch);
} else {
return error;
}
for (size_t i = 1; i < unmerged_branches.size(); ++i) {
auto branch = as<ElseIfBranch>(unmerged_branches[i]);
if (!branch)
return error;
if (!branch->m_condition) {
// There might be situation like:
// 1. If <condition>, then
// ...
// 2. Else,
// a. If <condition>, then
// ...
// 3. Else,
// ...
auto substep_list = as<TreeList>(branch->m_branch);
if (substep_list && substep_list->m_trees.size() == 1) {
if (auto nested_if = as<IfBranch>(substep_list->m_trees[0]); nested_if)
branch = make_ref_counted<ElseIfBranch>(nested_if->m_condition, nested_if->m_branch);
}
}
if (branch->m_condition) {
conditions.append(branch->m_condition.release_nonnull());
branches.append(branch->m_branch);
} else {
if (i + 1 != unmerged_branches.size())
return error;
else_branch = branch->m_branch;
}
}
return make_ref_counted<IfElseIfChain>(move(conditions), move(branches), else_branch);
}
}

View file

@ -1,38 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Compiler/GenericASTPass.h"
namespace JSSpecCompiler {
// IfBranchMergingPass, unsurprisingly, merges if-elseif-else chains, represented as a separate
// nodes after parsing, into one IfElseIfChain node. It also deals with the following nonsense from
// the spec:
// ```
// 1. If <condition>, then
// ...
// 2. Else,
// a. If <condition>, then
// ...
// 3. Else,
// ...
// ```
class IfBranchMergingPass : public GenericASTPass {
public:
inline static constexpr StringView name = "if-branch-merging"sv;
using GenericASTPass::GenericASTPass;
protected:
RecursionDecision on_entry(Tree tree) override;
private:
static Tree merge_branches(Vector<Tree> const& unmerged_branches);
};
}

View file

@ -1,61 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/HashMap.h>
#include "AST/AST.h"
#include "Compiler/Passes/ReferenceResolvingPass.h"
#include "Function.h"
namespace JSSpecCompiler {
void ReferenceResolvingPass::process_function()
{
for (auto argument : m_function->arguments())
m_function->m_local_variables.set(argument.name, make_ref_counted<NamedVariableDeclaration>(argument.name));
GenericASTPass::process_function();
}
RecursionDecision ReferenceResolvingPass::on_entry(Tree tree)
{
if (auto binary_operation = as<BinaryOperation>(tree); binary_operation) {
if (binary_operation->m_operation != BinaryOperator::Declaration)
return RecursionDecision::Recurse;
binary_operation->m_operation = BinaryOperator::Assignment;
if (auto variable_name = as<UnresolvedReference>(binary_operation->m_left); variable_name) {
auto name = variable_name->m_name;
if (!m_function->m_local_variables.contains(name))
m_function->m_local_variables.set(name, make_ref_counted<NamedVariableDeclaration>(name));
}
}
return RecursionDecision::Recurse;
}
void ReferenceResolvingPass::on_leave(Tree tree)
{
if (auto reference = as<UnresolvedReference>(tree); reference) {
auto name = reference->m_name;
if (name.starts_with("[["sv) && name.ends_with("]]"sv)) {
replace_current_node_with(make_ref_counted<SlotName>(name.substring_view(2, name.length() - 4)));
return;
}
if (auto it = m_function->m_local_variables.find(name); it != m_function->m_local_variables.end()) {
replace_current_node_with(make_ref_counted<Variable>(it->value));
return;
}
if (auto function = m_translation_unit->find_abstract_operation_by_name(name)) {
replace_current_node_with(make_ref_counted<FunctionPointer>(function));
return;
}
}
}
}

View file

@ -1,27 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Compiler/GenericASTPass.h"
namespace JSSpecCompiler {
// ReferenceResolvingPass collects all variable names declared in the function and replaces
// UnresolvedReference nodes with either SlotName, Variable, or FunctionPointer nodes.
class ReferenceResolvingPass : public GenericASTPass {
public:
inline static constexpr StringView name = "reference-resolving"sv;
using GenericASTPass::GenericASTPass;
protected:
void process_function() override;
RecursionDecision on_entry(Tree tree) override;
void on_leave(Tree tree) override;
};
}

View file

@ -1,454 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Enumerate.h>
#include <AK/Queue.h>
#include "AST/AST.h"
#include "Compiler/GenericASTPass.h"
#include "Compiler/Passes/SSABuildingPass.h"
#include "Function.h"
namespace JSSpecCompiler {
void SSABuildingPass::process_function()
{
m_dtree_timer = 0;
m_order.clear();
m_mark_version = 1;
m_def_stack.clear();
m_next_id.clear();
m_undo_vector.clear();
m_graph = m_function->m_cfg;
with_graph(m_graph->blocks_count(), [&] {
compute_dominator_tree();
compute_dominance_frontiers();
place_phi_nodes();
rename_variables();
});
}
// ===== compute_dominator_tree =====
namespace {
class DSU {
struct NodeData {
size_t sdom;
size_t parent;
};
public:
DSU(size_t n)
: n(n)
{
m_nodes.resize(n);
for (size_t i = 0; i < n; ++i)
m_nodes[i] = { i, i };
}
NodeData get(size_t u)
{
if (m_nodes[u].parent == u)
return { n, u };
auto [sdom, root] = get(m_nodes[u].parent);
sdom = min(sdom, m_nodes[u].sdom);
return m_nodes[u] = { sdom, root };
}
void merge(size_t u, size_t v, size_t v_sdom)
{
m_nodes[v] = { v_sdom, u };
}
private:
size_t n;
Vector<NodeData> m_nodes;
};
}
void SSABuildingPass::compute_order(BasicBlockRef u, Vertex parent)
{
if (m_nodes[u->m_index].is_used)
return;
m_nodes[u->m_index].is_used = true;
Vertex reordered_u = m_order.size();
m_order.append(RefPtr<BasicBlock>(u).release_nonnull());
reordered_u->parent = parent;
for (auto* v : u->m_continuation->references())
compute_order(*v, reordered_u);
}
void SSABuildingPass::compute_dominator_tree()
{
size_t n = m_graph->blocks_count();
m_nodes.resize(n);
// Algorithm is from https://tanujkhattar.wordpress.com/2016/01/11/dominator-tree-of-a-directed-graph/ ,
// an author writes awful CP-style write-only code, but the explanation is pretty good.
// Step 1
compute_order(m_graph->start_block);
VERIFY(m_order.size() == n);
for (size_t i = 0; i < n; ++i)
m_order[i]->m_index = i;
m_graph->blocks = m_order;
for (size_t i = 0; i < n; ++i) {
Vertex u = i;
for (auto* reference : u.block()->m_continuation->references()) {
Vertex v { *reference };
v->incoming_edges.append(u);
u->outgoing_edges.append(v);
}
}
// Steps 2 & 3
DSU dsu(n);
for (size_t i = n - 1; i > 0; --i) {
Vertex u = i;
Vertex& current_sdom = u->semi_dominator;
current_sdom = n;
for (Vertex v : u->incoming_edges) {
if (v < u)
current_sdom = min(current_sdom, v);
else
current_sdom = min(current_sdom, dsu.get(v).sdom);
}
current_sdom->buckets.append(u);
for (Vertex w : u->buckets) {
Vertex v = dsu.get(w).sdom;
if (v->semi_dominator == w->semi_dominator)
w->immediate_dominator = v->semi_dominator;
else
w->immediate_dominator = v;
}
dsu.merge(u->parent, u, current_sdom);
}
m_nodes[0].immediate_dominator = invalid_node;
for (size_t i = 1; i < n; ++i) {
Vertex u = i;
if (u->immediate_dominator.is_invalid())
u->immediate_dominator = 0;
else if (u->immediate_dominator != u->semi_dominator)
u->immediate_dominator = u->immediate_dominator->immediate_dominator;
}
// Populate dtree_children & BasicBlock::immediate_dominator
for (size_t i = 0; i < n; ++i) {
Vertex u = i;
if (i != 0) {
u.block()->m_immediate_dominator = u->immediate_dominator.block();
u->immediate_dominator->dtree_children.append(u);
} else {
u.block()->m_immediate_dominator = nullptr;
}
}
}
// ===== compute_dominance_frontiers =====
template<typename... Args>
Vector<SSABuildingPass::Vertex> SSABuildingPass::unique(Args const&... args)
{
++m_mark_version;
Vector<Vertex> result;
(([&](auto const& list) {
for (Vertex u : list) {
if (u->mark != m_mark_version) {
u->mark = m_mark_version;
result.append(u);
}
}
})(args),
...);
return result;
}
void SSABuildingPass::compute_dtree_tin_tout(Vertex u)
{
u->tin = m_dtree_timer++;
for (Vertex v : u->dtree_children)
compute_dtree_tin_tout(v);
u->tout = m_dtree_timer++;
}
bool SSABuildingPass::is_strictly_dominating(Vertex u, Vertex v)
{
return u != v && u->tin <= v->tin && v->tout <= u->tout;
}
void SSABuildingPass::compute_dominance_frontiers()
{
compute_dtree_tin_tout(0);
// Algorithm from https://en.wikipedia.org/wiki/Static_single-assignment_form#Converting%20to%20SSA:~:text=their%20paper%20titled-,A%20Simple%2C%20Fast%20Dominance%20Algorithm,-%3A%5B13%5D .
// DF(u) = {w : !(u sdom w) /\ (\exists v \in incoming_edges(v) : u dom v)}
for (size_t wi = 0; wi < m_nodes.size(); ++wi) {
Vertex w = wi;
for (Vertex v : w->incoming_edges) {
Vertex u = v;
while (u != invalid_node && !is_strictly_dominating(u, w)) {
u->d_frontier.append(w);
u = u->immediate_dominator;
}
}
}
for (size_t i = 0; i < m_nodes.size(); ++i) {
Vertex u = i;
u->d_frontier = unique(u->d_frontier);
}
}
// ===== place_phi_nodes =====
namespace {
class VariableAssignmentCollector : private RecursiveASTVisitor {
public:
VariableAssignmentCollector(OrderedHashMap<NamedVariableDeclarationRef, Vector<BasicBlockRef>>& declarations)
: m_declarations(declarations)
{
}
void run(BasicBlockRef block)
{
m_current_block = block;
for (auto& expression : block->m_expressions)
run_in_subtree(expression);
run_in_const_subtree(block->m_continuation);
}
protected:
RecursionDecision on_entry(Tree tree) override
{
if (tree->is_statement())
TODO();
return RecursionDecision::Recurse;
}
void on_leave(Tree tree) override
{
if (auto binary_operation = as<BinaryOperation>(tree); binary_operation) {
if (binary_operation->m_operation != BinaryOperator::Assignment)
return;
if (auto variable = as<Variable>(binary_operation->m_left); variable) {
auto& vector = m_declarations.get(variable->m_name).value();
if (vector.is_empty() || vector.last() != m_current_block)
vector.append(m_current_block);
}
}
}
private:
BasicBlockRef m_current_block;
OrderedHashMap<NamedVariableDeclarationRef, Vector<BasicBlockRef>>& m_declarations;
};
}
void SSABuildingPass::add_phi_node(BasicBlockRef block, NamedVariableDeclarationRef decl)
{
BasicBlock::PhiNode node { .var = make_ref_counted<Variable>(decl) };
for (Vertex incoming : Vertex(block)->incoming_edges) {
BasicBlockRef incoming_block = incoming.block();
auto value = make_ref_counted<Variable>(decl);
node.branches.append({ .block = incoming_block, .value = value });
}
block->m_phi_nodes.append(move(node));
}
void SSABuildingPass::place_phi_nodes()
{
// Entry block has implicit declarations of all variables.
OrderedHashMap<NamedVariableDeclarationRef, Vector<BasicBlockRef>> m_declarations;
for (auto const& [name, var_decl] : m_function->m_local_variables)
m_declarations.set(var_decl, { m_order[0] });
m_declarations.set(m_function->m_named_return_value, { m_order[0] });
VariableAssignmentCollector collector(m_declarations);
for (auto const& block : m_order)
collector.run(block);
for (auto const& [decl, blocks] : m_declarations) {
++m_mark_version;
Queue<BasicBlockRef> queue;
for (auto const& block : blocks)
queue.enqueue(block);
while (!queue.is_empty()) {
Vertex u(queue.dequeue());
for (Vertex frontier : u->d_frontier) {
if (frontier->mark == m_mark_version)
continue;
frontier->mark = m_mark_version;
add_phi_node(frontier.block(), decl);
}
}
}
}
// ===== rename_variables =====
namespace {
template<typename CreateSSAVariableFunc, typename RenameVariableFunc>
class VariableRenamer : private RecursiveASTVisitor {
public:
VariableRenamer(CreateSSAVariableFunc create, RenameVariableFunc rename)
: m_create(create)
, m_rename(rename)
{
}
void run(BasicBlockRef block)
{
for (auto& expression : block->m_expressions)
run_in_subtree(expression);
run_in_const_subtree(block->m_continuation);
}
protected:
RecursionDecision on_entry(Tree tree) override
{
if (tree->is_statement())
TODO();
auto binary_operation = as<BinaryOperation>(tree);
if (binary_operation && binary_operation->m_operation == BinaryOperator::Assignment) {
run_in_subtree(binary_operation->m_right);
if (auto variable = as<Variable>(binary_operation->m_left); variable) {
m_create(variable->m_name);
m_rename(variable.release_nonnull());
} else {
run_in_subtree(binary_operation->m_left);
}
return RecursionDecision::Continue;
}
if (auto variable = as<Variable>(tree); variable) {
m_rename(variable.release_nonnull());
return RecursionDecision::Continue;
}
return RecursionDecision::Recurse;
}
private:
CreateSSAVariableFunc m_create;
RenameVariableFunc m_rename;
};
}
void SSABuildingPass::make_new_ssa_variable_for(NamedVariableDeclarationRef var)
{
m_undo_vector.append(var);
u64 id = 0;
if (auto it = m_next_id.find(var); it == m_next_id.end())
m_next_id.set(var, 1);
else
id = it->value++;
auto ssa_decl = make_ref_counted<SSAVariableDeclaration>(id);
m_function->m_local_ssa_variables.append(ssa_decl);
if (auto it = m_def_stack.find(var); it == m_def_stack.end())
m_def_stack.set(var, { ssa_decl });
else
it->value.append(ssa_decl);
}
void SSABuildingPass::rename_variable(VariableRef var)
{
var->m_ssa = m_def_stack.get(var->m_name).value().last();
}
void SSABuildingPass::rename_variables(Vertex u, Vertex from)
{
size_t rollback_point = m_undo_vector.size();
for (auto& phi_node : u.block()->m_phi_nodes) {
// TODO: Find the right branch index without iterating through all of the branches.
bool found = false;
for (auto& branch : phi_node.branches) {
if (branch.block->m_index == from) {
rename_variable(branch.value);
found = true;
break;
}
}
VERIFY(found);
}
if (u->mark == m_mark_version)
return;
u->mark = m_mark_version;
for (auto& phi_node : u.block()->m_phi_nodes) {
make_new_ssa_variable_for(phi_node.var->m_name);
rename_variable(phi_node.var);
}
VariableRenamer renamer(
[&](NamedVariableDeclarationRef decl) {
make_new_ssa_variable_for(move(decl));
},
[&](VariableRef var) {
rename_variable(move(var));
});
renamer.run(u.block());
if (auto function_return = as<ControlFlowFunctionReturn>(u.block()->m_continuation); function_return) {
// CFG should have exactly one ControlFlowFunctionReturn.
VERIFY(m_function->m_return_value == nullptr);
m_function->m_return_value = function_return->m_return_value->m_ssa;
}
for (size_t j : u->outgoing_edges)
rename_variables(j, u);
while (m_undo_vector.size() > rollback_point)
(void)m_def_stack.get(m_undo_vector.take_last()).value().take_last();
}
void SSABuildingPass::rename_variables()
{
HashMap<StringView, size_t> argument_index_by_name;
for (auto [i, argument] : enumerate(m_function->arguments()))
argument_index_by_name.set(argument.name, i);
m_function->m_ssa_arguments.resize(m_function->arguments().size());
for (auto const& [name, var_decl] : m_function->m_local_variables) {
make_new_ssa_variable_for(var_decl);
if (auto maybe_index = argument_index_by_name.get(name); maybe_index.has_value()) {
size_t index = maybe_index.value();
m_function->m_ssa_arguments[index] = m_def_stack.get(var_decl).value()[0];
}
}
make_new_ssa_variable_for(m_function->m_named_return_value);
++m_mark_version;
rename_variables(0);
VERIFY(m_function->m_return_value);
m_function->reindex_ssa_variables();
}
}

View file

@ -1,91 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/HashMap.h>
#include "Compiler/CompilerPass.h"
#include "Compiler/ControlFlowGraph.h"
#include "Compiler/EnableGraphPointers.h"
namespace JSSpecCompiler {
// TODO: Add a LOT of unit tests.
class SSABuildingPass
: public IntraproceduralCompilerPass
, private EnableGraphPointers<SSABuildingPass, BasicBlockRef> {
public:
inline static constexpr StringView name = "ssa-building"sv;
using IntraproceduralCompilerPass::IntraproceduralCompilerPass;
protected:
void process_function() override;
private:
friend EnableGraphPointers;
class Vertex : public VertexBase {
public:
using VertexBase::VertexBase;
BasicBlockRef block() const { return m_instance->m_order[m_index]; }
};
void compute_order(BasicBlockRef u, Vertex parent = invalid_node);
void compute_dominator_tree();
template<typename... Args>
Vector<Vertex> unique(Args const&... args);
void compute_dtree_tin_tout(Vertex u);
bool is_strictly_dominating(Vertex u, Vertex v);
void compute_dominance_frontiers();
void add_phi_node(BasicBlockRef block, NamedVariableDeclarationRef decl);
void place_phi_nodes();
void make_new_ssa_variable_for(NamedVariableDeclarationRef var);
void rename_variable(VariableRef var);
void rename_variables(Vertex u, Vertex from = invalid_node);
void rename_variables();
struct NodeData {
Vector<Vertex> incoming_edges;
Vector<Vertex> outgoing_edges;
Vector<Vertex> buckets;
bool is_used = false;
Vertex parent;
Vertex semi_dominator;
Vertex immediate_dominator;
Vector<Vertex> dtree_children;
u64 tin, tout;
Vector<Vertex> d_frontier;
HashMap<NamedVariableDeclarationRef, Vertex> phi_nodes;
u64 mark = 0;
};
u64 m_dtree_timer;
Vector<NodeData> m_nodes;
Vector<NonnullRefPtr<BasicBlock>> m_order;
u64 m_mark_version;
HashMap<NamedVariableDeclarationRef, Vector<SSAVariableDeclarationRef>> m_def_stack;
HashMap<NamedVariableDeclarationRef, u64> m_next_id;
Vector<NamedVariableDeclarationRef> m_undo_vector;
ControlFlowGraph* m_graph;
};
}

View file

@ -1,86 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Vector.h>
#include "Compiler/EnableGraphPointers.h"
namespace JSSpecCompiler {
namespace Detail {
template<typename GraphVertex, typename GraphNode>
class StronglyConnectedComponents
: private EnableGraphPointers<StronglyConnectedComponents<GraphVertex, GraphNode>> {
using Self = StronglyConnectedComponents<GraphVertex, GraphNode>;
using Vertex = typename EnableGraphPointers<Self>::Vertex;
public:
StronglyConnectedComponents(Vector<GraphNode> const& graph)
: m_graph(graph)
{
}
Vector<Vector<GraphVertex>> find()
{
Vector<Vector<GraphVertex>> result;
size_t n = m_graph.size();
Self::with_graph(n, [&] {
for (size_t i = 0; i < m_graph.size(); ++i)
find_order(i);
for (size_t i = n; i--;) {
if (!m_order[i]->is_processed) {
result.empend();
find_component(GraphVertex(m_order[i]), result.last());
}
}
});
return result;
}
private:
friend EnableGraphPointers<Self>;
void find_order(Vertex u)
{
if (u->is_visited)
return;
u->is_visited = true;
for (auto v : GraphVertex(u)->incoming_edges)
find_order(Vertex(v));
m_order.append(u);
}
void find_component(GraphVertex u, Vector<GraphVertex>& current_scc)
{
current_scc.empend(u);
Vertex(u)->is_processed = true;
for (auto v : u->outgoing_edges)
if (!Vertex(v)->is_processed)
find_component(v, current_scc);
}
struct NodeData {
bool is_visited = false;
bool is_processed = false;
};
Vector<GraphNode> const& m_graph;
Vector<NodeData> m_nodes;
Vector<Vertex> m_order;
};
}
template<typename NodeData>
auto find_strongly_connected_components(Vector<NodeData> const& graph)
{
using Vertex = RemoveCVReference<decltype(graph[0].outgoing_edges[0])>;
return Detail::StronglyConnectedComponents<Vertex, NodeData>(graph).find();
}
}

View file

@ -1,74 +0,0 @@
/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* 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<StringView, 4> 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<StringView, 4> 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));
}
}

View file

@ -1,90 +0,0 @@
/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/FlyString.h>
#include <AK/QuickSort.h>
#include <AK/String.h>
#include <LibXML/DOM/Node.h>
namespace JSSpecCompiler {
struct LogicalLocation : RefCounted<LogicalLocation> {
String section;
String step;
};
struct Location {
StringView filename;
size_t line = 0;
size_t column = 0;
RefPtr<LogicalLocation> 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<typename... Parameters> \
void name_(Location const& location, AK::CheckedFormatString<Parameters...>&& 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<Diagnostic> notes;
bool operator<(Diagnostic const& other) const;
void format_into(StringBuilder& builder, UseColor) const;
};
void add_diagnostic(Diagnostic&& diagnostic);
Vector<Diagnostic> m_diagnostics;
bool m_has_fatal_errors = false;
};
}

View file

@ -1,92 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
namespace JSSpecCompiler {
// AST/AST.h
class NodeSubtreePointer;
class VariableDeclaration;
using VariableDeclarationRef = NonnullRefPtr<VariableDeclaration>;
class NamedVariableDeclaration;
using NamedVariableDeclarationRef = NonnullRefPtr<NamedVariableDeclaration>;
class SSAVariableDeclaration;
using SSAVariableDeclarationRef = RefPtr<SSAVariableDeclaration>;
class Node;
using NullableTree = RefPtr<Node>;
using Tree = NonnullRefPtr<Node>;
class Statement;
class Expression;
class ErrorNode;
class ControlFlowOperator;
class ControlFlowFunctionReturn;
class ControlFlowJump;
class ControlFlowBranch;
class MathematicalConstant;
class StringLiteral;
class BinaryOperation;
class UnaryOperation;
class IsOneOfOperation;
class UnresolvedReference;
class ReturnNode;
class AssertExpression;
class IfBranch;
class ElseIfBranch;
class IfElseIfChain;
class TreeList;
class RecordDirectListInitialization;
class FunctionCall;
class SlotName;
class Enumerator;
using EnumeratorRef = NonnullRefPtr<Enumerator>;
class Variable;
using VariableRef = NonnullRefPtr<Variable>;
class FunctionPointer;
using FunctionPointerRef = NonnullRefPtr<FunctionPointer>;
// Compiler/ControlFlowGraph.h
class BasicBlock;
using BasicBlockRef = BasicBlock*;
class ControlFlowGraph;
// Compiler/GenericASTPass.h
class RecursiveASTVisitor;
// Parser/SpecParser.h
class SpecificationParsingContext;
class AlgorithmStep;
class AlgorithmStepList;
class Algorithm;
class SpecificationFunction;
class SpecificationClause;
class Specification;
namespace Runtime {
class Cell;
class Object;
class ObjectType;
class Realm;
}
// DiagnosticEngine.h
struct LogicalLocation;
struct Location;
class DiagnosticEngine;
// Function.h
class TranslationUnit;
using TranslationUnitRef = TranslationUnit*;
class FunctionDeclaration;
using FunctionDeclarationRef = FunctionDeclaration*;
class FunctionDefinition;
using FunctionDefinitionRef = FunctionDefinition*;
}

View file

@ -1,98 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Function.h"
#include "AST/AST.h"
#include "Compiler/ControlFlowGraph.h"
#include "Runtime/Realm.h"
namespace JSSpecCompiler {
TranslationUnit::TranslationUnit(StringView filename)
: m_filename(filename)
, m_realm(make<Runtime::Realm>(m_diagnostic_engine))
{
}
TranslationUnit::~TranslationUnit() = default;
void TranslationUnit::adopt_declaration(NonnullRefPtr<FunctionDeclaration>&& declaration)
{
if (auto decl_name = declaration->declaration(); decl_name.has<AbstractOperationDeclaration>())
m_abstract_operation_index.set(decl_name.get<AbstractOperationDeclaration>().name, declaration.ptr());
m_declarations_owner.append(move(declaration));
}
void TranslationUnit::adopt_function(NonnullRefPtr<FunctionDefinition>&& definition)
{
m_functions_to_compile.append(definition);
adopt_declaration(definition);
}
FunctionDeclarationRef TranslationUnit::find_abstract_operation_by_name(StringView name) const
{
auto it = m_abstract_operation_index.find(name);
if (it == m_abstract_operation_index.end())
return nullptr;
return it->value;
}
EnumeratorRef TranslationUnit::get_node_for_enumerator_value(StringView value)
{
if (auto it = m_enumerator_nodes.find(value); it != m_enumerator_nodes.end())
return it->value;
auto enumerator = NonnullRefPtr(NonnullRefPtr<Enumerator>::Adopt, *new Enumerator { {}, value });
m_enumerator_nodes.set(value, enumerator);
return enumerator;
}
FunctionDeclaration::FunctionDeclaration(Declaration&& declaration, Location location)
: m_declaration(move(declaration))
, m_location(location)
{
}
String FunctionDeclaration::name() const
{
return m_declaration.visit(
[&](AbstractOperationDeclaration const& abstract_operation) {
return abstract_operation.name.to_string();
},
[&](MethodDeclaration const& method) {
return MUST(String::formatted("%{}%", method.name.to_string()));
},
[&](AccessorDeclaration const& accessor) {
return MUST(String::formatted("%get {}%", accessor.name.to_string()));
});
}
ReadonlySpan<FunctionArgument> FunctionDeclaration::arguments() const
{
return m_declaration.visit(
[&](AccessorDeclaration const&) {
return ReadonlySpan<FunctionArgument> {};
},
[&](auto const& declaration) {
return declaration.arguments.span();
});
}
FunctionDefinition::FunctionDefinition(Declaration&& declaration, Location location, Tree ast)
: FunctionDeclaration(move(declaration), location)
, m_ast(move(ast))
, m_named_return_value(make_ref_counted<NamedVariableDeclaration>("$return"sv))
{
}
void FunctionDefinition::reindex_ssa_variables()
{
size_t index = 0;
for (auto const& var : m_local_ssa_variables)
var->m_index = index++;
}
}

View file

@ -1,162 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/HashMap.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/StringView.h>
#include "DiagnosticEngine.h"
#include "Forward.h"
namespace JSSpecCompiler {
class TranslationUnit {
public:
TranslationUnit(StringView filename);
~TranslationUnit();
void adopt_declaration(NonnullRefPtr<FunctionDeclaration>&& declaration);
void adopt_function(NonnullRefPtr<FunctionDefinition>&& definition);
FunctionDeclarationRef find_abstract_operation_by_name(StringView name) const;
StringView filename() const { return m_filename; }
DiagnosticEngine& diag() { return m_diagnostic_engine; }
Vector<FunctionDefinitionRef> functions_to_compile() const { return m_functions_to_compile; }
EnumeratorRef get_node_for_enumerator_value(StringView value);
Runtime::Realm* realm() const { return m_realm; }
private:
StringView m_filename;
DiagnosticEngine m_diagnostic_engine;
Vector<FunctionDefinitionRef> m_functions_to_compile;
Vector<NonnullRefPtr<FunctionDeclaration>> m_declarations_owner;
HashMap<FlyString, FunctionDeclarationRef> m_abstract_operation_index;
HashMap<StringView, EnumeratorRef> m_enumerator_nodes;
NonnullOwnPtr<Runtime::Realm> m_realm;
};
struct FunctionArgument {
StringView name;
size_t optional_arguments_group;
};
class QualifiedName {
public:
QualifiedName() { }
QualifiedName(ReadonlySpan<StringView> parsed_name)
{
m_components.ensure_capacity(parsed_name.size());
for (auto component : parsed_name)
m_components.unchecked_append(MUST(FlyString::from_utf8(component)));
}
QualifiedName(ReadonlySpan<FlyString> parsed_name)
{
m_components.ensure_capacity(parsed_name.size());
for (auto component : parsed_name)
m_components.unchecked_append(component);
}
String to_string() const
{
return MUST(String::join("."sv, m_components));
}
Vector<FlyString> const& components() const
{
return m_components;
}
FlyString last_component() const
{
return m_components.last();
}
ReadonlySpan<FlyString> without_last_component() const
{
return components().span().slice(0, components().size() - 1);
}
QualifiedName slice(size_t start, size_t length) const
{
return { m_components.span().slice(start, length) };
}
QualifiedName with_appended(FlyString component) const
{
auto new_components = m_components;
new_components.append(component);
return { new_components };
}
private:
Vector<FlyString> m_components;
};
struct AbstractOperationDeclaration {
FlyString name;
Vector<FunctionArgument> arguments;
};
struct AccessorDeclaration {
QualifiedName name;
};
struct MethodDeclaration {
QualifiedName name;
Vector<FunctionArgument> arguments;
};
using Declaration = Variant<AbstractOperationDeclaration, AccessorDeclaration, MethodDeclaration>;
class FunctionDeclaration : public RefCounted<FunctionDeclaration> {
public:
FunctionDeclaration(Declaration&& declaration, Location location);
virtual ~FunctionDeclaration() = default;
Declaration const& declaration() const { return m_declaration; }
Location location() const { return m_location; }
String name() const;
ReadonlySpan<FunctionArgument> arguments() const;
private:
Declaration m_declaration;
Location m_location;
};
class FunctionDefinition : public FunctionDeclaration {
public:
FunctionDefinition(Declaration&& declaration, Location location, Tree ast);
void reindex_ssa_variables();
Tree m_ast;
// Populates during reference resolving
// NOTE: The hash map here is ordered since we do not want random hash changes to break our test
// expectations (looking at you, SipHash).
OrderedHashMap<StringView, NamedVariableDeclarationRef> m_local_variables;
// Fields populate during CFG building
NamedVariableDeclarationRef m_named_return_value;
RefPtr<ControlFlowGraph> m_cfg;
// Fields populate during SSA building
Vector<SSAVariableDeclarationRef> m_ssa_arguments;
SSAVariableDeclarationRef m_return_value;
Vector<SSAVariableDeclarationRef> m_local_ssa_variables;
};
}

View file

@ -1,53 +0,0 @@
/*
* Copyright (c) 2023-2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Parser/Lexer.h"
#include "Parser/SpecificationParsing.h"
#include "Parser/XMLUtils.h"
namespace JSSpecCompiler {
Optional<Algorithm> Algorithm::create(SpecificationParsingContext& ctx, XML::Node const* element)
{
VERIFY(element->as_element().name == tag_emu_alg);
Vector<XML::Node const*> steps_list;
for (auto const& child : element->as_element().children) {
child->content.visit(
[&](XML::Node::Element const& element) {
if (element.name == tag_ol) {
steps_list.append(child);
return;
}
ctx.diag().error(ctx.location_from_xml_offset(child->offset),
"<{}> should not be a child of <emu-alg>"sv, element.name);
},
[&](XML::Node::Text const&) {
if (!contains_empty_text(child)) {
ctx.diag().error(ctx.location_from_xml_offset(child->offset),
"non-empty text node should not be a child of <emu-alg>");
}
},
[&](auto const&) {});
}
if (steps_list.size() != 1) {
ctx.diag().error(ctx.location_from_xml_offset(element->offset),
"<emu-alg> should have exactly one <ol> child"sv);
return {};
}
auto steps_creation_result = AlgorithmStepList::create(ctx, steps_list[0]);
if (steps_creation_result.has_value()) {
Algorithm algorithm;
algorithm.m_tree = steps_creation_result.release_value().tree();
return algorithm;
}
return {};
}
}

View file

@ -1,60 +0,0 @@
/*
* Copyright (c) 2023-2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Parser/Lexer.h"
#include "Parser/SpecificationParsing.h"
namespace JSSpecCompiler {
Optional<AlgorithmStep> AlgorithmStep::create(SpecificationParsingContext& ctx, XML::Node const* element)
{
VERIFY(element->as_element().name == tag_li);
auto [maybe_tokens, substeps] = tokenize_step(ctx, element);
AlgorithmStep result(ctx);
result.m_node = element;
if (substeps) {
// FIXME: Remove this once macOS Lagom CI updates to Clang >= 16.
auto substeps_copy = substeps;
auto step_list = ctx.with_new_step_list_nesting_level([&] {
return AlgorithmStepList::create(ctx, substeps_copy);
});
result.m_substeps = step_list.has_value() ? step_list->tree() : error_tree;
}
if (!maybe_tokens.has_value())
return {};
result.m_tokens = maybe_tokens.release_value();
if (!result.parse())
return {};
return result;
}
bool AlgorithmStep::parse()
{
TextParser parser(m_ctx, m_tokens, m_node);
TextParseErrorOr<NullableTree> parse_result = TextParseError {};
if (m_substeps)
parse_result = parser.parse_step_with_substeps(RefPtr(m_substeps).release_nonnull());
else
parse_result = parser.parse_step_without_substeps();
if (parse_result.is_error()) {
auto [location, message] = parser.get_diagnostic();
m_ctx.diag().error(location, "{}", message);
return false;
} else {
m_expression = parse_result.release_value();
return true;
}
}
}

View file

@ -1,87 +0,0 @@
/*
* Copyright (c) 2023-2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Parser/Lexer.h"
#include "Parser/SpecificationParsing.h"
#include "Parser/XMLUtils.h"
namespace JSSpecCompiler {
Optional<AlgorithmStepList> AlgorithmStepList::create(SpecificationParsingContext& ctx, XML::Node const* element)
{
VERIFY(element->as_element().name == tag_ol);
AlgorithmStepList result;
Vector<Tree> step_expressions;
bool all_steps_parsed = true;
int step_number = 0;
auto const& parent_scope = ctx.current_logical_scope();
for (auto const& child : element->as_element().children) {
child->content.visit(
[&](XML::Node::Element const& element) {
if (element.name == tag_li) {
auto step_creation_result = ctx.with_new_logical_scope([&] {
update_logical_scope_for_step(ctx, parent_scope, step_number);
return AlgorithmStep::create(ctx, child);
});
if (!step_creation_result.has_value()) {
all_steps_parsed = false;
} else {
if (auto expression = step_creation_result.release_value().tree())
step_expressions.append(expression.release_nonnull());
}
++step_number;
return;
}
ctx.diag().error(ctx.location_from_xml_offset(child->offset),
"<{}> should not be a child of algorithm step list"sv, element.name);
},
[&](XML::Node::Text const&) {
if (!contains_empty_text(child)) {
ctx.diag().error(ctx.location_from_xml_offset(child->offset),
"non-empty text node should not be a child of algorithm step list");
}
},
[&](auto const&) {});
}
if (!all_steps_parsed)
return {};
result.m_expression = make_ref_counted<TreeList>(move(step_expressions));
return result;
}
void AlgorithmStepList::update_logical_scope_for_step(SpecificationParsingContext& ctx, LogicalLocation const& parent_scope, int step_number)
{
int nesting_level = ctx.step_list_nesting_level();
String list_step_number;
if (nesting_level == 0 || nesting_level == 3) {
list_step_number = MUST(String::formatted("{}", step_number + 1));
} else if (nesting_level == 1 || nesting_level == 4) {
if (step_number < 26)
list_step_number = String::from_code_point('a' + step_number);
else
list_step_number = MUST(String::formatted("{}", step_number + 1));
} else {
list_step_number = MUST(String::from_byte_string(ByteString::roman_number_from(step_number + 1).to_lowercase()));
}
auto& scope = ctx.current_logical_scope();
scope.section = parent_scope.section;
if (parent_scope.step.is_empty())
scope.step = list_step_number;
else
scope.step = MUST(String::formatted("{}.{}", parent_scope.step, list_step_number));
}
}

View file

@ -1,263 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/File.h>
#include "Function.h"
#include "Parser/CppASTConverter.h"
#include "Parser/SpecificationParsing.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));
Vector<FunctionArgument> arguments;
for (auto const& parameter : m_function->parameters())
arguments.append({ .name = parameter->full_name() });
return make_ref_counted<FunctionDefinition>(
AbstractOperationDeclaration {
.name = MUST(FlyString::from_utf8(name)),
.arguments = move(arguments),
},
Location {},
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.
VERIFY(literal.value().to_number<i64>().has_value());
return make_ref_counted<MathematicalConstant>(MUST(Crypto::BigFraction::from_string(literal.value())));
}
template<>
NullableTree CppASTConverter::convert_node(Cpp::StringLiteral const& literal)
{
return make_ref_counted<StringLiteral>(literal.value());
}
template<>
NullableTree CppASTConverter::convert_node(Cpp::BinaryExpression const& expression)
{
static constexpr auto operator_translation = []() consteval {
Array<BinaryOperator, to_underlying(Cpp::BinaryOp::Arrow) + 1> table;
#define ASSIGN_TRANSLATION(cpp_name, our_name) \
table[to_underlying(Cpp::BinaryOp::cpp_name)] = BinaryOperator::our_name
ASSIGN_TRANSLATION(Addition, Plus);
ASSIGN_TRANSLATION(Subtraction, Minus);
ASSIGN_TRANSLATION(Multiplication, Multiplication);
ASSIGN_TRANSLATION(Division, Division);
ASSIGN_TRANSLATION(Modulo, Invalid);
ASSIGN_TRANSLATION(GreaterThan, CompareGreater);
ASSIGN_TRANSLATION(GreaterThanEquals, Invalid);
ASSIGN_TRANSLATION(LessThan, CompareLess);
ASSIGN_TRANSLATION(LessThanEquals, Invalid);
ASSIGN_TRANSLATION(BitwiseAnd, Invalid);
ASSIGN_TRANSLATION(BitwiseOr, Invalid);
ASSIGN_TRANSLATION(BitwiseXor, Invalid);
ASSIGN_TRANSLATION(LeftShift, Invalid);
ASSIGN_TRANSLATION(RightShift, Invalid);
ASSIGN_TRANSLATION(EqualsEquals, CompareEqual);
ASSIGN_TRANSLATION(NotEqual, CompareNotEqual);
ASSIGN_TRANSLATION(LogicalOr, Invalid);
ASSIGN_TRANSLATION(LogicalAnd, Invalid);
ASSIGN_TRANSLATION(Arrow, Invalid);
#undef ASSIGN_TRANSLATION
return table;
}();
auto translated_operator = operator_translation[to_underlying(expression.op())];
// TODO: Print nicer error.
VERIFY(translated_operator != BinaryOperator::Invalid);
return make_ref_counted<BinaryOperation>(translated_operator, as_tree(expression.lhs()), as_tree(expression.rhs()));
}
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,
Cpp::StringLiteral,
Cpp::BinaryExpression>();
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> {});
}
CppParsingStep::CppParsingStep()
: CompilationStep("parser"sv)
{
}
CppParsingStep::~CppParsingStep() = default;
void CppParsingStep::run(TranslationUnitRef 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();
Cpp::Preprocessor preprocessor { filename, m_input };
m_parser = adopt_own_if_nonnull(new Cpp::Parser { preprocessor.process_and_lex(), filename });
auto cpp_translation_unit = m_parser->parse();
VERIFY(m_parser->errors().is_empty());
for (auto const& declaration : cpp_translation_unit->declarations()) {
if (declaration->is_function()) {
auto const* cpp_function = AK::verify_cast<Cpp::FunctionDeclaration>(declaration.ptr());
translation_unit->adopt_function(CppASTConverter(cpp_function).convert());
}
}
}
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/OwnPtr.h>
#include <LibCpp/AST.h>
#include <LibCpp/Parser.h>
#include "CompilationPipeline.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;
};
class CppParsingStep : public CompilationStep {
public:
CppParsingStep();
~CppParsingStep();
void run(TranslationUnitRef translation_unit) override;
private:
OwnPtr<Cpp::Parser> m_parser;
ByteBuffer m_input;
};
}

View file

@ -1,256 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/NonnullOwnPtr.h>
#include <LibXML/Parser/Parser.h>
#include "Parser/Lexer.h"
#include "Parser/SpecificationParsing.h"
#include "Parser/XMLUtils.h"
namespace JSSpecCompiler {
namespace {
Optional<Token> consume_number(LineTrackingLexer& lexer, Location& location)
{
u64 start = lexer.tell();
if (lexer.next_is('-'))
lexer.consume(1);
if (!lexer.next_is(is_ascii_digit)) {
lexer.retreat(lexer.tell() - start);
return {};
}
lexer.consume_while(is_ascii_digit);
if (lexer.next_is('.')) {
lexer.consume(1);
if (lexer.consume_while(is_ascii_digit).length() == 0)
lexer.retreat(1);
}
auto length = lexer.tell() - start;
lexer.retreat(length);
return { Token { TokenType::Number, lexer.consume(length), move(location) } };
}
bool can_end_word_token(char c)
{
return is_ascii_space(c) || ".,"sv.contains(c);
}
void tokenize_string(SpecificationParsingContext& ctx, XML::Node const* node, StringView view, Vector<Token>& tokens)
{
static constexpr struct {
StringView text_to_match;
TokenType token_type;
} choices[] = {
{ "-"sv, TokenType::AmbiguousMinus },
{ "}"sv, TokenType::BraceClose },
{ "{"sv, TokenType::BraceOpen },
{ ":"sv, TokenType::Colon },
{ ","sv, TokenType::Comma },
{ "/"sv, TokenType::Division },
{ ". "sv, TokenType::Dot },
{ ".\n"sv, TokenType::Dot },
{ "="sv, TokenType::Equals },
{ "is equal to"sv, TokenType::Equals },
{ "!"sv, TokenType::ExclamationMark },
{ ">"sv, TokenType::Greater },
{ "is"sv, TokenType::Is },
{ "<"sv, TokenType::Less },
{ "»"sv, TokenType::ListEnd },
{ "«"sv, TokenType::ListStart },
{ "."sv, TokenType::MemberAccess },
{ "×"sv, TokenType::Multiplication },
{ "is not equal to"sv, TokenType::NotEquals },
{ ""sv, TokenType::NotEquals },
{ ")"sv, TokenType::ParenClose },
{ "("sv, TokenType::ParenOpen },
{ "+"sv, TokenType::Plus },
{ "?"sv, TokenType::QuestionMark },
{ "]"sv, TokenType::SquareBracketClose },
{ "["sv, TokenType::SquareBracketOpen },
{ "NewTarget"sv, TokenType::WellKnownValue },
};
LineTrackingLexer lexer(view, node->offset);
while (!lexer.is_eof()) {
lexer.ignore_while(is_ascii_space);
// FIXME: This is incorrect since we count text offset after XML reference resolution. To do
// this properly, we need support from XML::Parser.
Location token_location = ctx.location_from_xml_offset(lexer.position_for(lexer.tell()));
if (auto result = consume_number(lexer, token_location); result.has_value()) {
tokens.append(result.release_value());
continue;
}
bool matched = false;
for (auto const& [text_to_match, token_type] : choices) {
if (lexer.consume_specific(text_to_match)) {
tokens.append({ token_type, text_to_match, move(token_location) });
matched = true;
break;
}
}
if (matched)
continue;
StringView word = lexer.consume_until(can_end_word_token);
if (word.length())
tokens.append({ TokenType::Word, word, move(token_location) });
}
}
enum class TreeType {
AlgorithmStep,
NestedExpression,
Header,
};
struct TokenizerState {
Vector<Token> tokens;
XML::Node const* substeps = nullptr;
bool has_errors = false;
};
void tokenize_tree(SpecificationParsingContext& ctx, TokenizerState& state, XML::Node const* node, TreeType tree_type)
{
// FIXME: Use structured binding once macOS Lagom CI updates to Clang >= 16.
auto& tokens = state.tokens;
auto& substeps = state.substeps;
auto& has_errors = state.has_errors;
for (auto const& child : node->as_element().children) {
if (has_errors)
break;
child->content.visit(
[&](XML::Node::Element const& element) -> void {
Location child_location = ctx.location_from_xml_offset(child->offset);
auto report_error = [&]<typename... Parameters>(AK::CheckedFormatString<Parameters...>&& fmt, Parameters const&... parameters) {
ctx.diag().error(child_location, move(fmt), parameters...);
has_errors = true;
};
if (substeps) {
report_error("substeps list must be the last child of algorithm step");
return;
}
if (element.name == tag_var) {
auto variable_name = get_text_contents(child);
if (!variable_name.has_value())
report_error("malformed <var> subtree, expected single text child node");
tokens.append({ TokenType::Identifier, variable_name.value_or(""sv), move(child_location) });
return;
}
if (element.name == tag_emu_val) {
auto maybe_contents = get_text_contents(child);
if (!maybe_contents.has_value())
report_error("malformed <emu-val> subtree, expected single text child node");
auto contents = maybe_contents.value_or(""sv);
if (contents.length() >= 2 && contents.starts_with('"') && contents.ends_with('"'))
tokens.append({ TokenType::String, contents.substring_view(1, contents.length() - 2), move(child_location) });
else if (contents.is_one_of("undefined", "null", "this", "true", "false"))
tokens.append({ TokenType::WellKnownValue, contents, move(child_location) });
else
tokens.append({ TokenType::Identifier, contents, move(child_location) });
return;
}
if (element.name == tag_emu_xref) {
auto identifier = get_single_child_with_tag(child, "a"sv).map([](XML::Node const* node) {
return get_text_contents(node).value_or(""sv);
});
if (!identifier.has_value() || identifier.value().is_empty())
report_error("malformed <emu-xref> subtree, expected <a> with nested single text node");
tokens.append({ TokenType::Identifier, identifier.value_or(""sv), move(child_location) });
return;
}
if (element.name == tag_sup) {
tokens.append({ TokenType::Superscript, ""sv, move(child_location) });
tokens.append({ TokenType::ParenOpen, ""sv, move(child_location) });
tokenize_tree(ctx, state, child, TreeType::NestedExpression);
tokens.append({ TokenType::ParenClose, ""sv, move(child_location) });
return;
}
if (element.name == tag_emu_const) {
auto maybe_contents = get_text_contents(child);
if (!maybe_contents.has_value())
report_error("malformed <emu-const> subtree, expected single text child node");
tokens.append({ TokenType::Enumerator, maybe_contents.value_or(""sv), move(child_location) });
return;
}
if (tree_type == TreeType::Header && element.name == tag_span) {
auto element_class = get_attribute_by_name(child, attribute_class);
if (element_class != class_secnum)
report_error("expected <span> to have class='secnum' attribute");
auto section_number = get_text_contents(child);
if (!section_number.has_value())
report_error("malformed section number span subtree, expected single text child node");
tokens.append({ TokenType::SectionNumber, section_number.value_or(""sv), move(child_location) });
return;
}
if (tree_type == TreeType::AlgorithmStep && element.name == tag_ol) {
substeps = child;
return;
}
report_error("<{}> should not be a child of algorithm step", element.name);
},
[&](XML::Node::Text const& text) {
auto view = text.builder.string_view();
if (substeps != nullptr && !contains_empty_text(child)) {
ctx.diag().error(ctx.location_from_xml_offset(child->offset),
"substeps list must be the last child of algorithm step");
} else {
tokenize_string(ctx, child, view, tokens);
}
},
[&](auto const&) {});
}
if (tree_type == TreeType::AlgorithmStep && tokens.size() && tokens.last().type == TokenType::MemberAccess)
tokens.last().type = TokenType::Dot;
}
}
StepTokenizationResult tokenize_step(SpecificationParsingContext& ctx, XML::Node const* node)
{
TokenizerState state;
tokenize_tree(ctx, state, node, TreeType::AlgorithmStep);
return {
.tokens = state.has_errors ? OptionalNone {} : Optional<Vector<Token>> { move(state.tokens) },
.substeps = state.substeps,
};
}
Optional<Vector<Token>> tokenize_header(SpecificationParsingContext& ctx, XML::Node const* node)
{
TokenizerState state;
tokenize_tree(ctx, state, node, TreeType::Header);
return state.has_errors ? OptionalNone {} : Optional<Vector<Token>> { state.tokens };
}
}

View file

@ -1,43 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Parser/Token.h"
namespace JSSpecCompiler {
inline constexpr StringView tag_emu_alg = "emu-alg"sv;
inline constexpr StringView tag_emu_clause = "emu-clause"sv;
inline constexpr StringView tag_emu_const = "emu-const"sv;
inline constexpr StringView tag_emu_import = "emu-import"sv;
inline constexpr StringView tag_emu_intro = "emu-intro"sv;
inline constexpr StringView tag_emu_val = "emu-val"sv;
inline constexpr StringView tag_emu_xref = "emu-xref"sv;
inline constexpr StringView tag_h1 = "h1"sv;
inline constexpr StringView tag_li = "li"sv;
inline constexpr StringView tag_ol = "ol"sv;
inline constexpr StringView tag_p = "p"sv;
inline constexpr StringView tag_span = "span"sv;
inline constexpr StringView tag_specification = "specification"sv;
inline constexpr StringView tag_sup = "sup"sv;
inline constexpr StringView tag_var = "var"sv;
inline constexpr StringView attribute_aoid = "aoid"sv;
inline constexpr StringView attribute_class = "class"sv;
inline constexpr StringView attribute_id = "id"sv;
inline constexpr StringView class_secnum = "secnum"sv;
struct StepTokenizationResult {
Optional<Vector<Token>> tokens;
XML::Node const* substeps = nullptr;
};
StepTokenizationResult tokenize_step(SpecificationParsingContext& ctx, XML::Node const* node);
Optional<Vector<Token>> tokenize_header(SpecificationParsingContext& ctx, XML::Node const* node);
}

View file

@ -1,54 +0,0 @@
/*
* Copyright (c) 2023-2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Parser/Lexer.h"
#include "Parser/SpecificationParsing.h"
#include "Parser/XMLUtils.h"
namespace JSSpecCompiler {
NonnullOwnPtr<Specification> Specification::create(SpecificationParsingContext& ctx, XML::Node const* element)
{
VERIFY(element->as_element().name == tag_specification);
auto specification = make<Specification>();
specification->parse(ctx, element);
return specification;
}
void Specification::collect_into(TranslationUnitRef translation_unit)
{
for (auto& clause : m_clauses)
clause->collect_into(translation_unit);
}
void Specification::parse(SpecificationParsingContext& ctx, XML::Node const* element)
{
for (auto const& child : element->as_element().children) {
child->content.visit(
[&](XML::Node::Element const& element) {
if (element.name == tag_emu_intro) {
// Introductory comments are ignored.
} else if (element.name == tag_emu_clause) {
m_clauses.append(SpecificationClause::create(ctx, child));
} else if (element.name == tag_emu_import) {
parse(ctx, child);
} else {
ctx.diag().error(ctx.location_from_xml_offset(child->offset),
"<{}> should not be a child of <specification>", element.name);
}
},
[&](XML::Node::Text const&) {
if (!contains_empty_text(child)) {
ctx.diag().error(ctx.location_from_xml_offset(child->offset),
"non-empty text node should not be a child of <specification>");
}
},
[&](auto) {});
}
}
}

View file

@ -1,127 +0,0 @@
/*
* Copyright (c) 2023-2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Parser/Lexer.h"
#include "Parser/SpecificationParsing.h"
#include "Parser/XMLUtils.h"
namespace JSSpecCompiler {
NonnullOwnPtr<SpecificationClause> SpecificationClause::create(SpecificationParsingContext& ctx, XML::Node const* element)
{
return ctx.with_new_logical_scope([&] {
VERIFY(element->as_element().name == tag_emu_clause);
SpecificationClause specification_clause(ctx);
specification_clause.parse(element);
OwnPtr<SpecificationClause> result;
specification_clause.m_header.header.visit(
[&](AK::Empty const&) {
result = make<SpecificationClause>(move(specification_clause));
},
[&](OneOf<AbstractOperationDeclaration, AccessorDeclaration, MethodDeclaration> auto const&) {
result = make<SpecificationFunction>(move(specification_clause));
},
[&](ClauseHeader::PropertiesList const&) {
result = make<ObjectProperties>(move(specification_clause));
});
if (!result->post_initialize(element))
result = make<SpecificationClause>(move(*result));
return result.release_nonnull();
});
}
void SpecificationClause::collect_into(TranslationUnitRef translation_unit)
{
do_collect(translation_unit);
for (auto& subclause : m_subclauses)
subclause->collect_into(translation_unit);
}
Optional<FailedTextParseDiagnostic> SpecificationClause::parse_header(XML::Node const* element)
{
auto& ctx = *m_ctx_pointer;
VERIFY(element->as_element().name == tag_h1);
auto maybe_tokens = tokenize_header(ctx, element);
if (!maybe_tokens.has_value())
return {};
auto const& tokens = maybe_tokens.release_value();
TextParser parser(ctx, tokens, element);
auto parse_result = parser.parse_clause_header(m_clause_has_aoid_attribute);
if (parse_result.is_error()) {
// Still try to at least scavenge section number.
if (tokens.size() && tokens[0].type == TokenType::SectionNumber)
ctx.current_logical_scope().section = MUST(String::from_utf8(tokens[0].data));
return parser.get_diagnostic();
}
m_header = parse_result.release_value();
ctx.current_logical_scope().section = MUST(String::from_utf8(m_header.section_number));
return {};
}
void SpecificationClause::parse(XML::Node const* element)
{
auto& ctx = context();
u32 child_index = 0;
bool node_ignored_warning_issued = false;
Optional<FailedTextParseDiagnostic> header_parse_error;
m_clause_has_aoid_attribute = element->as_element().attributes.get("aoid").has_value()
? TextParser::ClauseHasAoidAttribute::Yes
: TextParser::ClauseHasAoidAttribute::No;
for (auto const& child : element->as_element().children) {
child->content.visit(
[&](XML::Node::Element const& element) {
if (child_index == 0) {
if (element.name != tag_h1) {
ctx.diag().error(ctx.location_from_xml_offset(child->offset),
"<h1> must be the first child of <emu-clause>");
return;
}
header_parse_error = parse_header(child);
} else {
if (element.name == tag_h1) {
ctx.diag().error(ctx.location_from_xml_offset(child->offset),
"<h1> can only be the first child of <emu-clause>");
return;
}
if (element.name == tag_emu_clause) {
m_subclauses.append(create(ctx, child));
return;
}
if (!node_ignored_warning_issued && m_header.header.has<AK::Empty>()) {
node_ignored_warning_issued = true;
ctx.diag().warn(ctx.location_from_xml_offset(child->offset),
"node content will be ignored since section header was not parsed successfully");
if (header_parse_error.has_value())
ctx.diag().note(header_parse_error->location, "{}", header_parse_error->message);
}
}
++child_index;
},
[&](XML::Node::Text const&) {
if (!contains_empty_text(child)) {
ctx.diag().error(ctx.location_from_xml_offset(child->offset),
"non-empty text node should not be a child of <emu-clause>");
}
},
[&](auto) {});
}
}
}

View file

@ -1,86 +0,0 @@
/*
* Copyright (c) 2023-2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Parser/Lexer.h"
#include "Parser/SpecificationParsing.h"
#include "Parser/XMLUtils.h"
namespace JSSpecCompiler {
bool SpecificationFunction::post_initialize(XML::Node const* element)
{
VERIFY(element->as_element().name == tag_emu_clause);
auto& ctx = context();
m_location = ctx.location_from_xml_offset(element->offset);
auto maybe_id = get_attribute_by_name(element, attribute_id);
if (!maybe_id.has_value()) {
ctx.diag().error(m_location,
"no id attribute");
} else {
m_id = maybe_id.value();
}
m_header.header.visit(
[&](AbstractOperationDeclaration const& abstract_operation) {
m_declaration = abstract_operation;
auto abstract_operation_id = get_attribute_by_name(element, attribute_aoid).value();
if (abstract_operation.name != abstract_operation_id) {
ctx.diag().warn(m_location,
"function name in header and <emu-clause>[aoid] do not match");
}
},
[&](OneOf<AccessorDeclaration, MethodDeclaration> auto const& declaration) {
m_declaration = declaration;
},
[&](auto const&) {
VERIFY_NOT_REACHED();
});
Vector<XML::Node const*> algorithm_nodes;
for (auto const& child : element->as_element().children) {
child->content.visit(
[&](XML::Node::Element const& element) {
if (element.name == tag_h1) {
// Processed in SpecificationClause
} else if (element.name == tag_p) {
ctx.diag().warn(ctx.location_from_xml_offset(child->offset),
"prose is ignored");
} else if (element.name == tag_emu_alg) {
algorithm_nodes.append(child);
} else {
ctx.diag().error(ctx.location_from_xml_offset(child->offset),
"<{}> should not be a child of <emu-clause> specifing function"sv, element.name);
}
},
[&](auto const&) {});
}
if (algorithm_nodes.size() != 1) {
ctx.diag().error(m_location,
"<emu-clause> specifing function should have exactly one <emu-alg> child"sv);
return false;
}
auto maybe_algorithm = Algorithm::create(ctx, algorithm_nodes[0]);
if (maybe_algorithm.has_value()) {
m_algorithm = maybe_algorithm.release_value();
return true;
} else {
return false;
}
}
void SpecificationFunction::do_collect(TranslationUnitRef translation_unit)
{
translation_unit->adopt_function(make_ref_counted<FunctionDefinition>(m_declaration.release_value(), m_location, m_algorithm.tree()));
}
}

View file

@ -1,187 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/OwnPtr.h>
#include <AK/TemporaryChange.h>
#include "AST/AST.h"
#include "CompilationPipeline.h"
#include "Forward.h"
#include "Parser/TextParser.h"
#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)
{
}
TranslationUnitRef translation_unit();
DiagnosticEngine& diag();
template<typename Func>
auto with_new_logical_scope(Func&& func)
{
TemporaryChange<RefPtr<LogicalLocation>> change(m_current_logical_scope, make_ref_counted<LogicalLocation>());
return func();
}
LogicalLocation& current_logical_scope();
template<typename Func>
auto with_new_step_list_nesting_level(Func&& func)
{
TemporaryChange change(m_step_list_nesting_level, m_step_list_nesting_level + 1);
return func();
}
int step_list_nesting_level() const;
Location file_scope() const;
Location location_from_xml_offset(LineTrackingLexer::Position position) const;
private:
TranslationUnitRef m_translation_unit;
RefPtr<LogicalLocation> m_current_logical_scope;
int m_step_list_nesting_level = 0;
};
class AlgorithmStepList {
public:
static Optional<AlgorithmStepList> create(SpecificationParsingContext& ctx, XML::Node const* element);
Tree tree() const { return m_expression; }
private:
static void update_logical_scope_for_step(SpecificationParsingContext& ctx, LogicalLocation const& parent_scope, int step_number);
Tree m_expression = error_tree;
};
class AlgorithmStep {
public:
static Optional<AlgorithmStep> create(SpecificationParsingContext& ctx, XML::Node const* node);
NullableTree tree() const { return m_expression; }
private:
AlgorithmStep(SpecificationParsingContext& ctx)
: m_ctx(ctx)
{
}
bool parse();
SpecificationParsingContext& m_ctx;
Vector<Token> m_tokens;
XML::Node const* m_node;
NullableTree m_expression = error_tree;
NullableTree m_substeps;
};
class Algorithm {
public:
static Optional<Algorithm> create(SpecificationParsingContext& ctx, XML::Node const* element);
Tree tree() const { return m_tree; }
private:
Tree m_tree = error_tree;
};
class SpecificationClause {
AK_MAKE_DEFAULT_MOVABLE(SpecificationClause);
public:
static NonnullOwnPtr<SpecificationClause> create(SpecificationParsingContext& ctx, XML::Node const* element);
virtual ~SpecificationClause() = default;
void collect_into(TranslationUnitRef translation_unit);
protected:
virtual bool post_initialize(XML::Node const* /*element*/) { return true; }
virtual void do_collect(TranslationUnitRef /*translation_unit*/) { }
SpecificationParsingContext& context() { return *m_ctx_pointer; }
ClauseHeader m_header;
private:
SpecificationClause(SpecificationParsingContext& ctx)
: m_ctx_pointer(&ctx)
{
}
Optional<FailedTextParseDiagnostic> parse_header(XML::Node const* element);
void parse(XML::Node const* element);
TextParser::ClauseHasAoidAttribute m_clause_has_aoid_attribute;
SpecificationParsingContext* m_ctx_pointer;
Vector<NonnullOwnPtr<SpecificationClause>> m_subclauses;
};
class SpecificationFunction : public SpecificationClause {
public:
SpecificationFunction(SpecificationClause&& clause)
: SpecificationClause(move(clause))
{
}
protected:
bool post_initialize(XML::Node const* element) override;
void do_collect(TranslationUnitRef translation_unit) override;
private:
StringView m_id;
Optional<Declaration> m_declaration;
Location m_location;
Algorithm m_algorithm;
};
class ObjectProperties : public SpecificationClause {
public:
ObjectProperties(SpecificationClause&& clause)
: SpecificationClause(move(clause))
{
}
};
class Specification {
public:
static NonnullOwnPtr<Specification> create(SpecificationParsingContext& ctx, XML::Node const* element);
void collect_into(TranslationUnitRef translation_unit);
private:
void parse(SpecificationParsingContext& ctx, XML::Node const* element);
Vector<NonnullOwnPtr<SpecificationClause>> m_clauses;
};
class SpecificationParsingStep : public CompilationStep {
public:
SpecificationParsingStep();
~SpecificationParsingStep();
void run(TranslationUnitRef translation_unit) override;
private:
OwnPtr<XML::Document> m_document;
OwnPtr<Specification> m_specification;
ByteBuffer m_input;
};
}

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2023-2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Parser/SpecificationParsing.h"
namespace JSSpecCompiler {
TranslationUnitRef SpecificationParsingContext::translation_unit()
{
return m_translation_unit;
}
DiagnosticEngine& SpecificationParsingContext::diag()
{
return m_translation_unit->diag();
}
LogicalLocation& SpecificationParsingContext::current_logical_scope()
{
return *m_current_logical_scope;
}
int SpecificationParsingContext::step_list_nesting_level() const
{
return m_step_list_nesting_level;
}
Location SpecificationParsingContext::file_scope() const
{
return { .filename = m_translation_unit->filename() };
}
Location SpecificationParsingContext::location_from_xml_offset(LineTrackingLexer::Position position) const
{
return {
.filename = m_translation_unit->filename(),
.line = position.line,
.column = position.column,
.logical_location = m_current_logical_scope,
};
}
}

View file

@ -1,69 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/NonnullOwnPtr.h>
#include <LibCore/File.h>
#include <LibXML/Parser/Parser.h>
#include "Function.h"
#include "Parser/Lexer.h"
#include "Parser/SpecificationParsing.h"
#include "Parser/TextParser.h"
#include "Parser/XMLUtils.h"
namespace JSSpecCompiler {
SpecificationParsingStep::SpecificationParsingStep()
: CompilationStep("parser"sv)
{
}
SpecificationParsingStep::~SpecificationParsingStep() = default;
void SpecificationParsingStep::run(TranslationUnitRef translation_unit)
{
SpecificationParsingContext ctx(translation_unit);
auto filename = translation_unit->filename();
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_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<XML::Document>(document_or_error.release_value());
auto const& root = m_document->root();
if (!root.is_element() || root.as_element().name != tag_specification) {
ctx.diag().fatal_error(ctx.location_from_xml_offset(root.offset),
"document root must be <specification> tag");
return;
}
m_specification = Specification::create(ctx, &root);
m_specification->collect_into(translation_unit);
}
}

View file

@ -1,868 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ScopeGuard.h>
#include "Parser/SpecificationParsing.h"
#include "Parser/TextParser.h"
namespace JSSpecCompiler {
void TextParser::save_error(Variant<TokenType, StringView, CustomMessage>&& expected)
{
if (expected.has<TokenType>() && expected.get<TokenType>() == TokenType::Invalid)
return;
if (m_max_parsed_tokens > m_next_token_index)
return;
if (m_max_parsed_tokens < m_next_token_index)
m_suitable_continuations.clear();
m_max_parsed_tokens = m_next_token_index;
m_suitable_continuations.append(move(expected));
}
void TextParser::retreat()
{
--m_next_token_index;
}
auto TextParser::rollback_point()
{
return ArmedScopeGuard {
[this, index = this->m_next_token_index] {
m_next_token_index = index;
}
};
}
Optional<Token> TextParser::peek_token()
{
if (m_next_token_index == m_tokens.size())
return {};
return m_tokens[m_next_token_index];
}
Optional<Token> TextParser::consume_token()
{
auto result = peek_token();
if (result.has_value())
++m_next_token_index;
return result;
}
TextParseErrorOr<Token> TextParser::consume_token_with_one_of_types(std::initializer_list<TokenType> types)
{
auto token = peek_token();
if (token.has_value()) {
for (TokenType type : types) {
if (token->type == type) {
(void)consume_token();
return *token;
} else {
save_error(type);
}
}
} else {
for (TokenType type : types)
save_error(type);
}
return TextParseError {};
}
TextParseErrorOr<Token> TextParser::consume_token_with_type(TokenType type)
{
return consume_token_with_one_of_types({ type });
}
TextParseErrorOr<void> TextParser::consume_token(TokenType type, StringView data)
{
auto token = consume_token();
if (!token.has_value() || token->type != type || !token->data.equals_ignoring_ascii_case(data)) {
retreat();
save_error(data);
return TextParseError {};
}
return {};
}
TextParseErrorOr<void> TextParser::consume_word(StringView word)
{
auto token = consume_token();
if (!token.has_value() || token->type != TokenType::Word || !token->data.equals_ignoring_ascii_case(word)) {
retreat();
save_error(word);
return TextParseError {};
}
return {};
}
TextParseErrorOr<void> TextParser::consume_words(std::initializer_list<StringView> words)
{
for (auto word : words)
TRY(consume_word(word));
return {};
}
bool TextParser::is_eof() const
{
return m_next_token_index == m_tokens.size();
}
TextParseErrorOr<void> TextParser::expect_eof()
{
if (!is_eof()) {
save_error(CustomMessage { "EOF"sv });
return TextParseError {};
}
return {};
}
// <record_initialization> :== (the)? <record_name> { (<name>: <value>,)* }
TextParseErrorOr<Tree> TextParser::parse_record_direct_list_initialization()
{
auto rollback = rollback_point();
(void)consume_word("the"sv);
auto identifier = TRY(consume_token_with_type(TokenType::Identifier));
TRY(consume_token_with_type(TokenType::BraceOpen));
Vector<RecordDirectListInitialization::Argument> arguments;
while (true) {
auto name = TRY(consume_token_with_one_of_types({ TokenType::Identifier, TokenType::BraceClose }));
if (name.is_bracket()) {
break;
} else {
TRY(consume_token_with_type(TokenType::Colon));
auto value = TRY(parse_expression());
(void)consume_token_with_type(TokenType::Comma);
arguments.append({ make_ref_counted<UnresolvedReference>(name.data), value });
}
}
rollback.disarm();
return make_ref_counted<RecordDirectListInitialization>(
make_ref_counted<UnresolvedReference>(identifier.data), move(arguments));
}
// <function_arguments> :== '(' (<expr> (, <expr>)* )? ')'
TextParseErrorOr<Vector<Tree>> TextParser::parse_function_arguments()
{
auto rollback = rollback_point();
TRY(consume_token_with_type(TokenType::ParenOpen));
if (!consume_token_with_type(TokenType::ParenClose).is_error()) {
rollback.disarm();
return Vector<Tree> {};
}
Vector<Tree> arguments;
while (true) {
arguments.append(TRY(parse_expression()));
auto token = TRY(consume_token_with_one_of_types({ TokenType::ParenClose, TokenType::Comma }));
if (token.type == TokenType::ParenClose)
break;
}
rollback.disarm();
return arguments;
}
// <list_initialization> :== « (<expr> (, <expr>)*)? »
TextParseErrorOr<Tree> TextParser::parse_list_initialization()
{
auto rollback = rollback_point();
TRY(consume_token_with_type(TokenType::ListStart));
if (!consume_token_with_type(TokenType::ListEnd).is_error()) {
rollback.disarm();
return make_ref_counted<List>(Vector<Tree> {});
}
Vector<Tree> elements;
while (true) {
elements.append(TRY(parse_expression()));
auto token = TRY(consume_token_with_one_of_types({ TokenType::ListEnd, TokenType::Comma }));
if (token.type == TokenType::ListEnd)
break;
}
rollback.disarm();
return make_ref_counted<List>(move(elements));
}
TextParseErrorOr<Tree> TextParser::parse_the_this_value()
{
auto rollback = rollback_point();
TRY(consume_word("the"sv));
TRY(consume_token(TokenType::WellKnownValue, "this"sv));
TRY(consume_word("value"sv));
rollback.disarm();
return make_ref_counted<WellKnownNode>(WellKnownNode::Type::This);
}
// <value> :== <identifier> | <well_known_value> | <enumerator> | <number> | <string> | <list_initialization> | <record_initialization>
TextParseErrorOr<Tree> TextParser::parse_value()
{
if (auto identifier = consume_token_with_type(TokenType::Identifier); !identifier.is_error())
return make_ref_counted<UnresolvedReference>(identifier.release_value().data);
if (auto well_known_value = consume_token_with_type(TokenType::WellKnownValue); !well_known_value.is_error()) {
static constexpr struct {
StringView name;
WellKnownNode::Type type;
} translations[] = {
{ "false"sv, WellKnownNode::Type::False },
{ "NewTarget"sv, WellKnownNode::Type::NewTarget },
{ "null"sv, WellKnownNode::Type::Null },
{ "this"sv, WellKnownNode::Type::This },
{ "true"sv, WellKnownNode::Type::True },
{ "undefined"sv, WellKnownNode::Type::Undefined },
};
for (auto [name, type] : translations)
if (well_known_value.value().data == name)
return make_ref_counted<WellKnownNode>(type);
VERIFY_NOT_REACHED();
}
if (auto enumerator = consume_token_with_type(TokenType::Enumerator); !enumerator.is_error())
return m_ctx.translation_unit()->get_node_for_enumerator_value(enumerator.value().data);
if (auto number = consume_token_with_type(TokenType::Number); !number.is_error())
return make_ref_counted<MathematicalConstant>(MUST(Crypto::BigFraction::from_string(number.value().data)));
if (auto string = consume_token_with_type(TokenType::String); !string.is_error())
return make_ref_counted<StringLiteral>(string.value().data);
if (auto list_initialization = parse_list_initialization(); !list_initialization.is_error())
return list_initialization.release_value();
if (auto record_initialization = parse_record_direct_list_initialization(); !record_initialization.is_error())
return record_initialization.release_value();
if (auto the_this_value = parse_the_this_value(); !the_this_value.is_error())
return the_this_value.release_value();
return TextParseError {};
}
// <expr>
TextParseErrorOr<Tree> TextParser::parse_expression()
{
auto rollback = rollback_point();
#define THROW_PARSE_ERROR_IF(expr) \
do { \
if (expr) { \
save_error(CustomMessage { "valid expression continuation (not valid because " #expr ")"##sv }); \
return TextParseError {}; \
} \
} while (false)
#define THROW_PARSE_ERROR THROW_PARSE_ERROR_IF(true)
Vector<Variant<Tree, Token>> stack;
auto merge_stack = [&](i32 precedence) {
if (!stack.last().has<Tree>())
return;
while (stack.size() >= 2) {
auto const& maybe_operator = stack[stack.size() - 2];
if (!maybe_operator.has<Token>())
break;
auto last_operator = maybe_operator.get<Token>();
auto right = stack.last().get<Tree>();
if (last_operator.is_unary_operator()) {
auto operation = make_ref_counted<UnaryOperation>(last_operator.as_unary_operator(), right);
stack.shrink(stack.size() - 2);
stack.empend(operation);
} else if (last_operator.is_binary_operator() && last_operator.precedence() < precedence) {
auto left = stack[stack.size() - 3].get<Tree>();
auto operation = make_ref_counted<BinaryOperation>(last_operator.as_binary_operator(), left, right);
stack.shrink(stack.size() - 3);
stack.empend(operation);
} else {
break;
}
}
};
auto merge_pre_merged = [&] {
if (stack.size() < 3)
return;
auto const& maybe_left = stack[stack.size() - 3];
auto const& maybe_operator = stack[stack.size() - 2];
auto const& maybe_right = stack.last();
if (!maybe_left.has<Tree>() || !maybe_operator.has<Token>() || !maybe_right.has<Tree>())
return;
auto last_operator = maybe_operator.get<Token>();
if (!last_operator.is_pre_merged_binary_operator())
return;
auto expression = make_ref_counted<BinaryOperation>(last_operator.as_binary_operator(), maybe_left.get<Tree>(), maybe_right.get<Tree>());
stack.shrink(stack.size() - 3);
stack.empend(expression);
};
i32 bracket_balance = 0;
while (true) {
auto token_or_error = peek_token();
if (!token_or_error.has_value())
break;
auto token = token_or_error.release_value();
bool is_consumed = false;
enum {
NoneType,
ExpressionType,
PreMergedBinaryOperatorType,
UnaryOperatorType,
BinaryOperatorType,
BracketType,
} last_element_type;
if (stack.is_empty())
last_element_type = NoneType;
else if (stack.last().has<Tree>())
last_element_type = ExpressionType;
else if (stack.last().get<Token>().is_pre_merged_binary_operator())
last_element_type = PreMergedBinaryOperatorType;
else if (stack.last().get<Token>().is_unary_operator())
last_element_type = UnaryOperatorType;
else if (stack.last().get<Token>().is_binary_operator())
last_element_type = BinaryOperatorType;
else if (stack.last().get<Token>().is_bracket())
last_element_type = BracketType;
else
VERIFY_NOT_REACHED();
if (token.is_ambiguous_operator()) {
if (token.type == TokenType::AmbiguousMinus)
token.type = last_element_type == ExpressionType ? TokenType::BinaryMinus : TokenType::UnaryMinus;
else
VERIFY_NOT_REACHED();
}
bracket_balance += token.is_opening_bracket();
bracket_balance -= token.is_closing_bracket();
if (bracket_balance < 0)
break;
if (token.type == TokenType::ParenOpen) {
if (last_element_type == ExpressionType) {
// This is a function call.
auto arguments = TRY(parse_function_arguments());
is_consumed = true;
stack.append(Tree { make_ref_counted<FunctionCall>(stack.take_last().get<Tree>(), move(arguments)) });
--bracket_balance;
} else {
// This is just an opening '(' in expression.
stack.append(token);
}
} else if (token.is_pre_merged_binary_operator()) {
THROW_PARSE_ERROR_IF(last_element_type != ExpressionType);
stack.append(token);
} else if (token.is_unary_operator()) {
THROW_PARSE_ERROR_IF(last_element_type == PreMergedBinaryOperatorType);
stack.append(token);
} else if (token.is_binary_operator() || token.is_closing_bracket()) {
if (bracket_balance == 0 && token.type == TokenType::Comma)
break;
THROW_PARSE_ERROR_IF(last_element_type != ExpressionType);
merge_stack(token.precedence());
if (token.is_closing_bracket()) {
THROW_PARSE_ERROR_IF(stack.size() == 1);
THROW_PARSE_ERROR_IF(!stack[stack.size() - 2].get<Token>().matches_with(token));
stack.remove(stack.size() - 2);
merge_pre_merged();
} else {
stack.append(token);
}
} else {
if (auto expression = parse_value(); !expression.is_error()) {
is_consumed = true;
THROW_PARSE_ERROR_IF(last_element_type == ExpressionType);
stack.append(expression.release_value());
merge_pre_merged();
} else {
break;
}
}
if (!is_consumed)
VERIFY(consume_token().has_value());
}
THROW_PARSE_ERROR_IF(stack.is_empty());
merge_stack(closing_bracket_precedence);
THROW_PARSE_ERROR_IF(stack.size() != 1 || !stack[0].has<Tree>());
rollback.disarm();
return stack[0].get<Tree>();
#undef THROW_PARSE_ERROR
#undef THROW_PARSE_ERROR_IF
}
// <condition> :== <expr> | (<expr> is <expr> (or <expr>)?)
TextParseErrorOr<Tree> TextParser::parse_condition()
{
auto rollback = rollback_point();
auto expression = TRY(parse_expression());
if (!consume_token_with_type(TokenType::Is).is_error()) {
Vector compare_values { TRY(parse_expression()) };
if (!consume_word("or"sv).is_error())
compare_values.append(TRY(parse_expression()));
rollback.disarm();
return make_ref_counted<IsOneOfOperation>(expression, move(compare_values));
}
rollback.disarm();
return expression;
}
// return <expr>
TextParseErrorOr<Tree> TextParser::parse_return_statement()
{
auto rollback = rollback_point();
TRY(consume_word("return"sv));
auto return_value = TRY(parse_expression());
rollback.disarm();
return make_ref_counted<ReturnNode>(return_value);
}
// assert: <condition>
TextParseErrorOr<Tree> TextParser::parse_assert()
{
auto rollback = rollback_point();
TRY(consume_token(TokenType::Identifier, "assert"sv));
TRY(consume_token_with_type(TokenType::Colon));
auto condition = TRY(parse_condition());
rollback.disarm();
return make_ref_counted<AssertExpression>(condition);
}
// (let <expr> be <expr>) | (set <expr> to <expr>)
TextParseErrorOr<Tree> TextParser::parse_assignment()
{
auto rollback = rollback_point();
bool is_let = !consume_word("let"sv).is_error();
if (!is_let)
TRY(consume_word("set"sv));
auto lvalue = TRY(parse_expression());
TRY(consume_word(is_let ? "be"sv : "to"sv));
auto rvalue = TRY(parse_expression());
rollback.disarm();
auto op = is_let ? BinaryOperator::Declaration : BinaryOperator::Assignment;
return make_ref_counted<BinaryOperation>(op, lvalue, rvalue);
}
// perform <expr>
TextParseErrorOr<Tree> TextParser::parse_perform()
{
auto rollback = rollback_point();
TRY(consume_word("perform"sv));
auto value = TRY(parse_expression());
rollback.disarm();
return value;
}
// <simple_step>
TextParseErrorOr<Tree> TextParser::parse_simple_step_or_inline_if_branch()
{
auto rollback = rollback_point();
// Return <expr>.$
if (auto result = parse_return_statement(); !result.is_error()) {
TRY(consume_token_with_type(TokenType::Dot));
TRY(expect_eof());
rollback.disarm();
return result.release_value();
}
// Assert: <expr>.$
if (auto result = parse_assert(); !result.is_error()) {
TRY(consume_token_with_type(TokenType::Dot));
TRY(expect_eof());
rollback.disarm();
return result.release_value();
}
// Let <expr> be <expr>.$
// Set <expr> to <expr>.$
if (auto result = parse_assignment(); !result.is_error()) {
TRY(consume_token_with_type(TokenType::Dot));
TRY(expect_eof());
rollback.disarm();
return result.release_value();
}
// Perform <expr>.$
if (auto result = parse_perform(); !result.is_error()) {
TRY(consume_token_with_type(TokenType::Dot));
TRY(expect_eof());
rollback.disarm();
return result.release_value();
}
return TextParseError {};
}
// <if_condition> :== (If <condition>) | (Else) | (Else if <condition>),
TextParseErrorOr<TextParser::IfConditionParseResult> TextParser::parse_if_beginning()
{
auto rollback = rollback_point();
bool is_if_branch = !consume_word("if"sv).is_error();
NullableTree condition = nullptr;
if (is_if_branch) {
condition = TRY(parse_condition());
} else {
TRY(consume_word("else"sv));
if (!consume_word("if"sv).is_error())
condition = TRY(parse_condition());
}
TRY(consume_token_with_type(TokenType::Comma));
rollback.disarm();
return IfConditionParseResult { is_if_branch, condition };
}
// <inline_if> :== <if_condition> <simple_step>.$
TextParseErrorOr<Tree> TextParser::parse_inline_if_else()
{
auto rollback = rollback_point();
auto [is_if_branch, condition] = TRY(parse_if_beginning());
auto then_branch = TRY(parse_simple_step_or_inline_if_branch());
rollback.disarm();
if (is_if_branch)
return make_ref_counted<IfBranch>(condition.release_nonnull(), then_branch);
return make_ref_counted<ElseIfBranch>(condition, then_branch);
}
// <if> :== <if_condition> then$ <substeps>
TextParseErrorOr<Tree> TextParser::parse_if(Tree then_branch)
{
auto rollback = rollback_point();
auto [is_if_branch, condition] = TRY(parse_if_beginning());
TRY(consume_word("then"sv));
TRY(expect_eof());
rollback.disarm();
if (is_if_branch)
return make_ref_counted<IfBranch>(*condition, then_branch);
else
return make_ref_counted<ElseIfBranch>(condition, then_branch);
}
// <else> :== Else,$ <substeps>
TextParseErrorOr<Tree> TextParser::parse_else(Tree else_branch)
{
auto rollback = rollback_point();
TRY(consume_word("else"sv));
TRY(consume_token_with_type(TokenType::Comma));
TRY(expect_eof());
rollback.disarm();
return make_ref_counted<ElseIfBranch>(nullptr, else_branch);
}
// <simple_step> | <inline_if>
TextParseErrorOr<NullableTree> TextParser::parse_step_without_substeps()
{
auto rollback = rollback_point();
// NOTE: ...
if (auto result = consume_word("NOTE:"sv); !result.is_error()) {
rollback.disarm();
return nullptr;
}
// <simple_step>
if (auto result = parse_simple_step_or_inline_if_branch(); !result.is_error()) {
rollback.disarm();
return result.release_value();
}
// <inline_if>
if (auto result = parse_inline_if_else(); !result.is_error()) {
rollback.disarm();
return result.release_value();
}
return TextParseError {};
}
// <if> | <else>
TextParseErrorOr<Tree> TextParser::parse_step_with_substeps(Tree substeps)
{
auto rollback = rollback_point();
// <if>
if (auto result = parse_if(substeps); !result.is_error()) {
rollback.disarm();
return result.release_value();
}
// <else>
if (auto result = parse_else(substeps); !result.is_error()) {
rollback.disarm();
return result.release_value();
}
return TextParseError {};
}
// <qualified_name> :== <word> (. <word>)*
TextParseErrorOr<QualifiedName> TextParser::parse_qualified_name()
{
Vector<StringView> qualified_name;
qualified_name.append(TRY(consume_token_with_type(TokenType::Word)).data);
while (true) {
auto token_or_error = consume_token_with_type(TokenType::MemberAccess);
if (token_or_error.is_error())
return QualifiedName { qualified_name };
qualified_name.append(TRY(consume_token_with_type(TokenType::Word)).data);
}
}
// <function_arguments> :== '(' (<word> (, <word>)*)? ')'
TextParseErrorOr<Vector<FunctionArgument>> TextParser::parse_function_arguments_in_declaration()
{
TRY(consume_token_with_type(TokenType::ParenOpen));
Vector<FunctionArgument> arguments;
size_t optional_arguments_group = 0;
while (true) {
Token token = TRY(consume_token_with_one_of_types({
TokenType::SquareBracketOpen,
arguments.is_empty() ? TokenType::Identifier : TokenType::Comma,
!optional_arguments_group ? TokenType::ParenClose : TokenType::Invalid,
optional_arguments_group ? TokenType::SquareBracketClose : TokenType::Invalid,
}));
StringView identifier;
if (token.type == TokenType::SquareBracketClose) {
VERIFY(optional_arguments_group != 0);
for (size_t i = 1; i < optional_arguments_group; ++i)
TRY(consume_token_with_type(TokenType::SquareBracketClose));
TRY(consume_token_with_type(TokenType::ParenClose));
break;
} else if (token.type == TokenType::ParenClose) {
VERIFY(optional_arguments_group == 0);
break;
} else if (token.type == TokenType::SquareBracketOpen) {
++optional_arguments_group;
if (!arguments.is_empty())
TRY(consume_token_with_type(TokenType::Comma));
identifier = TRY(consume_token_with_type(TokenType::Identifier)).data;
} else if (token.type == TokenType::Comma) {
identifier = TRY(consume_token_with_type(TokenType::Identifier)).data;
} else {
VERIFY(token.type == TokenType::Identifier);
identifier = token.data;
}
arguments.append({ identifier, optional_arguments_group });
}
return arguments;
}
// <ao_declaration> :== <word> <function_arguments> $
TextParseErrorOr<AbstractOperationDeclaration> TextParser::parse_abstract_operation_declaration()
{
auto rollback = rollback_point();
auto name = TRY(consume_token_with_type(TokenType::Word)).data;
AbstractOperationDeclaration function_definition;
function_definition.name = MUST(FlyString::from_utf8(name));
function_definition.arguments = TRY(parse_function_arguments_in_declaration());
TRY(expect_eof());
rollback.disarm();
return function_definition;
}
// <accessor_declaration> :== get <qualified_name> $
TextParseErrorOr<AccessorDeclaration> TextParser::parse_accessor_declaration()
{
auto rollback = rollback_point();
TRY(consume_word("get"sv));
AccessorDeclaration accessor;
accessor.name = TRY(parse_qualified_name());
TRY(expect_eof());
rollback.disarm();
return accessor;
}
// <properties_list_declaration> :== | Properties of the <qualified_name> Prototype Object $
// | Properties of the <qualified_name> Constructor $
// | Properties of <qualified_name> Instances $
// | The <qualified_name> Constructor $
TextParseErrorOr<ClauseHeader::PropertiesList> TextParser::parse_properties_list_declaration()
{
auto rollback = rollback_point();
ClauseHeader::PropertiesList properties_list;
if (!consume_word("The"sv).is_error()) {
properties_list.name = TRY(parse_qualified_name());
properties_list.object_type = ClauseHeader::ObjectType::Constructor;
TRY(consume_word("Constructor"sv));
} else {
TRY(consume_words({ "Properties"sv, "of"sv }));
bool has_the = !consume_word("the"sv).is_error();
properties_list.name = TRY(parse_qualified_name());
if (!has_the) {
TRY(consume_word("Instances"sv));
properties_list.object_type = ClauseHeader::ObjectType::Instance;
} else {
if (consume_word("Prototype"sv).is_error()) {
TRY(consume_word("Constructor"sv));
properties_list.object_type = ClauseHeader::ObjectType::Constructor;
} else {
TRY(consume_word("Object"sv));
properties_list.object_type = ClauseHeader::ObjectType::Prototype;
}
}
}
TRY(expect_eof());
rollback.disarm();
return properties_list;
}
TextParseErrorOr<MethodDeclaration> TextParser::parse_method_declaration()
{
auto rollback = rollback_point();
MethodDeclaration method;
method.name = TRY(parse_qualified_name());
method.arguments = TRY(parse_function_arguments_in_declaration());
TRY(expect_eof());
rollback.disarm();
return method;
}
// <clause_header> :== <section_number> <ao_declaration> | <accessor_declaration>
TextParseErrorOr<ClauseHeader> TextParser::parse_clause_header(ClauseHasAoidAttribute clause_has_aoid_attribute)
{
ClauseHeader result;
auto section_number_token = TRY(consume_token_with_type(TokenType::SectionNumber));
result.section_number = section_number_token.data;
if (clause_has_aoid_attribute == ClauseHasAoidAttribute::Yes) {
if (auto ao_declaration = parse_abstract_operation_declaration(); !ao_declaration.is_error()) {
result.header = ao_declaration.release_value();
return result;
}
} else {
if (auto accessor = parse_accessor_declaration(); !accessor.is_error()) {
result.header = accessor.release_value();
return result;
} else if (auto method = parse_method_declaration(); !method.is_error()) {
result.header = method.release_value();
return result;
} else if (auto properties_list = parse_properties_list_declaration(); !properties_list.is_error()) {
result.header = properties_list.release_value();
return result;
}
}
return TextParseError {};
}
FailedTextParseDiagnostic TextParser::get_diagnostic() const
{
StringBuilder message;
message.append("unexpected "sv);
if (m_max_parsed_tokens == m_tokens.size()) {
message.append("EOF"sv);
} else {
auto token = m_tokens[m_max_parsed_tokens];
if (token.type == TokenType::Word)
message.appendff("'{}'", token.data);
else if (token.type == TokenType::Identifier)
message.appendff("identifier '{}'", token.data);
else
message.append(token.name_for_diagnostic());
}
message.appendff(", expected ");
size_t size = m_suitable_continuations.size();
VERIFY(size > 0);
for (size_t i = 0; i < size; ++i) {
m_suitable_continuations[i].visit(
[&](TokenType type) { message.append(token_info[to_underlying(type)].name_for_diagnostic); },
[&](StringView word) { message.appendff("'{}'", word); },
[&](CustomMessage continuation) { message.append(continuation.message); });
if (i + 1 != size) {
if (size == 2)
message.append(" or "sv);
else if (i + 2 == size)
message.append(", or "sv);
else
message.append(", "sv);
}
}
Location location = Location::global_scope();
if (m_max_parsed_tokens < m_tokens.size()) {
location = m_tokens[m_max_parsed_tokens].location;
} else {
// FIXME: Would be nice to point to the closing tag not the opening one. This is also the
// only place where we use m_location.
location = m_ctx.location_from_xml_offset(m_node->offset);
}
return { location, MUST(message.to_string()) };
}
}

View file

@ -1,118 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "AST/AST.h"
#include "Function.h"
#include "Parser/Token.h"
namespace JSSpecCompiler {
struct ClauseHeader {
enum class ObjectType {
Constructor,
Prototype,
Instance,
};
struct PropertiesList {
QualifiedName name;
ObjectType object_type;
};
StringView section_number;
Variant<AK::Empty, AbstractOperationDeclaration, AccessorDeclaration, MethodDeclaration, PropertiesList> header;
};
struct TextParseError { };
struct FailedTextParseDiagnostic {
Location location;
String message;
};
template<typename T>
using TextParseErrorOr = ErrorOr<T, TextParseError>;
class TextParser {
public:
enum class ClauseHasAoidAttribute {
No,
Yes,
};
TextParser(SpecificationParsingContext& ctx, Vector<Token> const& tokens, XML::Node const* node)
: m_ctx(ctx)
, m_tokens(tokens)
, m_node(node)
{
}
TextParseErrorOr<ClauseHeader> parse_clause_header(ClauseHasAoidAttribute clause_has_aoid_attribute);
TextParseErrorOr<NullableTree> parse_step_without_substeps();
TextParseErrorOr<Tree> parse_step_with_substeps(Tree substeps);
FailedTextParseDiagnostic get_diagnostic() const;
private:
struct IfConditionParseResult {
bool is_if_branch;
NullableTree condition;
};
struct CustomMessage {
StringView message;
};
void save_error(Variant<TokenType, StringView, CustomMessage>&& expected);
void retreat();
[[nodiscard]] auto rollback_point();
Optional<Token> peek_token();
Optional<Token> consume_token();
TextParseErrorOr<Token> consume_token_with_one_of_types(std::initializer_list<TokenType> types);
TextParseErrorOr<Token> consume_token_with_type(TokenType type);
TextParseErrorOr<void> consume_token(TokenType type, StringView data);
TextParseErrorOr<void> consume_word(StringView word);
TextParseErrorOr<void> consume_words(std::initializer_list<StringView> words);
bool is_eof() const;
TextParseErrorOr<void> expect_eof();
TextParseErrorOr<Tree> parse_record_direct_list_initialization();
TextParseErrorOr<Vector<Tree>> parse_function_arguments();
TextParseErrorOr<Tree> parse_list_initialization();
TextParseErrorOr<Tree> parse_the_this_value();
TextParseErrorOr<Tree> parse_value();
TextParseErrorOr<Tree> parse_expression();
TextParseErrorOr<Tree> parse_condition();
TextParseErrorOr<Tree> parse_return_statement();
TextParseErrorOr<Tree> parse_assert();
TextParseErrorOr<Tree> parse_assignment();
TextParseErrorOr<Tree> parse_perform();
TextParseErrorOr<Tree> parse_simple_step_or_inline_if_branch();
TextParseErrorOr<IfConditionParseResult> parse_if_beginning();
TextParseErrorOr<Tree> parse_inline_if_else();
TextParseErrorOr<Tree> parse_if(Tree then_branch);
TextParseErrorOr<Tree> parse_else(Tree else_branch);
TextParseErrorOr<QualifiedName> parse_qualified_name();
TextParseErrorOr<Vector<FunctionArgument>> parse_function_arguments_in_declaration();
TextParseErrorOr<AbstractOperationDeclaration> parse_abstract_operation_declaration();
TextParseErrorOr<MethodDeclaration> parse_method_declaration();
TextParseErrorOr<AccessorDeclaration> parse_accessor_declaration();
TextParseErrorOr<ClauseHeader::PropertiesList> parse_properties_list_declaration();
SpecificationParsingContext& m_ctx;
Vector<Token> const& m_tokens;
size_t m_next_token_index = 0;
XML::Node const* m_node;
size_t m_max_parsed_tokens = 0;
Vector<Variant<TokenType, StringView, CustomMessage>, 8> m_suitable_continuations;
};
}

View file

@ -1,124 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibXML/Forward.h>
#include "AST/AST.h"
#include "DiagnosticEngine.h"
namespace JSSpecCompiler {
constexpr i32 ambiguous_operator_precedence = -2;
constexpr i32 pre_merged_operator_precedence = 2;
constexpr i32 unary_operator_precedence = 3;
constexpr i32 closing_bracket_precedence = 18;
// NOTE: Operator precedence is generally the same as in
// https://en.cppreference.com/w/cpp/language/operator_precedence (common sense applies).
#define ENUMERATE_TOKENS(F) \
F(Invalid, -1, Invalid, Invalid, Invalid, "") \
F(AmbiguousMinus, -2, Invalid, Invalid, Invalid, "minus") \
F(BinaryMinus, 6, Invalid, Minus, Invalid, "binary minus") \
F(BraceClose, 18, Invalid, Invalid, BraceOpen, "'}'") \
F(BraceOpen, -1, Invalid, Invalid, BraceClose, "'{'") \
F(Colon, -1, Invalid, Invalid, Invalid, "':'") \
F(Comma, 17, Invalid, Comma, Invalid, "','") \
F(Division, 5, Invalid, Division, Invalid, "division") \
F(Dot, -1, Invalid, Invalid, Invalid, "punctuation mark '.'") \
F(Enumerator, -1, Invalid, Invalid, Invalid, "enumerator") \
F(Equals, 10, Invalid, CompareEqual, Invalid, "equals") \
F(ExclamationMark, 3, AssertCompletion, Invalid, Invalid, "exclamation mark") \
F(Greater, 9, Invalid, CompareGreater, Invalid, "greater than") \
F(Identifier, -1, Invalid, Invalid, Invalid, "identifier") \
F(Is, -1, Invalid, Invalid, Invalid, "operator is") \
F(Less, 9, Invalid, CompareLess, Invalid, "less than") \
F(ListEnd, -1, Invalid, Invalid, Invalid, "»") \
F(ListStart, -1, Invalid, Invalid, Invalid, "«") \
F(MemberAccess, 2, Invalid, MemberAccess, Invalid, "member access operator '.'") \
F(Multiplication, 5, Invalid, Multiplication, Invalid, "multiplication") \
F(NotEquals, 10, Invalid, CompareNotEqual, Invalid, "not equals") \
F(Number, -1, Invalid, Invalid, Invalid, "number") \
F(ParenClose, 18, Invalid, Invalid, ParenOpen, "')'") \
F(ParenOpen, -1, Invalid, Invalid, ParenClose, "'('") \
F(Plus, 6, Invalid, Plus, Invalid, "plus") \
F(QuestionMark, 3, ReturnIfAbrubt, Invalid, Invalid, "question mark") \
F(SectionNumber, -1, Invalid, Invalid, Invalid, "section number") \
F(SquareBracketClose, -1, Invalid, Invalid, Invalid, "']'") \
F(SquareBracketOpen, -1, Invalid, Invalid, Invalid, "'['") \
F(String, -1, Invalid, Invalid, Invalid, "string literal") \
F(Superscript, 4, Invalid, Power, Invalid, "subscript") \
F(UnaryMinus, 3, Minus, Invalid, Invalid, "unary minus") \
F(WellKnownValue, -1, Invalid, Invalid, Invalid, "constant") \
F(Word, -1, Invalid, Invalid, Invalid, "word")
enum class TokenType {
#define ID(name, precedence, unary_name, binary_name, matching_bracket, name_for_diagnostic) name,
ENUMERATE_TOKENS(ID)
#undef ID
};
constexpr struct TokenInfo {
StringView name;
i32 precedence;
UnaryOperator as_unary_operator;
BinaryOperator as_binary_operator;
TokenType matching_bracket;
StringView name_for_diagnostic;
} token_info[] = {
#define TOKEN_INFO(name, precedence, unary_name, binary_name, matching_bracket, name_for_diagnostic) \
{ \
#name##sv, \
precedence, \
UnaryOperator::unary_name, \
BinaryOperator::binary_name, \
TokenType::matching_bracket, \
name_for_diagnostic##sv \
},
ENUMERATE_TOKENS(TOKEN_INFO)
#undef TOKEN_INFO
};
struct Token {
TokenInfo const& info() const { return token_info[to_underlying(type)]; }
StringView name() const { return info().name; }
StringView name_for_diagnostic() const { return info().name_for_diagnostic; }
i32 precedence() const { return info().precedence; }
bool is_operator() const { return precedence() > 0 && precedence() < closing_bracket_precedence; }
bool is_ambiguous_operator() const { return precedence() == ambiguous_operator_precedence; }
bool is_pre_merged_binary_operator() const { return precedence() == pre_merged_operator_precedence; }
bool is_unary_operator() const { return precedence() == unary_operator_precedence; }
bool is_binary_operator() const { return is_operator() && !is_unary_operator(); }
bool is_bracket() const { return info().matching_bracket != TokenType::Invalid; }
bool is_opening_bracket() const { return is_bracket() && precedence() == -1; }
bool is_closing_bracket() const { return is_bracket() && precedence() == closing_bracket_precedence; }
UnaryOperator as_unary_operator() const
{
VERIFY(is_unary_operator());
return info().as_unary_operator;
}
BinaryOperator as_binary_operator() const
{
VERIFY(is_binary_operator());
return info().as_binary_operator;
}
bool matches_with(Token const& bracket)
{
VERIFY(is_bracket());
return info().matching_bracket == bracket.type;
}
TokenType type;
StringView data;
Location location;
};
}

View file

@ -1,59 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/NonnullOwnPtr.h>
#include <LibXML/DOM/Node.h>
#include "Parser/XMLUtils.h"
namespace JSSpecCompiler {
bool contains_empty_text(XML::Node const* node)
{
return node->as_text().builder.string_view().trim_whitespace().is_empty();
}
Optional<StringView> get_attribute_by_name(XML::Node const* node, StringView attribute_name)
{
auto const& attribute = node->as_element().attributes.get(attribute_name);
if (!attribute.has_value())
return {};
return attribute.value();
}
Optional<StringView> get_text_contents(XML::Node const* node)
{
auto const& children = node->as_element().children;
if (children.size() != 1 || !children[0]->is_text())
return {};
return children[0]->as_text().builder.string_view();
}
Optional<XML::Node const*> get_single_child_with_tag(XML::Node const* element, StringView tag_name)
{
XML::Node const* result = nullptr;
for (auto const& child : element->as_element().children) {
auto is_valid = child->content.visit(
[&](XML::Node::Element const& element) {
result = child;
return result != nullptr || element.name != tag_name;
},
[&](XML::Node::Text const&) {
return contains_empty_text(child);
},
[&](auto const&) { return true; });
if (!is_valid)
return {};
}
if (result == nullptr)
return {};
return result;
}
}

View file

@ -1,22 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/StringView.h>
#include <LibXML/Forward.h>
namespace JSSpecCompiler {
bool contains_empty_text(XML::Node const* node);
Optional<StringView> get_attribute_by_name(XML::Node const* node, StringView attribute_name);
Optional<StringView> get_text_contents(XML::Node const* node);
Optional<XML::Node const*> get_single_child_with_tag(XML::Node const* element, StringView tag_name);
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/StringBuilder.h>
#include <AK/TemporaryChange.h>
namespace JSSpecCompiler {
class Printer {
public:
template<typename Func>
void block(Func&& func, StringView start = "{"sv, StringView end = "}"sv)
{
formatln("{}", start);
++indent_level;
func();
--indent_level;
format("{}", end);
}
template<typename... Parameters>
void format(AK::CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters)
{
if (builder.string_view().ends_with('\n'))
builder.append_repeated(' ', indent_level * 4);
builder.appendff(move(fmtstr), forward<Parameters const&>(parameters)...);
}
template<typename... Parameters>
void formatln(AK::CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters)
{
format(move(fmtstr), forward<Parameters const&>(parameters)...);
builder.append("\n"sv);
}
StringView view() const { return builder.string_view(); }
private:
StringBuilder builder;
size_t indent_level = 0;
};
}

View file

@ -1,30 +0,0 @@
/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Forward.h"
#include "Printer.h"
namespace JSSpecCompiler::Runtime {
class Cell {
public:
virtual ~Cell() { }
virtual StringView type_name() const = 0;
void dump(Printer& printer) const
{
// FIXME: Handle cyclic references.
return do_dump(printer);
}
protected:
virtual void do_dump(Printer& printer) const = 0;
};
}

View file

@ -1,69 +0,0 @@
/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Runtime/Object.h"
#include "Function.h"
namespace JSSpecCompiler::Runtime {
Optional<DataProperty&> Property::get_data_property_or_diagnose(Realm* realm, QualifiedName name, Location current_location)
{
if (!has<DataProperty>()) {
realm->diag().error(current_location,
"{} must be a data property", name.to_string());
realm->diag().note(location(),
"defined as an accessor property here");
return {};
}
return get<DataProperty>();
}
static StringView well_known_symbol_to_sv(WellKnownSymbol symbol)
{
static Array string_value = {
#define STRING_VALUE(enum_name, spec_name) "@@" #spec_name##sv,
ENUMERATE_WELL_KNOWN_SYMBOLS(STRING_VALUE)
#undef STRING_VALUE
};
return string_value[to_underlying(symbol)];
}
void Object::do_dump(Printer& printer) const
{
printer.block([&] {
for (auto const& [key, value] : m_properties) {
key.visit(
[&](Slot const& slot) { printer.format("[[{}]]", slot.key); },
[&](StringPropertyKey const& string_property) { printer.format("{}", string_property.key); },
[&](WellKnownSymbol const& symbol) { printer.format("{}", well_known_symbol_to_sv(symbol)); });
printer.format(": ");
value.visit(
[&](DataProperty const& data) {
printer.format(
"[{}{}{}] ",
data.is_configurable ? "c" : "",
data.is_enumerable ? "e" : "",
data.is_writable ? "w" : "");
data.value->dump(printer);
},
[&](AccessorProperty const& accessor) {
printer.format(
"[{}{}] AccessorProperty",
accessor.is_configurable ? "c" : "",
accessor.is_enumerable ? "e" : "");
printer.block([&] {
if (accessor.getter.has_value())
printer.formatln("get: {},", accessor.getter.value()->name());
if (accessor.setter.has_value())
printer.formatln("set: {},", accessor.setter.value()->name());
});
});
printer.formatln(",");
}
});
}
}

View file

@ -1,151 +0,0 @@
/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/TypeCasts.h>
#include "DiagnosticEngine.h"
#include "Function.h"
#include "Runtime/ObjectType.h"
namespace JSSpecCompiler::Runtime {
struct Slot {
bool operator==(Slot const&) const = default;
FlyString key;
};
struct StringPropertyKey {
bool operator==(StringPropertyKey const&) const = default;
FlyString key;
};
#define ENUMERATE_WELL_KNOWN_SYMBOLS(F) \
F(InstanceType, _instanceType) \
F(ToStringTag, toStringTag)
enum class WellKnownSymbol {
#define ID(enum_name, spec_name) enum_name,
ENUMERATE_WELL_KNOWN_SYMBOLS(ID)
#undef ID
};
class PropertyKey : public Variant<Slot, StringPropertyKey, WellKnownSymbol> {
public:
using Variant::Variant;
};
struct DataProperty {
template<typename T>
bool is() const
{
return ::is<Runtime::Object>(value);
}
template<typename T>
T* as() const
{
return verify_cast<T>(value);
}
template<typename T>
Optional<T*> get_or_diagnose(Realm* realm, QualifiedName name, Location location)
{
if (!is<T>()) {
realm->diag().error(location,
"{} must be a {}", name.to_string(), T::TYPE_NAME);
realm->diag().note(this->location,
"set to {} here", value->type_name());
return {};
}
return verify_cast<T>(value);
}
Cell* value;
Location location;
bool is_writable = true;
bool is_enumerable = false;
bool is_configurable = true;
};
struct AccessorProperty {
Optional<FunctionDeclarationRef> getter;
Optional<FunctionDeclarationRef> setter;
Location location;
bool is_enumerable = false;
bool is_configurable = true;
};
class Property : public Variant<DataProperty, AccessorProperty> {
public:
using Variant::Variant;
Location location() const
{
return visit([&](auto const& value) { return value.location; });
}
Optional<DataProperty&> get_data_property_or_diagnose(Realm* realm, QualifiedName name, Location location);
};
class Object : public Runtime::Cell {
public:
static constexpr StringView TYPE_NAME = "object"sv;
static Object* create(Realm* realm)
{
return realm->adopt_cell(new Object {});
}
StringView type_name() const override { return TYPE_NAME; }
auto& type() { return m_type; }
auto& properties() { return m_properties; }
bool has(PropertyKey const& key) const
{
return m_properties.contains(key);
}
Property& get(PropertyKey const& key)
{
return m_properties.get(key).value();
}
void set(PropertyKey const& key, Property&& property)
{
auto insertion_result = m_properties.set(key, move(property));
VERIFY(insertion_result == HashSetResult::InsertedNewEntry);
}
protected:
void do_dump(Printer& printer) const override;
private:
Object() = default;
Optional<ObjectType*> m_type;
HashMap<PropertyKey, Property> m_properties;
};
}
template<>
struct AK::Traits<JSSpecCompiler::Runtime::PropertyKey> : public DefaultTraits<JSSpecCompiler::Runtime::PropertyKey> {
static unsigned hash(JSSpecCompiler::Runtime::PropertyKey const& key)
{
using namespace JSSpecCompiler::Runtime;
return key.visit(
[](Slot const& slot) { return pair_int_hash(1, slot.key.hash()); },
[](StringPropertyKey const& string_key) { return pair_int_hash(2, string_key.key.hash()); },
[](WellKnownSymbol const& symbol) { return pair_int_hash(3, to_underlying(symbol)); });
}
};

View file

@ -1,16 +0,0 @@
/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Runtime/ObjectType.h"
namespace JSSpecCompiler::Runtime {
void ObjectType::do_dump(Printer& printer) const
{
printer.format("ObjectType");
}
}

View file

@ -1,31 +0,0 @@
/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Runtime/Realm.h"
namespace JSSpecCompiler::Runtime {
class ObjectType : public Cell {
public:
static constexpr StringView TYPE_NAME = "type"sv;
static ObjectType* create(Realm* realm)
{
return realm->adopt_cell(new ObjectType {});
}
StringView type_name() const override { return TYPE_NAME; }
protected:
void do_dump(Printer& printer) const override;
private:
ObjectType() = default;
};
}

View file

@ -1,18 +0,0 @@
/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Runtime/Realm.h"
#include "Runtime/Object.h"
namespace JSSpecCompiler::Runtime {
Realm::Realm(DiagnosticEngine& diag)
: m_diag(diag)
, m_global_object(Object::create(this))
{
}
}

View file

@ -1,38 +0,0 @@
/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullOwnPtr.h>
#include <AK/Vector.h>
#include "Runtime/Cell.h"
namespace JSSpecCompiler::Runtime {
class Realm {
public:
Realm(DiagnosticEngine& diag);
Runtime::Object* global_object() { return m_global_object; }
template<typename T>
T* adopt_cell(T* cell)
{
m_cells.append(NonnullOwnPtr<T> { NonnullOwnPtr<T>::AdoptTag::Adopt, *cell });
return cell;
}
DiagnosticEngine& diag() { return m_diag; }
private:
DiagnosticEngine& m_diag;
Vector<NonnullOwnPtr<Runtime::Cell>> m_cells;
Runtime::Object* m_global_object;
};
}

View file

@ -1,18 +0,0 @@
auto f(auto cond1, auto cond2)
{
int a;
int b;
if (cond1) {
a = 1;
if (cond2) {
b = a;
} else {
b = 3;
}
} else {
b = 4;
}
return b;
}

View file

@ -1,360 +0,0 @@
===== AST after parser =====
f(cond1, cond2):
TreeList
IfBranch
UnresolvedReference cond1
TreeList
BinaryOperation Declaration
UnresolvedReference a
MathematicalConstant 1
IfBranch
UnresolvedReference cond2
TreeList
BinaryOperation Declaration
UnresolvedReference b
UnresolvedReference a
ElseIfBranch Else
TreeList
BinaryOperation Declaration
UnresolvedReference b
MathematicalConstant 3
ElseIfBranch Else
TreeList
BinaryOperation Declaration
UnresolvedReference b
MathematicalConstant 4
ReturnNode
UnresolvedReference b
===== AST after if-branch-merging =====
f(cond1, cond2):
TreeList
IfElseIfChain
UnresolvedReference cond1
TreeList
BinaryOperation Declaration
UnresolvedReference a
MathematicalConstant 1
IfElseIfChain
UnresolvedReference cond2
TreeList
BinaryOperation Declaration
UnresolvedReference b
UnresolvedReference a
TreeList
BinaryOperation Declaration
UnresolvedReference b
MathematicalConstant 3
TreeList
BinaryOperation Declaration
UnresolvedReference b
MathematicalConstant 4
ReturnNode
UnresolvedReference b
===== AST after reference-resolving =====
f(cond1, cond2):
TreeList
IfElseIfChain
Var cond1
TreeList
BinaryOperation Assignment
Var a
MathematicalConstant 1
IfElseIfChain
Var cond2
TreeList
BinaryOperation Assignment
Var b
Var a
TreeList
BinaryOperation Assignment
Var b
MathematicalConstant 3
TreeList
BinaryOperation Assignment
Var b
MathematicalConstant 4
ReturnNode
Var b
===== AST after cfg-building =====
f(cond1, cond2):
TreeList
IfElseIfChain
Var cond1
TreeList
BinaryOperation Assignment
Var a
MathematicalConstant 1
IfElseIfChain
Var cond2
TreeList
BinaryOperation Assignment
Var b
Var a
TreeList
BinaryOperation Assignment
Var b
MathematicalConstant 3
TreeList
BinaryOperation Assignment
Var b
MathematicalConstant 4
ReturnNode
Var b
===== CFG after cfg-building =====
f(cond1, cond2):
0:
ControlFlowBranch true=3 false=7
Var cond1
1:
ControlFlowFunctionReturn
Var $return
2:
BinaryOperation Assignment
Var $return
Var b
ControlFlowJump jump=1
3:
BinaryOperation Assignment
Var a
MathematicalConstant 1
ControlFlowBranch true=5 false=6
Var cond2
4:
ControlFlowJump jump=2
5:
BinaryOperation Assignment
Var b
Var a
ControlFlowJump jump=4
6:
BinaryOperation Assignment
Var b
MathematicalConstant 3
ControlFlowJump jump=4
7:
BinaryOperation Assignment
Var b
MathematicalConstant 4
ControlFlowJump jump=2
8:
BinaryOperation Assignment
Var $return
Error ""
ControlFlowJump jump=1
===== AST after cfg-simplification =====
f(cond1, cond2):
TreeList
IfElseIfChain
Var cond1
TreeList
BinaryOperation Assignment
Var a
MathematicalConstant 1
IfElseIfChain
Var cond2
TreeList
BinaryOperation Assignment
Var b
Var a
TreeList
BinaryOperation Assignment
Var b
MathematicalConstant 3
TreeList
BinaryOperation Assignment
Var b
MathematicalConstant 4
ReturnNode
Var b
===== CFG after cfg-simplification =====
f(cond1, cond2):
0:
ControlFlowBranch true=3 false=6
Var cond1
1:
ControlFlowFunctionReturn
Var $return
2:
BinaryOperation Assignment
Var $return
Var b
ControlFlowJump jump=1
3:
BinaryOperation Assignment
Var a
MathematicalConstant 1
ControlFlowBranch true=4 false=5
Var cond2
4:
BinaryOperation Assignment
Var b
Var a
ControlFlowJump jump=2
5:
BinaryOperation Assignment
Var b
MathematicalConstant 3
ControlFlowJump jump=2
6:
BinaryOperation Assignment
Var b
MathematicalConstant 4
ControlFlowJump jump=2
===== AST after ssa-building =====
f(cond1, cond2):
TreeList
IfElseIfChain
Var cond1@0
TreeList
BinaryOperation Assignment
Var a@1
MathematicalConstant 1
IfElseIfChain
Var cond2@0
TreeList
BinaryOperation Assignment
Var b@1
Var a@1
TreeList
BinaryOperation Assignment
Var b@3
MathematicalConstant 3
TreeList
BinaryOperation Assignment
Var b@4
MathematicalConstant 4
ReturnNode
Var b@2
===== CFG after ssa-building =====
f(cond1, cond2):
0:
ControlFlowBranch true=1 false=6
Var cond1@0
1:
BinaryOperation Assignment
Var a@1
MathematicalConstant 1
ControlFlowBranch true=2 false=5
Var cond2@0
2:
BinaryOperation Assignment
Var b@1
Var a@1
ControlFlowJump jump=3
3:
a@2 = phi(2: a@1, 5: a@1, 6: a@0)
b@2 = phi(2: b@1, 5: b@3, 6: b@4)
BinaryOperation Assignment
Var $return@1
Var b@2
ControlFlowJump jump=4
4:
ControlFlowFunctionReturn
Var $return@1
5:
BinaryOperation Assignment
Var b@3
MathematicalConstant 3
ControlFlowJump jump=3
6:
BinaryOperation Assignment
Var b@4
MathematicalConstant 4
ControlFlowJump jump=3
===== AST after dce =====
f(cond1, cond2):
TreeList
IfElseIfChain
Var cond1@0
TreeList
BinaryOperation Assignment
Var a@1
MathematicalConstant 1
IfElseIfChain
Var cond2@0
TreeList
BinaryOperation Assignment
Var b@1
Var a@1
TreeList
BinaryOperation Assignment
Var b@3
MathematicalConstant 3
TreeList
BinaryOperation Assignment
Var b@4
MathematicalConstant 4
ReturnNode
Var b@2
===== CFG after dce =====
f(cond1, cond2):
0:
ControlFlowBranch true=1 false=6
Var cond1@0
1:
BinaryOperation Assignment
Var a@1
MathematicalConstant 1
ControlFlowBranch true=2 false=5
Var cond2@0
2:
BinaryOperation Assignment
Var b@1
Var a@1
ControlFlowJump jump=3
3:
b@2 = phi(2: b@1, 5: b@3, 6: b@4)
BinaryOperation Assignment
Var $return@1
Var b@2
ControlFlowJump jump=4
4:
ControlFlowFunctionReturn
Var $return@1
5:
BinaryOperation Assignment
Var b@3
MathematicalConstant 3
ControlFlowJump jump=3
6:
BinaryOperation Assignment
Var b@4
MathematicalConstant 4
ControlFlowJump jump=3

View file

@ -1,17 +0,0 @@
<!DOCTYPE inline_dtd[<!ENTITY nbsp " ">]>
<specification>
<emu-clause id="1">
<h1><span class="secnum">1</span> get Foo.Bar.baz</h1>
<emu-alg><ol><li>Return <emu-const>unused</emu-const>.</li></ol></emu-alg>
</emu-clause>
<emu-clause id="2" aoid="TestAbstractOperation">
<h1><span class="secnum">2</span> TestAbstractOperation ( <var>a</var> )</h1>
<emu-alg><ol><li>Return <emu-const>unused</emu-const>.</li></ol></emu-alg>
</emu-clause>
<emu-clause id="3">
<h1><span class="secnum">3</span> Foo.Bar.foo ( <var>a</var> )</h1>
<emu-alg><ol><li>Return <emu-const>unused</emu-const>.</li></ol></emu-alg>
</emu-clause>
</specification>

View file

@ -1,16 +0,0 @@
===== AST after reference-resolving =====
%get Foo.Bar.baz%():
TreeList
ReturnNode
Enumerator unused
TestAbstractOperation(a):
TreeList
ReturnNode
Enumerator unused
%Foo.Bar.foo%(a):
TreeList
ReturnNode
Enumerator unused

View file

@ -1,19 +0,0 @@
<!DOCTYPE inline_dtd[<!ENTITY nbsp " ">]>
<specification>
<emu-import>
<emu-clause id="1">
<h1><span class="secnum">1</span> The Celestial Object</h1>
<emu-clause id="1-1">
<h1><span class="secnum">1.1</span> Abstract Operations</h1>
<emu-clause id="1-1-1" aoid="Foo">
<h1><span class="secnum">1.1.1</span> Foo ( <var>a</var> )</h1>
<emu-alg>
<ol>
<li>Return <var>a</var>.<var>[[b]]</var>.</li>
</ol>
</emu-alg>
</emu-clause>
</emu-clause>
</emu-clause>
</emu-import>
</specification>

View file

@ -1,8 +0,0 @@
===== AST after reference-resolving =====
Foo(a):
TreeList
ReturnNode
BinaryOperation MemberAccess
Var a
Slot b

View file

@ -1,12 +0,0 @@
<!DOCTYPE inline_dtd[<!ENTITY nbsp " ">]>
<specification>
<emu-clause id="1" aoid="TestOptionalArgumentsGroups1">
<h1><span class="secnum">1</span> TestOptionalArgumentsGroups1 ( [<var>a</var>, <var>b</var>[, <var>c</var>]] )</h1>
<emu-alg><ol><li>Return <emu-const>unused</emu-const>.</li></ol></emu-alg>
</emu-clause>
<emu-clause id="2" aoid="TestOptionalArgumentsGroups2">
<h1><span class="secnum">2</span> TestOptionalArgumentsGroups2 ( <var>a</var>, <var>b</var>[, <var>c</var>, <var>d</var>] )</h1>
<emu-alg><ol><li>Return <emu-const>unused</emu-const>.</li></ol></emu-alg>
</emu-clause>
</specification>

View file

@ -1,11 +0,0 @@
===== AST after reference-resolving =====
TestOptionalArgumentsGroups1([a, b, [c]]):
TreeList
ReturnNode
Enumerator unused
TestOptionalArgumentsGroups2(a, b, [c, d]):
TreeList
ReturnNode
Enumerator unused

View file

@ -1,85 +0,0 @@
<!DOCTYPE inline_dtd[<!ENTITY nbsp " ">]>
<specification>
<emu-clause id="1" aoid="ArbitrarilyLargeNumbers">
<h1><span class="secnum">1</span> ArbitrarilyLargeNumbers ( <var>a</var> )</h1>
<emu-alg>
<ol>
<li>Let <var>a</var> be 1.</li>
<li>Let <var>b</var> be 3.6.</li>
<li>Let <var>c</var> be -3.6.</li>
<li>Let <var>d</var> be -1000000000000000000000.</li>
<li>Let <var>e</var> be 1.0000001.</li>
<li>Return <var>a</var>+<var>b</var>+<var>c</var>+<var>d</var>+<var>e</var>.</li>
</ol>
</emu-alg>
</emu-clause>
<emu-clause id="2" aoid="WellKnownConstants">
<h1><span class="secnum">2</span> WellKnownConstants ( <var>a</var> )</h1>
<emu-alg>
<ol>
<li>
If <var>a</var> is <emu-val>undefined</emu-val>, then
<ol>
<li>Let <var>b</var> be <emu-val>null</emu-val>.</li>
<li>Return <emu-val>true</emu-val>.</li>
</ol>
</li>
<li>Else,
<ol>
<li>Let <var>c</var> be <emu-val>this</emu-val>.</li>
<li>Return <emu-val>false</emu-val>.</li>
</ol>
</li>
</ol>
</emu-alg>
</emu-clause>
<emu-clause id="3" aoid="TestReturnIfAbrupt">
<h1><span class="secnum">3</span> TestReturnIfAbrupt ( <var>a</var> )</h1>
<emu-alg>
<ol>
<li>Return ? <emu-xref><a>WellKnownConstants</a></emu-xref>(<var>a</var>).</li>
</ol>
</emu-alg>
</emu-clause>
<emu-clause id="4" aoid="Enumerators">
<h1><span class="secnum">4</span> Enumerators ( )</h1>
<emu-alg>
<ol>
<li>Return ? <emu-xref><a>WellKnownConstants</a></emu-xref>(<emu-const>enumerator</emu-const>).</li>
</ol>
</emu-alg>
</emu-clause>
<emu-clause id="5" aoid="Lists">
<h1>
<span class="secnum">5</span> Lists ( <var>a</var>, <var>b</var> )
</h1>
<emu-alg>
<ol>
<li>Let <var>a</var> be « ».</li>
<li>Set <var>a</var> to « <emu-const>1</emu-const> ».</li>
<li>Set <var>a</var> to « <emu-const>1</emu-const>, <emu-const>2</emu-const> ».</li>
<li>Set <var>a</var> to « <emu-const>1</emu-const>, <emu-const>2</emu-const>, 3 + 4 ».</li>
<li>Return <emu-const>unused</emu-const>.</li>
</ol>
</emu-alg>
</emu-clause>
<emu-clause id="6">
<h1><span class="secnum">6</span> get Temporal.PlainDateTime.prototype.inLeapYear</h1>
<emu-alg>
<ol>
<li>Let <var>dateTime</var> be the <emu-val>this</emu-val> value.</li>
<li>Perform ? <emu-xref><a>RequireInternalSlot</a></emu-xref>(<var>dateTime</var>, <var class="field">[[A]]</var>).</li>
<li>Return <emu-val>undefined</emu-val>.</li>
</ol>
</emu-alg>
</emu-clause>
<emu-clause id="7" aoid="Notes">
<h1><span class="secnum">7</span> Notes ( )</h1>
<emu-alg>
<ol>
<li>NOTE: This abstract operation returns <emu-const>unused</emu-const> in case you didn't notice.</li>
<li>Return <emu-const>unused</emu-const>.</li>
</ol>
</emu-alg>
</emu-clause>
</specification>

View file

@ -1,107 +0,0 @@
===== AST after reference-resolving =====
ArbitrarilyLargeNumbers(a):
TreeList
BinaryOperation Assignment
Var a
MathematicalConstant 1
BinaryOperation Assignment
Var b
MathematicalConstant 3.6
BinaryOperation Assignment
Var c
MathematicalConstant -3.6
BinaryOperation Assignment
Var d
MathematicalConstant -1000000000000000000000
BinaryOperation Assignment
Var e
MathematicalConstant 10000001/10000000
ReturnNode
BinaryOperation Plus
Var a
BinaryOperation Plus
Var b
BinaryOperation Plus
Var c
BinaryOperation Plus
Var d
Var e
WellKnownConstants(a):
TreeList
IfElseIfChain
IsOneOf
Var a
WellKnownNode Undefined
TreeList
BinaryOperation Assignment
Var b
WellKnownNode Null
ReturnNode
WellKnownNode True
TreeList
BinaryOperation Assignment
Var c
WellKnownNode This
ReturnNode
WellKnownNode False
TestReturnIfAbrupt(a):
TreeList
ReturnNode
UnaryOperation ReturnIfAbrubt
FunctionCall
Func "WellKnownConstants"
Var a
Enumerators():
TreeList
ReturnNode
UnaryOperation ReturnIfAbrubt
FunctionCall
Func "WellKnownConstants"
Enumerator enumerator
Lists(a, b):
TreeList
BinaryOperation Assignment
Var a
List
BinaryOperation Assignment
Var a
List
Enumerator 1
BinaryOperation Assignment
Var a
List
Enumerator 1
Enumerator 2
BinaryOperation Assignment
Var a
List
Enumerator 1
Enumerator 2
BinaryOperation Plus
MathematicalConstant 3
MathematicalConstant 4
ReturnNode
Enumerator unused
%get Temporal.PlainDateTime.prototype.inLeapYear%():
TreeList
BinaryOperation Assignment
Var dateTime
WellKnownNode This
UnaryOperation ReturnIfAbrubt
FunctionCall
UnresolvedReference RequireInternalSlot
Var dateTime
Slot A
ReturnNode
WellKnownNode Undefined
Notes():
TreeList
ReturnNode
Enumerator unused

View file

@ -1,35 +0,0 @@
<!DOCTYPE inline_dtd[<!ENTITY nbsp " ">]>
<specification>
<emu-import href="spec/celestial.html">
<emu-clause id="sec-celestial-now-object">
<h1><span class="secnum">2</span> The Celestial.Now Object</h1>
<emu-clause id="sec-celestial-now-abstract-ops">
<h1><span class="secnum">2.3</span> Abstract Operations</h1>
<emu-clause id="sec-celestial-systemutcepochmilliseconds" aoid="SystemUTCEpochMilliseconds">
<h1><span class="secnum">2.3.2</span> SystemUTCEpochMilliseconds ( )</h1>
<emu-alg>
<ol>
<li>
Let <var>global</var> be
<emu-xref aoid="GetGlobalObject"><a href="https://tc39.es/ecma262/#sec-getglobalobject">GetGlobalObject</a></emu-xref>
().
</li>
<li>
Let <var>nowNs</var> be
<emu-xref aoid="HostSystemUTCEpochNanoseconds" id="_ref_119"><a href="#sec-hostsystemutcepochnanoseconds">HostSystemUTCEpochNanoseconds</a></emu-xref>
(<var>global</var>).
</li>
<li>
Return
<emu-xref aoid="𝔽"><a href="https://tc39.es/ecma262/#𝔽">𝔽</a></emu-xref>
(
<emu-xref aoid="floor"><a href="https://tc39.es/ecma262/#eqn-floor">floor</a></emu-xref>
(<var>nowNs</var> / 10<sup>6</sup>)).
</li>
</ol>
</emu-alg>
</emu-clause>
</emu-clause>
</emu-clause>
</emu-import>
</specification>

View file

@ -1,23 +0,0 @@
===== AST after reference-resolving =====
SystemUTCEpochMilliseconds():
TreeList
BinaryOperation Assignment
Var global
FunctionCall
UnresolvedReference GetGlobalObject
BinaryOperation Assignment
Var nowNs
FunctionCall
UnresolvedReference HostSystemUTCEpochNanoseconds
Var global
ReturnNode
FunctionCall
UnresolvedReference 𝔽
FunctionCall
UnresolvedReference floor
BinaryOperation Division
Var nowNs
BinaryOperation Power
MathematicalConstant 10
MathematicalConstant 6

View file

@ -1,173 +0,0 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Format.h>
#include <LibCore/ArgsParser.h>
#include <LibMain/Main.h>
#include "Compiler/Passes/CFGBuildingPass.h"
#include "Compiler/Passes/CFGSimplificationPass.h"
#include "Compiler/Passes/DeadCodeEliminationPass.h"
#include "Compiler/Passes/IfBranchMergingPass.h"
#include "Compiler/Passes/ReferenceResolvingPass.h"
#include "Compiler/Passes/SSABuildingPass.h"
#include "Function.h"
#include "Parser/CppASTConverter.h"
#include "Parser/SpecificationParsing.h"
using namespace JSSpecCompiler;
struct CompilationStepWithDumpOptions {
OwnPtr<CompilationStep> step;
bool dump_ast = false;
bool dump_cfg = false;
};
class CompilationPipeline {
public:
template<typename T>
void add_compilation_pass()
{
auto func = +[](TranslationUnitRef translation_unit) {
T { translation_unit }.run();
};
add_step(adopt_own_if_nonnull(new NonOwningCompilationStep(T::name, func)));
}
template<typename T>
void for_each_step_in(StringView pass_list, T&& func)
{
HashTable<StringView> selected_steps;
for (auto pass : pass_list.split_view(',')) {
if (pass == "all") {
for (auto const& step : m_pipeline)
selected_steps.set(step.step->name());
} else if (pass == "last") {
selected_steps.set(m_pipeline.last().step->name());
} else if (pass.starts_with('-')) {
VERIFY(selected_steps.remove(pass.substring_view(1)));
} else {
selected_steps.set(pass);
}
}
for (auto& step : m_pipeline)
if (selected_steps.contains(step.step->name()))
func(step);
}
void add_step(OwnPtr<CompilationStep>&& step)
{
m_pipeline.append({ move(step) });
}
auto const& pipeline() const { return m_pipeline; }
private:
Vector<CompilationStepWithDumpOptions> m_pipeline;
};
template<>
struct AK::Formatter<ReadonlySpan<FunctionArgument>> : AK::Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, ReadonlySpan<FunctionArgument> const& arguments)
{
size_t previous_optional_group = 0;
for (size_t i = 0; i < arguments.size(); ++i) {
if (previous_optional_group != arguments[i].optional_arguments_group) {
previous_optional_group = arguments[i].optional_arguments_group;
TRY(builder.put_string("["sv));
}
TRY(builder.put_string(arguments[i].name));
if (i + 1 != arguments.size())
TRY(builder.put_literal(", "sv));
}
TRY(builder.put_string(TRY(String::repeated(']', previous_optional_group))));
return {};
}
};
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
Core::ArgsParser args_parser;
StringView filename;
args_parser.add_positional_argument(filename, "File to compile", "file");
constexpr StringView language_spec = "spec"sv;
constexpr StringView language_cpp = "c++"sv;
StringView language = language_spec;
args_parser.add_option(Core::ArgsParser::Option {
.argument_mode = Core::ArgsParser::OptionArgumentMode::Optional,
.help_string = "Specify the language of the input file.",
.short_name = 'x',
.value_name = "{c++|spec}",
.accept_value = [&](StringView value) {
language = value;
return language.is_one_of(language_spec, language_cpp);
},
});
StringView passes_to_dump_ast;
args_parser.add_option(passes_to_dump_ast, "Dump AST after specified passes.", "dump-ast", 0, "{all|last|<pass-name>|-<pass-name>[,...]}");
StringView passes_to_dump_cfg;
args_parser.add_option(passes_to_dump_cfg, "Dump CFG after specified passes.", "dump-cfg", 0, "{all|last|<pass-name>|-<pass-name>[,...]}");
bool silence_diagnostics = false;
args_parser.add_option(silence_diagnostics, "Silence all diagnostics.", "silence-diagnostics");
args_parser.parse(arguments);
CompilationPipeline pipeline;
if (language == language_cpp)
pipeline.add_step(adopt_own_if_nonnull(new CppParsingStep()));
else
pipeline.add_step(adopt_own_if_nonnull(new SpecificationParsingStep()));
pipeline.add_compilation_pass<IfBranchMergingPass>();
pipeline.add_compilation_pass<ReferenceResolvingPass>();
pipeline.add_compilation_pass<CFGBuildingPass>();
pipeline.add_compilation_pass<CFGSimplificationPass>();
pipeline.add_compilation_pass<SSABuildingPass>();
pipeline.add_compilation_pass<DeadCodeEliminationPass>();
pipeline.for_each_step_in(passes_to_dump_ast, [](CompilationStepWithDumpOptions& step) {
step.dump_ast = true;
});
pipeline.for_each_step_in(passes_to_dump_cfg, [](CompilationStepWithDumpOptions& step) {
step.dump_cfg = true;
});
TranslationUnit translation_unit(filename);
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()) {
outln(stderr, "{}({}):", function->name(), function->arguments());
outln(stderr, "{}", function->m_ast);
}
}
if (step.dump_cfg && translation_unit.functions_to_compile().size() && translation_unit.functions_to_compile()[0]->m_cfg != nullptr) {
outln(stderr, "===== CFG after {} =====", step.step->name());
for (auto const& function : translation_unit.functions_to_compile()) {
outln(stderr, "{}({}):", function->name(), function->arguments());
outln(stderr, "{}", *function->m_cfg);
}
}
}
if (!silence_diagnostics)
translation_unit.diag().print_diagnostics();
return 0;
}

Some files were not shown because too many files have changed in this diff Show more