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
parent 421aa7c475
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
/Lagom/Tools/CodeGenerators/LibWeb @AtkinsSJ
/Tests/LibCompress @timschumi
/Toolchain @BertalanD
/Userland/Libraries/LibArchive @timschumi
/Userland/Libraries/LibCompress @timschumi
/Userland/Libraries/LibCore/File.* @timschumi
/Userland/Libraries/LibCore/Socket.* @timschumi
/Userland/Libraries/LibCrypto @alimpfard
/Userland/Libraries/LibELF @BertalanD
/Userland/Libraries/LibGL @GMTA
/Userland/Libraries/LibGPU @GMTA
/Userland/Libraries/LibHTTP @alimpfard
/Userland/Libraries/LibJS/Runtime/Intl @trflynn89
/Userland/Libraries/LibLocale @trflynn89
/Userland/Libraries/LibRegex @alimpfard
/Userland/Libraries/LibSoftGPU @GMTA
/Userland/Libraries/LibSQL @GMTA @trflynn89
/Userland/Libraries/LibTLS @alimpfard
/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})
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)
if (NOT IS_ABSOLUTE ${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.
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
install(
DIRECTORY "${SERENITY_PROJECT_ROOT}/AK"
@ -444,27 +433,20 @@ if (BUILD_LAGOM)
Archive
Audio
Compress
Cpp
Crypto
Diff
Gemini
Gfx
GL
GLSL
GPU
HTTP
ImageDecoderClient
IPC
JIT
JS
Line
Locale
Markdown
PDF
Protocol
Regex
RIFF
SoftGPU
SQL
Syntax
TextCodec
@ -489,10 +471,6 @@ if (BUILD_LAGOM)
compile_ipc(${SERENITY_PROJECT_ROOT}/Userland/Services/WebContent/WebDriverServer.ipc Userland/Services/WebContent/WebDriverServerEndpoint.h)
endif()
if (NOT EMSCRIPTEN)
list(APPEND lagom_standard_libraries ELF X86)
endif()
foreach(lib IN LISTS lagom_standard_libraries)
add_serenity_subdirectory("Userland/Libraries/Lib${lib}")
endforeach()
@ -518,10 +496,6 @@ if (BUILD_LAGOM)
add_serenity_subdirectory(Ladybird)
endif()
if (APPLE)
add_serenity_subdirectory(Meta/Lagom/Contrib/MacPDF)
endif()
find_package(SDL2 QUIET)
if (SDL2_FOUND)
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(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(tar SOURCES ../../Userland/Utilities/tar.cpp LIBS LibArchive LibCompress LibFileSystem LibMain)
lagom_utility(test262-runner SOURCES ../../Tests/LibJS/test262-runner.cpp LIBS LibJS LibFileSystem)
@ -591,14 +564,11 @@ if (BUILD_LAGOM)
# LibTest tests from Tests/
set(TEST_DIRECTORIES
AK
JSSpecCompiler
LibCrypto
LibCompress
LibGL
LibGfx
LibLocale
LibMarkdown
LibPDF
LibSQL
LibTest
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
DeflateCompression
DeflateDecompression
ELF
FlacLoader
Gemini
GIFLoader
@ -30,7 +29,6 @@ set(FUZZER_TARGETS
MP3Loader
PAMLoader
PBMLoader
PDF
PEM
PGMLoader
PNGLoader
@ -99,7 +97,6 @@ set(FUZZER_DEPENDENCIES_MD5 LibCrypto)
set(FUZZER_DEPENDENCIES_MP3Loader LibAudio)
set(FUZZER_DEPENDENCIES_PAMLoader LibGfx)
set(FUZZER_DEPENDENCIES_PBMLoader LibGfx)
set(FUZZER_DEPENDENCIES_PDF LibPDF)
set(FUZZER_DEPENDENCIES_PEM LibCrypto)
set(FUZZER_DEPENDENCIES_PGMLoader LibGfx)
set(FUZZER_DEPENDENCIES_PNGLoader LibGfx)

View file

@ -1,9 +1,4 @@
add_subdirectory(GMLCompiler)
add_subdirectory(IPCCompiler)
if (BUILD_LAGOM)
add_subdirectory(JSSpecCompiler)
endif()
add_subdirectory(LibGL)
add_subdirectory(LibLocale)
add_subdirectory(LibTextCodec)
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