mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-24 16:40:21 +00:00
Everywhere: Remove a lot more things we don't need
This commit is contained in:
parent
421aa7c475
commit
e70d96e4e7
Notes:
sideshowbarker
2024-07-17 04:09:56 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/LadybirdBrowser/ancient-history/commit/e70d96e4e7
779 changed files with 3 additions and 122585 deletions
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
162
AK/UBSanitizer.h
162
AK/UBSanitizer.h
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
};
|
|
@ -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})
|
||||
|
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
lagom_tool(GMLCompiler LIBS LibMain LibCore LibGUI_GML)
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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 {};
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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*;
|
||||
|
||||
}
|
|
@ -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++;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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 };
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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) {});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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()) };
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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(",");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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)); });
|
||||
}
|
||||
};
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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))
|
||||
{
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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>
|
|
@ -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
|
||||
|
|
@ -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>
|
|
@ -1,8 +0,0 @@
|
|||
===== AST after reference-resolving =====
|
||||
Foo(a):
|
||||
TreeList
|
||||
ReturnNode
|
||||
BinaryOperation MemberAccess
|
||||
Var a
|
||||
Slot b
|
||||
|
|
@ -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>
|
|
@ -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
|
||||
|
|
@ -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>
|
|
@ -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
|
||||
|
|
@ -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>
|
|
@ -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
|
||||
|
|
@ -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
Loading…
Reference in a new issue