From c932e30f63b88cca2bedecb67f89b4d842311150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Tue, 10 Mar 2026 00:08:03 -0300 Subject: [PATCH 01/15] Initial shot at Smalltalk Compiler and Parser Implementation - Introduced SSmalltalkScanner for token scanning and parsing. - Created SToken and its derived classes for token representation. - Implemented Stream class for handling source code streams. - Developed SCompiler for frontend compilation logic and character classification. - Added SSmalltalkCompiler to orchestrate the compilation pipeline. - Implemented Stretch class to represent source code ranges. - Introduced egg::string for Unicode string handling in the compiler. - Added error handling and semantic analysis capabilities. --- runtime/cpp/Compiler/Backend/SCompiledBlock.h | 31 + .../cpp/Compiler/Backend/SCompiledMethod.cpp | 109 +++ .../cpp/Compiler/Backend/SCompiledMethod.h | 127 +++ runtime/cpp/Compiler/Binding/BlockScope.cpp | 299 +++++++ runtime/cpp/Compiler/Binding/BlockScope.h | 60 ++ runtime/cpp/Compiler/Binding/MethodScope.cpp | 69 ++ runtime/cpp/Compiler/Binding/MethodScope.h | 46 + runtime/cpp/Compiler/Binding/Scope.h | 31 + runtime/cpp/Compiler/Binding/ScriptScope.cpp | 141 +++ runtime/cpp/Compiler/Binding/ScriptScope.h | 71 ++ runtime/cpp/Compiler/CompilationError.cpp | 54 ++ runtime/cpp/Compiler/CompilationError.h | 52 ++ runtime/cpp/Compiler/CompilationResult.h | 42 + .../cpp/Compiler/Parser/SSmalltalkParser.cpp | 846 ++++++++++++++++++ .../cpp/Compiler/Parser/SSmalltalkParser.h | 129 +++ .../cpp/Compiler/Parser/SSmalltalkScanner.cpp | 419 +++++++++ .../cpp/Compiler/Parser/SSmalltalkScanner.h | 84 ++ runtime/cpp/Compiler/Parser/SToken.h | 155 ++++ runtime/cpp/Compiler/Parser/Stream.h | 95 ++ runtime/cpp/Compiler/SCompiler.cpp | 20 + runtime/cpp/Compiler/SCompiler.h | 35 + runtime/cpp/Compiler/SSmalltalkCompiler.cpp | 295 ++++++ runtime/cpp/Compiler/SSmalltalkCompiler.h | 115 +++ runtime/cpp/Compiler/Stretch.h | 34 + runtime/cpp/Compiler/egg_string.h | 274 ++++++ 25 files changed, 3633 insertions(+) create mode 100644 runtime/cpp/Compiler/Backend/SCompiledBlock.h create mode 100644 runtime/cpp/Compiler/Backend/SCompiledMethod.cpp create mode 100644 runtime/cpp/Compiler/Backend/SCompiledMethod.h create mode 100644 runtime/cpp/Compiler/Binding/BlockScope.cpp create mode 100644 runtime/cpp/Compiler/Binding/BlockScope.h create mode 100644 runtime/cpp/Compiler/Binding/MethodScope.cpp create mode 100644 runtime/cpp/Compiler/Binding/MethodScope.h create mode 100644 runtime/cpp/Compiler/Binding/Scope.h create mode 100644 runtime/cpp/Compiler/Binding/ScriptScope.cpp create mode 100644 runtime/cpp/Compiler/Binding/ScriptScope.h create mode 100644 runtime/cpp/Compiler/CompilationError.cpp create mode 100644 runtime/cpp/Compiler/CompilationError.h create mode 100644 runtime/cpp/Compiler/CompilationResult.h create mode 100644 runtime/cpp/Compiler/Parser/SSmalltalkParser.cpp create mode 100644 runtime/cpp/Compiler/Parser/SSmalltalkParser.h create mode 100644 runtime/cpp/Compiler/Parser/SSmalltalkScanner.cpp create mode 100644 runtime/cpp/Compiler/Parser/SSmalltalkScanner.h create mode 100644 runtime/cpp/Compiler/Parser/SToken.h create mode 100644 runtime/cpp/Compiler/Parser/Stream.h create mode 100644 runtime/cpp/Compiler/SCompiler.cpp create mode 100644 runtime/cpp/Compiler/SCompiler.h create mode 100644 runtime/cpp/Compiler/SSmalltalkCompiler.cpp create mode 100644 runtime/cpp/Compiler/SSmalltalkCompiler.h create mode 100644 runtime/cpp/Compiler/Stretch.h create mode 100644 runtime/cpp/Compiler/egg_string.h diff --git a/runtime/cpp/Compiler/Backend/SCompiledBlock.h b/runtime/cpp/Compiler/Backend/SCompiledBlock.h new file mode 100644 index 00000000..37a2da69 --- /dev/null +++ b/runtime/cpp/Compiler/Backend/SCompiledBlock.h @@ -0,0 +1,31 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _SCOMPILEDBLOCK_H_ +#define _SCOMPILEDBLOCK_H_ + +namespace Egg { + +class SCompiledMethod; + +/** + * Compiled block representation + * Corresponds to SCompiledBlock in Smalltalk + */ +class SCompiledBlock { +private: + SCompiledMethod* _method; + +public: + SCompiledBlock() : _method(nullptr) {} + + SCompiledMethod* method() const { return _method; } + void method_(SCompiledMethod* m) { _method = m; } + + bool isBlock() const { return true; } +}; + +} // namespace Egg + +#endif // _SCOMPILEDBLOCK_H_ diff --git a/runtime/cpp/Compiler/Backend/SCompiledMethod.cpp b/runtime/cpp/Compiler/Backend/SCompiledMethod.cpp new file mode 100644 index 00000000..0f9f06c6 --- /dev/null +++ b/runtime/cpp/Compiler/Backend/SCompiledMethod.cpp @@ -0,0 +1,109 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#include "SCompiledMethod.h" +#include "SCompiledBlock.h" + +namespace Egg { + +SCompiledMethod::SCompiledMethod(std::vector literals) + : _literals(std::move(literals)), _format(0), _optimizedCode(nullptr), _classBinding(nullptr) { +} + +SCompiledMethod::~SCompiledMethod() { +} + +SCompiledMethod* SCompiledMethod::withAll(const std::vector& literals) { + return new SCompiledMethod(literals); +} + +uint32_t SCompiledMethod::getBits_(uint32_t shift, uint32_t mask) const { + return (_format >> shift) & mask; +} + +void SCompiledMethod::setBits_(uint32_t shift, uint32_t mask, uint32_t value) { + _format = (_format & ~(mask << shift)) | ((value & mask) << shift); +} + +uint32_t SCompiledMethod::argumentCount() const { + return getBits_(ArgCount_Shift, ArgCount_Mask); +} + +void SCompiledMethod::argumentCount_(uint32_t count) { + setBits_(ArgCount_Shift, ArgCount_Mask, count); +} + +uint32_t SCompiledMethod::blockCount() const { + return getBits_(BlockCount_Shift, BlockCount_Mask); +} + +void SCompiledMethod::blockCount_(uint32_t count) { + setBits_(BlockCount_Shift, BlockCount_Mask, count); +} + +uint32_t SCompiledMethod::tempCount() const { + return getBits_(TempCount_Shift, TempCount_Mask); +} + +void SCompiledMethod::tempCount_(uint32_t count) { + setBits_(TempCount_Shift, TempCount_Mask, count); +} + +uint32_t SCompiledMethod::environmentCount() const { + return getBits_(EnvCount_Shift, EnvCount_Mask); +} + +void SCompiledMethod::environmentCount_(uint32_t count) { + setBits_(EnvCount_Shift, EnvCount_Mask, count); +} + +bool SCompiledMethod::capturesSelf() const { + return (_format & CapturesSelf) != 0; +} + +void SCompiledMethod::capturesSelf_(bool value) { + if (value) { + _format |= CapturesSelf; + } else { + _format &= ~CapturesSelf; + } +} + +bool SCompiledMethod::hasEnvironment() const { + return (_format & HasEnvironment) != 0; +} + +void SCompiledMethod::hasEnvironment_(bool value) { + if (value) { + _format |= HasEnvironment; + } else { + _format &= ~HasEnvironment; + } +} + +bool SCompiledMethod::hasFrame() const { + return (_format & HasFrame) != 0; +} + +void SCompiledMethod::hasFrame_(bool value) { + if (value) { + _format |= HasFrame; + } else { + _format &= ~HasFrame; + } +} + +bool SCompiledMethod::isDebuggable() const { + return (_format & Debuggable) != 0; +} + +void SCompiledMethod::beDebuggable_() { + _format |= Debuggable; +} + +std::vector SCompiledMethod::blocks() const { + return std::vector(); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/Backend/SCompiledMethod.h b/runtime/cpp/Compiler/Backend/SCompiledMethod.h new file mode 100644 index 00000000..db1641d9 --- /dev/null +++ b/runtime/cpp/Compiler/Backend/SCompiledMethod.h @@ -0,0 +1,127 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _SCOMPILEDMETHOD_H_ +#define _SCOMPILEDMETHOD_H_ +#include +#include +#include +#include "../LiteralValue.h" + +namespace Egg { + +class SCompiledBlock; +class TreecodeEncoder; + +/** + * Compiled method representation + * Corresponds to SCompiledMethod in Smalltalk + * + * In C++, this stores a vector typed literal pool, + * while maintaining the format and other metadata separately. + */ +class SCompiledMethod { +private: + std::vector _literals; // Typed literal pool + uint32_t _format; + void* _optimizedCode; + std::vector _treecodes; + void* _classBinding; + egg::string _selector; + egg::string _source; + + // Format bit layout matching Smalltalk CompiledMethod>>initializeFormatFlags: + // ArgCount: bits 0-5 (6 bits) + // BlockCount: bits 6-12 (7 bits) + // TempCount: bits 13-20 (8 bits) + // CapturesSelf: bit 21 + // HasEnvironment: bit 22 + // HasFrame: bit 23 + // Debuggable: bit 24 + // EnvCount: bits 25-30 (6 bits) + // IsExtension: bit 31 + static constexpr uint32_t ArgCount_Shift = 0; + static constexpr uint32_t ArgCount_Mask = 0x3F; // 6 bits + static constexpr uint32_t BlockCount_Shift = 6; + static constexpr uint32_t BlockCount_Mask = 0x7F; // 7 bits + static constexpr uint32_t TempCount_Shift = 13; + static constexpr uint32_t TempCount_Mask = 0xFF; // 8 bits + static constexpr uint32_t EnvCount_Shift = 25; + static constexpr uint32_t EnvCount_Mask = 0x3F; // 6 bits + + static constexpr uint32_t CapturesSelf = 1 << 21; + static constexpr uint32_t HasEnvironment = 1 << 22; + static constexpr uint32_t HasFrame = 1 << 23; + static constexpr uint32_t Debuggable = 1 << 24; + static constexpr uint32_t IsExtension = 1u << 31; + +public: + SCompiledMethod(std::vector literals = {}); + virtual ~SCompiledMethod(); + + static SCompiledMethod* withAll(const std::vector& literals); + + uint32_t argumentCount() const; + void argumentCount_(uint32_t count); + + uint32_t blockCount() const; + void blockCount_(uint32_t count); + + uint32_t tempCount() const; + void tempCount_(uint32_t count); + + uint32_t environmentCount() const; + void environmentCount_(uint32_t count); + + bool capturesSelf() const; + void capturesSelf_(bool value); + + bool hasEnvironment() const; + void hasEnvironment_(bool value); + + bool hasFrame() const; + void hasFrame_(bool value); + + bool isDebuggable() const; + void beDebuggable_(); + + egg::string selector() const { return _selector; } + void selector_(const egg::string& sel) { _selector = sel; } + + egg::string source() const { return _source; } + void source_(const egg::string& src) { _source = src; } + + void* classBinding() const { return _classBinding; } + void classBinding_(void* binding) { _classBinding = binding; } + + const std::vector& treecodes() const { return _treecodes; } + void treecodes_(const std::vector& codes) { _treecodes = codes; } + + uint32_t format() const { return _format; } + void format_(uint32_t fmt) { _format = fmt; } + + const std::vector& literals() const { return _literals; } + + int literalIndexOf(const LiteralValue& lit) const { + for (size_t i = 0; i < _literals.size(); i++) { + if (_literals[i] == lit) return static_cast(i + 1); // 1-based + } + return -1; + } + + std::vector blocks() const; + + void pragma_(void* pragma) { /* TODO */ } + + void* optimizedCode() const { return _optimizedCode; } + void optimizedCode_(void* code) { _optimizedCode = code; } + +private: + uint32_t getBits_(uint32_t shift, uint32_t mask) const; + void setBits_(uint32_t shift, uint32_t mask, uint32_t value); +}; + +} // namespace Egg + +#endif // _SCOMPILEDMETHOD_H_ diff --git a/runtime/cpp/Compiler/Binding/BlockScope.cpp b/runtime/cpp/Compiler/Binding/BlockScope.cpp new file mode 100644 index 00000000..21fe907b --- /dev/null +++ b/runtime/cpp/Compiler/Binding/BlockScope.cpp @@ -0,0 +1,299 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#include "BlockScope.h" +#include "LocalBinding.h" +#include "../AST/SBlockNode.h" +#include "../AST/SMethodNode.h" +#include +#include + +namespace Egg { + +BlockScope::BlockScope() : ScriptScope() { +} + +Binding* BlockScope::captureArgument_(Binding* anArgumentBinding) { + egg::string name = anArgumentBinding->name(); + auto it = _captured.find(name); + if (it != _captured.end()) { + return it->second; + } + + auto transferred = parent_()->transferLocal_(name); + auto copy = copyLocal_(transferred); + _captured[name] = copy; + return copy; +} + +void BlockScope::captureEnvironment_(SParseNode* aScriptNode) { + if (_script == aScriptNode) return; + + auto it = std::find(_environments.begin(), _environments.end(), aScriptNode); + if (it != _environments.end()) return; + + realParent_()->captureEnvironment_(aScriptNode); + + if (aScriptNode->isMethod()) { + _environments.insert(_environments.begin(), aScriptNode); + } else { + _environments.push_back(aScriptNode); + } +} + +Binding* BlockScope::captureLocal_(Binding* aLocalBinding) { + if (defines_(aLocalBinding->name())) { + return aLocalBinding; + } + + if (aLocalBinding->kind() == Binding::Kind::Temporary) { + return captureTemporary_(aLocalBinding); + } else { + return captureArgument_(aLocalBinding); + } +} + +void BlockScope::captureSelf_() { + if (_captureSelf) return; + _captureSelf = true; + parent_()->captureSelf_(); +} + +Binding* BlockScope::captureTemporary_(Binding* aTemporaryBinding) { + egg::string name = aTemporaryBinding->name(); + if (defines_(name)) { + return aTemporaryBinding; + } + + auto it = _captured.find(name); + if (it != _captured.end()) { + return it->second; + } + + auto parent = parent_(); + auto declaration = parent->scriptDefining_(name); + realScope_()->captureEnvironment_(declaration->realScript()); + + auto transferred = parent->transferLocal_(name); + auto copy = copyLocal_(transferred); + + // In Smalltalk: copy isInArray ifTrue: [aTemporaryBinding beInArray]. + // copyLocal_ for non-inlined blocks always marks the copy as inArray, + // so this effectively always marks the original binding as inArray too. + if (auto localCopy = dynamic_cast(copy)) { + if (localCopy->isInArray()) { + if (auto localTemp = dynamic_cast(aTemporaryBinding)) { + localTemp->beInArray_(); + } + } + } + + _captured[name] = copy; + return copy; +} + +std::vector BlockScope::capturedArguments_() { + std::vector result; + for (auto& pair : _captured) { + if (pair.second->kind() == Binding::Kind::Argument) { + result.push_back(pair.second); + } + } + return result; +} + +int BlockScope::capturedEnvironmentIndexOf_(SScriptNode* aScriptNode) { + auto realScript = aScriptNode->realScript(); + if (realScript == _script->realScript()) { + return -1; // Return special value for nullptr + } + + auto it = std::find(_environments.begin(), _environments.end(), aScriptNode); + assert(it != _environments.end()); + + int index = std::distance(_environments.begin(), it) + 1; + return capturesSelf() ? index + 1 : index; +} + +bool BlockScope::capturesHome_() { + return home_() != nullptr; +} + +Binding* BlockScope::copyLocal_(Binding* binding) { + if (static_cast(_script)->isInlined()) { + return binding; + } else { + auto copy = binding->copy_(); + if (auto localCopy = dynamic_cast(copy)) { + localCopy->beInArray_(); + } + return copy; + } +} + +int* BlockScope::environmentIndexOf_(SScriptNode* aScriptNode) { + if (!aScriptNode->isMethod() && !aScriptNode->isBlock()) { + assert(false); + return nullptr; + } + + int index = capturedEnvironmentIndexOf_(aScriptNode); + if (index < 0) return nullptr; + + static int result; + result = index; + return &result; +} + +int BlockScope::environmentSizeUpToCapturedArguments_() { + return environmentSizeUpToEnvironments_() + capturedArguments_().size(); +} + +int BlockScope::environmentSizeUpToEnvironments_() { + int receiver = capturesSelf() ? 1 : 0; + return receiver + _environments.size(); +} + +std::vector BlockScope::environments_() { + if (_environments.empty()) { + return std::vector(); + } + + auto first = _environments[0]; + if (first->isMethod()) { + return std::vector(_environments.begin() + 1, _environments.end()); + } + return _environments; +} + +SParseNode* BlockScope::home_() { + if (_environments.empty()) { + return nullptr; + } + + auto first = _environments[0]; + return first->isMethod() ? first : nullptr; +} + +std::vector BlockScope::localBindings_() { + auto result = ScriptScope::localBindings_(); + for (auto& pair : _captured) { + result.push_back(pair.second); + } + return result; +} + +ScriptScope* BlockScope::parent_() { + if (!_script) return nullptr; + auto blockNode = static_cast(_script); + auto parent = blockNode->parent(); + return parent ? parent->scope() : nullptr; +} + +void BlockScope::positionCapturedArgument_(Binding* anArgumentBinding) { + if (auto localBinding = dynamic_cast(anArgumentBinding)) { + localBinding->index_(growEnvironment_()); + } +} + +void BlockScope::positionCapturedLocals_() { + if (static_cast(_script)->isInlined()) { + return; + } + + _envSize = environmentSizeUpToEnvironments_(); + for (auto& pair : _captured) { + auto binding = pair.second; + if (binding->kind() == Binding::Kind::Argument) { + positionCapturedArgument_(binding); + } else { + positionCapturedTemporary_(binding); + } + } +} + +void BlockScope::positionCapturedTemporary_(Binding* aTemporaryBinding) { + auto outest = scriptDefining_(aTemporaryBinding->name()); + int index = capturedEnvironmentIndexOf_(outest->realScript()); + + if (auto localTemp = dynamic_cast(aTemporaryBinding)) { + localTemp->environmentIndex_(index); + + auto declaration = outest->scope()->resolve_(aTemporaryBinding->name()); + if (auto localDecl = dynamic_cast(declaration)) { + assert(localDecl->index() >= 0); + localTemp->index_(localDecl->index()); + } + } +} + +void BlockScope::positionDefinedArgumentsIn_(ScriptScope* aScriptScope) { + for (auto& pair : _arguments) { + auto binding = pair.second; + if (auto localBinding = dynamic_cast(binding)) { + int index = localBinding->isInArray() ? + aScriptScope->growEnvironment_() : + aScriptScope->growStack_(); + localBinding->index_(index); + } + } +} + +void BlockScope::positionDefinedLocals_() { + auto blockNode = static_cast(_script); + if (blockNode->isInlined()) { + auto real = realScope_(); + positionDefinedTemporariesIn_(real); + positionDefinedArgumentsIn_(real); + } else { + ScriptScope::positionDefinedLocals_(); + } +} + +void BlockScope::positionLocals_() { + positionCapturedLocals_(); + ScriptScope::positionLocals_(); +} + +ScriptScope* BlockScope::realParent_() { + if (!_script) return nullptr; + auto blockNode = static_cast(_script); + auto parent = blockNode->parent(); + if (!parent) return nullptr; + auto realParent = parent->realScript(); + return realParent ? realParent->scope() : nullptr; +} + +Binding* BlockScope::resolve_(const egg::string& aString) { + auto local = resolveLocal_(aString); + if (local) return local; + return parent_()->resolve_(aString); +} + +Binding* BlockScope::resolveLocal_(const egg::string& aString) { + auto local = ScriptScope::resolveLocal_(aString); + if (local) return local; + + auto it = _captured.find(aString); + return (it != _captured.end()) ? it->second : nullptr; +} + +SScriptNode* BlockScope::scriptDefining_(const egg::string& aString) { + if (defines_(aString)) { + return _script; + } + return parent_()->scriptDefining_(aString); +} + +Binding* BlockScope::transferLocal_(const egg::string& name) { + auto binding = resolveLocal_(name); + if (binding) return binding; + + binding = parent_()->transferLocal_(name); + auto copy = copyLocal_(binding); + _captured[name] = copy; + return copy; +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/Binding/BlockScope.h b/runtime/cpp/Compiler/Binding/BlockScope.h new file mode 100644 index 00000000..5fdce70d --- /dev/null +++ b/runtime/cpp/Compiler/Binding/BlockScope.h @@ -0,0 +1,60 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _BLOCKSCOPE_H_ +#define _BLOCKSCOPE_H_ +#include "ScriptScope.h" +#include +#include + +namespace Egg { + +/** + * Scope for block nodes + * Corresponds to BlockScope in Smalltalk + */ +class BlockScope : public ScriptScope { +private: + std::vector _environments; + std::map _captured; + + Binding* captureArgument_(Binding* anArgumentBinding); + Binding* captureTemporary_(Binding* aTemporaryBinding); + Binding* copyLocal_(Binding* binding); + int capturedEnvironmentIndexOf_(SScriptNode* aScriptNode); + void positionCapturedLocals_(); + void positionCapturedArgument_(Binding* anArgumentBinding); + void positionCapturedTemporary_(Binding* aTemporaryBinding); + void positionDefinedArgumentsIn_(ScriptScope* aScriptScope); + int environmentSizeUpToEnvironments_(); + int environmentSizeUpToCapturedArguments_(); + ScriptScope* parent_(); + ScriptScope* realParent_(); + +public: + BlockScope(); + virtual ~BlockScope() {} + + std::vector capturedArguments_(); + std::vector capturedEnvironments_() { return _environments; } + bool capturesHome_(); + std::vector environments_(); + SParseNode* home_(); + + void captureEnvironment_(SParseNode* aScriptNode) override; + Binding* captureLocal_(Binding* aLocalBinding) override; + void captureSelf_() override; + int* environmentIndexOf_(SScriptNode* aScriptNode) override; + Binding* resolve_(const egg::string& aString) override; + Binding* resolveLocal_(const egg::string& aString); + SScriptNode* scriptDefining_(const egg::string& aString) override; + Binding* transferLocal_(const egg::string& name) override; + std::vector localBindings_() override; + void positionDefinedLocals_() override; + void positionLocals_() override; +}; + +} // namespace Egg + +#endif // _BLOCKSCOPE_H_ diff --git a/runtime/cpp/Compiler/Binding/MethodScope.cpp b/runtime/cpp/Compiler/Binding/MethodScope.cpp new file mode 100644 index 00000000..dd96745b --- /dev/null +++ b/runtime/cpp/Compiler/Binding/MethodScope.cpp @@ -0,0 +1,69 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#include "MethodScope.h" +#include "PseudoVariableBindings.h" +#include "../AST/SScriptNode.h" +#include + +namespace Egg { + +MethodScope::MethodScope() : ScriptScope() { + initializePseudoVars_(); +} + +void MethodScope::initializePseudoVars_() { + _pseudo["nil"] = new NilBinding(); + _pseudo["true"] = new TrueBinding(); + _pseudo["false"] = new FalseBinding(); + _pseudo["self"] = new SelfBinding(); + _pseudo["super"] = new SuperBinding(); +} + +void MethodScope::captureEnvironment_(SParseNode* aScriptNode) { + assert(aScriptNode == _script); +} + +Binding* MethodScope::captureLocal_(Binding* aLocalBinding) { + assert(resolveLocal_(aLocalBinding->name()) != nullptr); + return aLocalBinding; +} + +void MethodScope::captureSelf_() { + _captureSelf = true; +} + +int* MethodScope::environmentIndexOf_(SScriptNode* aScriptNode) { + assert(aScriptNode == _script); + return nullptr; +} + +Binding* MethodScope::resolve_(const egg::string& aString) { + auto local = resolveLocal_(aString); + if (local) return local; + + auto pseudo = resolvePseudo_(aString); + if (pseudo) return pseudo; + + return DynamicBinding::named_(aString); +} + +Binding* MethodScope::resolvePseudo_(const egg::string& aString) { + auto it = _pseudo.find(aString); + return (it != _pseudo.end()) ? it->second : nullptr; +} + +SScriptNode* MethodScope::scriptDefining_(const egg::string& aString) { + if (resolveLocal_(aString)) { + return _script; + } + assert(false); + return nullptr; +} + +Binding* MethodScope::transferLocal_(const egg::string& name) { + return resolveLocal_(name); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/Binding/MethodScope.h b/runtime/cpp/Compiler/Binding/MethodScope.h new file mode 100644 index 00000000..da5eb0da --- /dev/null +++ b/runtime/cpp/Compiler/Binding/MethodScope.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _METHODSCOPE_H_ +#define _METHODSCOPE_H_ +#include "ScriptScope.h" +#include +#include + +namespace Egg { + +class NilBinding; +class TrueBinding; +class FalseBinding; +class SelfBinding; +class SuperBinding; +class DynamicBinding; + +/** + * Scope for method nodes + * Corresponds to MethodScope in Smalltalk + */ +class MethodScope : public ScriptScope { +private: + std::map _pseudo; + + void initializePseudoVars_(); + Binding* resolvePseudo_(const egg::string& aString); + +public: + MethodScope(); + virtual ~MethodScope() {} + + void captureEnvironment_(SParseNode* aScriptNode) override; + Binding* captureLocal_(Binding* aLocalBinding) override; + void captureSelf_() override; + int* environmentIndexOf_(SScriptNode* aScriptNode) override; + Binding* resolve_(const egg::string& aString) override; + SScriptNode* scriptDefining_(const egg::string& aString) override; + Binding* transferLocal_(const egg::string& name) override; +}; + +} // namespace Egg + +#endif // _METHODSCOPE_H_ diff --git a/runtime/cpp/Compiler/Binding/Scope.h b/runtime/cpp/Compiler/Binding/Scope.h new file mode 100644 index 00000000..9cbeedca --- /dev/null +++ b/runtime/cpp/Compiler/Binding/Scope.h @@ -0,0 +1,31 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _SCOPE_H_ +#define _SCOPE_H_ +#include "Binding.h" +#include "ArgumentBinding.h" +#include "TemporaryBinding.h" +#include + +namespace Egg { + +class SParseNode; + +class Scope { +public: + Scope() {} + virtual ~Scope() {} + void addBinding_(Binding* binding) { _bindings.push_back(binding); } + const std::vector& bindings() const { return _bindings; } + virtual Binding* defineArgument_(const egg::string& name) { auto b = new ArgumentBinding(name, 0); addBinding_(b); return b; } + virtual Binding* defineTemporary_(const egg::string& name) { auto b = new TemporaryBinding(name, 0); addBinding_(b); return b; } + virtual void captureEnvironment_(SParseNode* aScriptNode) { /* Default: do nothing */ } +private: + std::vector _bindings; +}; + +} // namespace Egg + +#endif // _SCOPE_H_ diff --git a/runtime/cpp/Compiler/Binding/ScriptScope.cpp b/runtime/cpp/Compiler/Binding/ScriptScope.cpp new file mode 100644 index 00000000..c6503360 --- /dev/null +++ b/runtime/cpp/Compiler/Binding/ScriptScope.cpp @@ -0,0 +1,141 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#include "ScriptScope.h" +#include "ArgumentBinding.h" +#include "TemporaryBinding.h" +#include "../AST/SScriptNode.h" +#include "../SSmalltalkCompiler.h" +#include "../CompilationError.h" +#include "../LiteralValue.h" + +namespace Egg { + +ScriptScope::ScriptScope() + : Scope(), _script(nullptr), _stackSize(0), _envSize(0), _captureSelf(false) { +} + +Binding* ScriptScope::defineArgument_(const egg::string& identifier) { + if (resolves_(identifier)) { + redefinitionError_(identifier); + } + auto binding = new ArgumentBinding(identifier, 0); + _arguments[identifier] = binding; + _argumentOrder.push_back(identifier); + addBinding_(binding); + return binding; +} + +Binding* ScriptScope::defineTemporary_(const egg::string& identifier) { + if (_temporaries.find(identifier) != _temporaries.end()) { + redefinitionError_(identifier); + } + auto binding = new TemporaryBinding(identifier, 0); + _temporaries[identifier] = binding; + addBinding_(binding); + return binding; +} + +bool ScriptScope::defines_(const egg::string& aString) { + return _temporaries.find(aString) != _temporaries.end() || + _arguments.find(aString) != _arguments.end(); +} + +Binding* ScriptScope::resolveLocal_(const egg::string& aString) { + auto it = _temporaries.find(aString); + if (it != _temporaries.end()) { + return it->second; + } + auto ait = _arguments.find(aString); + if (ait != _arguments.end()) { + return ait->second; + } + return nullptr; +} + +bool ScriptScope::resolves_(const egg::string& aString) { + auto binding = resolve_(aString); + return binding && !binding->isDynamic(); +} + +std::vector ScriptScope::localBindings_() { + std::vector result; + for (auto& pair : _arguments) { + result.push_back(pair.second); + } + for (auto& pair : _temporaries) { + result.push_back(pair.second); + } + return result; +} + +void ScriptScope::positionLocals_() { + positionDefinedLocals_(); +} + +void ScriptScope::positionDefinedLocals_() { + positionDefinedTemporariesIn_(this); + positionDefinedArguments_(); +} + +void ScriptScope::positionDefinedTemporariesIn_(ScriptScope* aScriptScope) { + for (auto& pair : _temporaries) { + auto binding = pair.second; + if (auto localBinding = dynamic_cast(binding)) { + bool inStack = localBinding->isInStack(); + int position = inStack ? + aScriptScope->growStack_() : + aScriptScope->growEnvironment_(); + localBinding->index_(position); + } + } +} + +void ScriptScope::positionDefinedArguments_() { + int index = 1; + for (auto& name : _argumentOrder) { + auto it = _arguments.find(name); + if (it != _arguments.end()) { + if (auto localBinding = dynamic_cast(it->second)) { + localBinding->index_(index++); + } + } + } +} + +void ScriptScope::captureEnvironment_(SParseNode* aScriptNode) { +} + +Binding* ScriptScope::captureLocal_(Binding* aLocalBinding) { + return aLocalBinding; +} + +void ScriptScope::captureSelf_() { + _captureSelf = true; +} + +int* ScriptScope::environmentIndexOf_(SScriptNode* aScriptNode) { + return nullptr; +} + +Binding* ScriptScope::transferLocal_(const egg::string& name) { + return resolveLocal_(name); +} + +ScriptScope* ScriptScope::realScope_() { + if (!_script) return nullptr; + auto realScript = _script->realScript(); + return realScript ? realScript->scope() : nullptr; +} + +void ScriptScope::redefinitionError_(const egg::string& name) { + if (_script && _script->compiler()) { + _script->compiler()->warning_at_( + name.toUtf8() + " already declared", + nullptr + ); + } +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/Binding/ScriptScope.h b/runtime/cpp/Compiler/Binding/ScriptScope.h new file mode 100644 index 00000000..ec52622d --- /dev/null +++ b/runtime/cpp/Compiler/Binding/ScriptScope.h @@ -0,0 +1,71 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _SCRIPTSCOPE_H_ +#define _SCRIPTSCOPE_H_ +#include "Scope.h" +#include +#include +#include + +namespace Egg { + +class SScriptNode; +class SIdentifierNode; +class ArgumentBinding; +class TemporaryBinding; + +/** + * Scope for script nodes (methods and blocks) + * Corresponds to ScriptScope in Smalltalk + */ +class ScriptScope : public Scope { +protected: + SScriptNode* _script; + std::map _arguments; + std::vector _argumentOrder; + std::map _temporaries; + int _stackSize; + int _envSize; + bool _captureSelf; + + void redefinitionError_(const egg::string& name); + +public: + ScriptScope(); + virtual ~ScriptScope() {} + + void script_(SScriptNode* aScriptNode) { _script = aScriptNode; } + + bool capturesSelf() const { return _captureSelf; } + int environmentSize() const { return _envSize; } + int stackSize() const { return _stackSize; } + int growEnvironment_() { return ++_envSize; } + int growStack_() { return ++_stackSize; } + + Binding* defineArgument_(const egg::string& identifier) override; + Binding* defineTemporary_(const egg::string& identifier) override; + bool defines_(const egg::string& aString); + Binding* resolveLocal_(const egg::string& aString); + bool resolves_(const egg::string& aString); + virtual std::vector localBindings_(); + + virtual void positionLocals_(); + virtual void positionDefinedLocals_(); + void positionDefinedTemporariesIn_(ScriptScope* aScriptScope); + void positionDefinedArguments_(); + + void captureEnvironment_(SParseNode* aScriptNode) override; + virtual Binding* captureLocal_(Binding* aLocalBinding); + virtual void captureSelf_(); + virtual int* environmentIndexOf_(SScriptNode* aScriptNode); + virtual Binding* resolve_(const egg::string& aString) = 0; + virtual SScriptNode* scriptDefining_(const egg::string& aString) = 0; + virtual Binding* transferLocal_(const egg::string& name); + virtual ScriptScope* realScope_(); +}; + +} // namespace Egg + +#endif // _SCRIPTSCOPE_H_ diff --git a/runtime/cpp/Compiler/CompilationError.cpp b/runtime/cpp/Compiler/CompilationError.cpp new file mode 100644 index 00000000..3f2142bd --- /dev/null +++ b/runtime/cpp/Compiler/CompilationError.cpp @@ -0,0 +1,54 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "CompilationError.h" +#include "SSmalltalkCompiler.h" +#include "CompilationResult.h" + +namespace Egg { + +CompilationError::CompilationError(const egg::string& desc) + : std::runtime_error(desc.toUtf8()), _compiler(nullptr), _resumable(false), _retryable(false), _stretch(nullptr), _description(desc) { +} + +void CompilationError::beFatal() { + _resumable = false; + _retryable = false; +} + +void CompilationError::beResumable() { + _resumable = true; +} + +void CompilationError::beWarning() { + _resumable = true; +} + +void CompilationError::compiler_(SSmalltalkCompiler* aSCompiler) { + _compiler = aSCompiler; + if (_compiler) { + _compiler->result()->error_(this); + } +} + +void CompilationError::proceed() { + _retryable = false; + if (_compiler) { + _compiler->result()->beSuccessful(); + } +} + +egg::string CompilationError::source() { + if (!_compiler || !_stretch) return ""; + egg::string sourceCode = _compiler->sourceCode(); + int start = _stretch->start(); + int end = _stretch->end(); + if (start < 0 || end > static_cast(sourceCode.length()) || start > end) { + return ""; + } + return sourceCode.substr(start, end - start); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/CompilationError.h b/runtime/cpp/Compiler/CompilationError.h new file mode 100644 index 00000000..344c9579 --- /dev/null +++ b/runtime/cpp/Compiler/CompilationError.h @@ -0,0 +1,52 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _COMPILATION_ERROR_H_ +#define _COMPILATION_ERROR_H_ + +#include +#include +#include "Stretch.h" +#include "egg_string.h" + +namespace Egg { + +class SSmalltalkCompiler; + +/** + * Compilation error + * Corresponds to SCompilationError in Smalltalk + */ +class CompilationError : public std::runtime_error { +private: + SSmalltalkCompiler* _compiler; + bool _resumable; + bool _retryable; + Stretch* _stretch; + egg::string _description; + +public: + CompilationError(const egg::string& desc = ""); + virtual ~CompilationError() {} + + void beFatal(); + void beResumable(); + void beWarning(); + SSmalltalkCompiler* compiler() { return _compiler; } + void compiler_(SSmalltalkCompiler* aSCompiler); + void description_(const egg::string& aString) { _description = aString; } + egg::string description() const { return _description; } + bool isResumable() const { return _resumable; } + bool isUndeclaredAccess() const { return false; } + bool isUndeclaredAssignment() const { return false; } + void proceed(); + egg::string source(); + Stretch* stretch() { return _stretch; } + void stretch_(Stretch* aStretch) { _stretch = aStretch; } +}; + +} // namespace Egg + +#endif // _COMPILATION_ERROR_H_ diff --git a/runtime/cpp/Compiler/CompilationResult.h b/runtime/cpp/Compiler/CompilationResult.h new file mode 100644 index 00000000..63368561 --- /dev/null +++ b/runtime/cpp/Compiler/CompilationResult.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _COMPILATION_RESULT_H_ +#define _COMPILATION_RESULT_H_ + +namespace Egg { + +class SSmalltalkCompiler; +class CompilationError; +class SParseNode; + +/** + * Compilation result + * Corresponds to SCompilationResult in Smalltalk + */ +class CompilationResult { +private: + SSmalltalkCompiler* _compiler; + CompilationError* _error; + SParseNode* _ast; + void* _method; // CompiledMethod placeholder + +public: + CompilationResult() : _compiler(nullptr), _error(nullptr), _ast(nullptr), _method(nullptr) {} + virtual ~CompilationResult() {} + + SParseNode* ast() { return _ast; } + void ast_(SParseNode* aParseNode) { _ast = aParseNode; } + void beSuccessful() { _error = nullptr; } + void compiler_(SSmalltalkCompiler* aSSmalltalkCompiler) { _compiler = aSSmalltalkCompiler; } + CompilationError* error() { return _error; } + void error_(CompilationError* aCompilationError) { _error = aCompilationError; } + void* method() { return _method; } + void method_(void* aCompiledMethod) { _method = aCompiledMethod; } +}; + +} // namespace Egg + +#endif // _COMPILATION_RESULT_H_ diff --git a/runtime/cpp/Compiler/Parser/SSmalltalkParser.cpp b/runtime/cpp/Compiler/Parser/SSmalltalkParser.cpp new file mode 100644 index 00000000..23ab0e4c --- /dev/null +++ b/runtime/cpp/Compiler/Parser/SSmalltalkParser.cpp @@ -0,0 +1,846 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SSmalltalkParser.h" +#include "../SSmalltalkCompiler.h" +#include "../LiteralValue.h" +#include "SSmalltalkScanner.h" +#include +#include +#include +#include + +namespace Egg { + +SSmalltalkParser::SSmalltalkParser(SSmalltalkCompiler* compiler) + : _compiler(compiler), _scanner(compiler->scanner()) { +} + +SSmalltalkParser::~SSmalltalkParser() { +} + +SMethodNode* SSmalltalkParser::parseMethod_() { + return method_(); +} + +SMethodNode* SSmalltalkParser::parseExpression_() { + return headlessMethod_(); +} + +SToken* SSmalltalkParser::next_() { + if (_next) { + _token = std::move(_next); + _next.reset(); + } else { + _token.reset(_scanner->nextToken().release()); + } + return _token.get(); +} + +SToken* SSmalltalkParser::peek_() { + if (_next) { + return _next.get(); + } + + _next.reset(_scanner->nextToken().release()); + std::vector comments; + while (_next && _next->isComment()) { + comments.push_back(_next->value()); + _next.reset(_scanner->nextToken().release()); + } + + if (_next && !comments.empty()) { + for (auto& comment : comments) { + _next->addComment_(comment); + } + } + + return _next.get(); +} + +SToken* SSmalltalkParser::step_() { + SToken* save = _token.get(); + next_(); + std::vector comments; + while (_token && _token->isComment()) { + comments.push_back(_token->value()); + next_(); + } + + if (_token && !comments.empty()) { + for (auto& comment : comments) { + _token->addComment_(comment); + } + } + + return save; +} + +void SSmalltalkParser::skipDots_() { + while (_token && _token->is('.')) step_(); +} + +void SSmalltalkParser::error_(const std::string& message) { + error_(message, _token ? _token->position().start() : 0); +} + +void SSmalltalkParser::error_(const std::string& message, uint32_t position) { + std::stringstream ss; + ss << "Parse error at position " << position << ": " << message; + throw std::runtime_error(ss.str()); +} + +void SSmalltalkParser::missingToken_(const std::string& expected) { + error_("missing " + expected); +} + +void SSmalltalkParser::missingExpression_() { + error_("missing expression"); +} + +void SSmalltalkParser::missingArgument_() { + error_("argument missing"); +} + +SMethodNode* SSmalltalkParser::method_() { + step_(); + SMethodNode* method = methodSignature_(); + if (!method) { + return nullptr; + } + addBodyTo_(method); + return method; +} + +SMethodNode* SSmalltalkParser::headlessMethod_() { + step_(); + SMethodNode* method = new SMethodNode(_compiler); + _compiler->activeScript_(method); + addBodyTo_(method); + return method; +} + +SMethodNode* SSmalltalkParser::methodSignature_() { + SMethodNode* method = keywordSignature_(); + if (method) return method; + + method = binarySignature_(); + if (method) return method; + + method = unarySignature_(); + if (method) return method; + + error_("method signature expected"); + return nullptr; +} + +SMethodNode* SSmalltalkParser::unarySignature_() { + if (!hasUnarySelector_()) { + return nullptr; + } + + SSelectorNode* selectorNode = new SSelectorNode(_compiler); + selectorNode->symbol_(_token->value()); + selectorNode->position_(_token->position()); + + step_(); + + std::vector emptyArgs; + return buildMethodNode_(selectorNode, emptyArgs); +} + +SMethodNode* SSmalltalkParser::binarySignature_() { + if (!hasBinarySelector_()) { + return nullptr; + } + SSelectorNode* selectorNode = new SSelectorNode(_compiler); + selectorNode->symbol_(_token->value()); + selectorNode->position_(_token->position()); + + step_(); + + if (!_token || !_token->isName()) { + missingArgument_(); + } + + SIdentifierNode* arg = new SIdentifierNode(_compiler); + arg->name_(_token->value()); + arg->position_(_token->position()); + + step_(); + + std::vector args; + args.push_back(arg); + + return buildMethodNode_(selectorNode, args); +} + +SMethodNode* SSmalltalkParser::keywordSignature_() { + if (!hasKeywordSelector_()) { + return nullptr; + } + + egg::string selector; + std::vector arguments; + uint32_t start = _token->position().start(); + + while (_token && _token->isKeyword()) { + selector += _token->value(); + step_(); + + if (!_token || !_token->isName()) { + missingArgument_(); + } + + SIdentifierNode* arg = new SIdentifierNode(_compiler); + arg->name_(_token->value()); + arg->position_(_token->position()); + arguments.push_back(arg); + + step_(); + } + + if (arguments.empty()) { + return nullptr; + } + SSelectorNode* selectorNode = new SSelectorNode(_compiler); + selectorNode->symbol_(selector); + selectorNode->position_(Stretch(start, _token->position().start() - 1)); + + return buildMethodNode_(selectorNode, arguments); +} + +void SSmalltalkParser::addBodyTo_(SMethodNode* method) { + addTemporariesTo_(method); + addPragmaTo_(method); + addStatementsTo_(method); +} + +void SSmalltalkParser::addTemporariesTo_(SMethodNode* method) { + method->temporaries_(temporaries_()); +} + +void SSmalltalkParser::addStatementsTo_(SMethodNode* method) { + method->position_(_token->position()); + auto stmts = statements_(); + for (auto stmt : stmts) method->addStatement_(stmt); + method->position_(Stretch(method->position().start(), _token->position().start())); + if (_token && !_token->isEnd()) { + error_("unexpected statement", _token->position().start()); + } +} + +std::vector SSmalltalkParser::temporaries_() { + std::vector temps; + if (!_token) return temps; + if (_token->is("||")) { + step_(); + return temps; + } + if (!_token->isBar()) { + return temps; + } + while (true) { + step_(); + if (!_token || !_token->isName()) break; + SIdentifierNode* temp = new SIdentifierNode(_compiler); + temp->name_(_token->value()); + temp->position_(_token->position()); + temps.push_back(temp); + } + if (!_token || !_token->isBar()) { + missingToken_("|"); + } + step_(); + + return temps; +} + +std::vector SSmalltalkParser::statements_() { + std::vector stmts; + while (_token && !_token->endsExpression()) { + stmts.push_back(statement_()); + if (_token && _token->is('.')) skipDots_(); else break; + } + return stmts; +} + +SParseNode* SSmalltalkParser::statement_() { + if (_token && _token->is('^')) return return_(); + SParseNode* expr = expression_(); + return expr; +} + +SReturnNode* SSmalltalkParser::return_() { + uint32_t returnPos = _token->position().start(); + step_(); + auto expr = expression_(); + if (!expr) missingExpression_(); + uint32_t end = _token->position().start(); + skipDots_(); + auto node = buildNode_(returnPos); + node->expression_(expr); + node->position_(Stretch(returnPos, end)); + return node; +} + +SParseNode* SSmalltalkParser::expression_() { + if (_token && _token->isName() && peek_() && peek_()->isAssignment()) { + return assignment_(); + } + + SParseNode* prim = primary_(); + if (!prim) { + missingExpression_(); + } + + SParseNode* expr = unarySequence_(prim); + expr = binarySequence_(expr); + expr = keywordSequence_(expr); + if (expr != prim && expr->isMessage()) { + expr = cascadeSequence_(static_cast(expr)); + } + + if (_token && !_token->endsExpression()) { + std::string desc = _token->isEnd() + ? "unexpected end" + : "unexpected token: " + _token->value().toUtf8(); + error_(desc, _token->position().start()); + } + + return expr; +} + +SAssignmentNode* SSmalltalkParser::assignment_() { + uint32_t position = _token->position().start(); + auto variable = new SIdentifierNode(_compiler); + variable->name_(_token->value()); + variable->position_(_token->position()); + step_(); step_(); + auto expr = expression_(); + if (!expr) missingExpression_(); + auto assignment = buildNode_(position); + assignment->assign_operator_(variable, nullptr); + assignment->expression_(expr); + return assignment; +} + +SParseNode* SSmalltalkParser::primary_() { + if (!_token) return nullptr; + if (_token->isName()) { + SIdentifierNode* id = new SIdentifierNode(_compiler); + id->name_(_token->value()); + id->position_(_token->position()); + step_(); + return id; + } + if (_token->isLiteral()) { + SLiteralNode* lit = new SLiteralNode(_compiler); + auto* strTok = static_cast(_token.get()); + switch (strTok->literalKind()) { + case SStringToken::LitNumber: { + std::string v = _token->value().toUtf8(); + if (v.find('.') != std::string::npos || v.find('e') != std::string::npos || v.find('E') != std::string::npos) { + lit->literalValue_(LiteralValue::fromFloat(std::stod(v))); + } else { + lit->literalValue_(LiteralValue::fromInteger(std::stoll(v, nullptr, 0))); + } + break; + } + case SStringToken::LitCharacter: + lit->literalValue_(LiteralValue::fromCharacter(_token->value()[0])); + break; + case SStringToken::LitSymbol: + lit->literalValue_(LiteralValue::fromSymbol(_token->value())); + break; + case SStringToken::LitString: + default: + lit->literalValue_(LiteralValue::fromString(_token->value())); + break; + } + lit->position_(_token->position()); + step_(); + return lit; + } + if (_token->is('[')) return block_(); + if (_token->is('(')) return parenthesizedExpression_(); + if (_token->is("#(")) return literalArray_(); + if (_token->is("#[")) return literalByteArray_(); + if (_token->is('{')) return bracedArray_(); + if (_token->is('-')) { + auto peekToken = peek_(); + if (peekToken && peekToken->isLiteral()) { + step_(); + SLiteralNode* lit = new SLiteralNode(_compiler); + auto* strTok = static_cast(_token.get()); + std::string v = _token->value().toUtf8(); + if (strTok->literalKind() == SStringToken::LitNumber) { + if (v.find('.') != std::string::npos || v.find('e') != std::string::npos || v.find('E') != std::string::npos) { + lit->literalValue_(LiteralValue::fromFloat(-std::stod(v))); + } else { + lit->literalValue_(LiteralValue::fromInteger(-std::stoll(v, nullptr, 0))); + } + } else { + lit->literalValue_(LiteralValue::fromString("-" + _token->value())); + } + lit->position_(Stretch(_token->position().start() - 1, _token->position().end())); + step_(); + return lit; + } + return nullptr; + } + return nullptr; +} + +SBlockNode* SSmalltalkParser::block_() { + uint32_t position = _token->position().start(); + SBlockNode* block = new SBlockNode(_compiler); + block->position_(Stretch(position, _token->position().start())); + block->parent_(_compiler->activeScript()); + _compiler->activate_while_(block, [&]() { + step_(); + block->arguments_(blockArguments_()); + block->temporaries_(temporaries_()); + auto stmts = statements_(); + for (auto stmt : stmts) block->addStatement_(stmt); + if (!_token || !_token->is(']')) { + missingToken_("]"); + } + block->position_(Stretch(position, _token->position().end())); + step_(); + }); + return block; +} + +std::vector SSmalltalkParser::blockArguments_() { + std::vector args; + + if (!_token || !_token->is(':')) { + return args; + } + + while (_token && _token->is(':')) { + step_(); + + if (!_token || !_token->isName()) { + missingArgument_(); + } + + SIdentifierNode* arg = new SIdentifierNode(_compiler); + arg->name_(_token->value()); + arg->position_(_token->position()); + args.push_back(arg); + + step_(); + } + if (_token && _token->isBar()) { + step_(); + } else if (_token && _token->is("||")) { + step_(); // consume || as closing | for args + empty temps + } else { + missingToken_("|"); + } + + return args; +} + +SParseNode* SSmalltalkParser::parenthesizedExpression_() { + uint32_t start = _token->position().start(); + step_(); + auto expr = expression_(); + if (!expr) missingExpression_(); + if (!_token || !_token->is(')')) missingToken_(")"); + uint32_t end = _token->position().end(); + step_(); + if (!expr->isImmediate()) expr->position_(Stretch(start, end)); + return expr; +} + +SParseNode* SSmalltalkParser::unarySequence_(SParseNode* receiver) { + auto node = receiver; + while (hasUnarySelector_()) { + auto msg = buildMessageNode_(node); + unaryMessage_(msg); + node = msg; + } + return node; +} + +void SSmalltalkParser::unaryMessage_(SMessageNode* message) { + auto selectorNode = new SSelectorNode(_compiler); + selectorNode->symbol_(_token->value()); + selectorNode->position_(_token->position()); + step_(); + message->selector_(selectorNode); + message->position_(Stretch(message->position().start(), selectorNode->position().end())); +} + +SParseNode* SSmalltalkParser::binarySequence_(SParseNode* receiver) { + auto node = receiver; + while (hasBinarySelector_()) { + auto msg = buildMessageNode_(node); + binaryMessage_(msg); + node = msg; + } + return node; +} + +void SSmalltalkParser::binaryMessage_(SMessageNode* message) { + auto selectorNode = new SSelectorNode(_compiler); + selectorNode->symbol_(_token->value()); + selectorNode->position_(_token->position()); + step_(); + auto prim = primary_(); + if (!prim) error_("primary missing"); + auto arg = unarySequence_(prim); + message->selector_(selectorNode); + message->addArgument_(arg); + message->position_(Stretch(message->position().start(), arg->position().end())); +} + +SParseNode* SSmalltalkParser::keywordSequence_(SParseNode* receiver) { + if (!hasKeywordSelector_()) return receiver; + auto message = buildMessageNode_(receiver); + keywordMessage_(message); + return message; +} + +void SSmalltalkParser::keywordMessage_(SMessageNode* message) { + egg::string selector; + std::vector arguments; + uint32_t start = _token->position().start(); + while (_token && _token->isKeyword()) { + selector += _token->value(); + step_(); + auto prim = primary_(); + if (!prim) missingArgument_(); + auto arg = unarySequence_(prim); + arg = binarySequence_(arg); + arguments.push_back(arg); + } + auto selectorNode = new SSelectorNode(_compiler); + selectorNode->symbol_(selector); + selectorNode->position_(Stretch(start, _token->position().start() - 1)); + message->selector_(selectorNode); + message->arguments_(arguments); + if (!arguments.empty()) message->position_(Stretch(message->position().start(), arguments.back()->position().end())); +} + +SParseNode* SSmalltalkParser::cascadeSequence_(SMessageNode* messageNode) { + if (!_token || !_token->is(';')) return messageNode; + auto cascade = new SCascadeNode(_compiler); + cascade->position_(messageNode->position()); + auto receiver = messageNode->receiver(); + cascade->receiver_(receiver); + auto firstMsg = new SCascadeMessageNode(_compiler); + firstMsg->receiver_(receiver); + firstMsg->selector_(messageNode->selector()); + firstMsg->arguments_(messageNode->arguments()); + firstMsg->position_(messageNode->position()); + firstMsg->cascade_(cascade); + cascade->addMessage_(firstMsg); + while (_token && _token->is(';')) { + step_(); + auto msg = buildCascadeMessageNode_(receiver); + msg->cascade_(cascade); + msg->position_(_token->position()); + cascadeMessage_(msg); + cascade->addMessage_(msg); + } + const auto& messages = cascade->messages(); + if (!messages.empty()) cascade->position_(Stretch(cascade->position().start(), messages.back()->position().end())); + return cascade; +} + +void SSmalltalkParser::cascadeMessage_(SMessageNode* message) { + if (hasUnarySelector_()) unaryMessage_(message); + else if (hasBinarySelector_()) binaryMessage_(message); + else if (hasKeywordSelector_()) keywordMessage_(message); + else error_("invalid cascade message"); +} + +bool SSmalltalkParser::hasUnarySelector_() const { + return _token && _token->isName(); +} + +bool SSmalltalkParser::hasBinarySelector_() const { + if (!_token) return false; + // ST: (token isStringToken and: [token hasSymbol]) or: [token is: $^] or: [token is: $:] + if (_token->isSymbolic() && _token->hasSymbol()) return true; + if (_token->is('^')) return true; + if (_token->is(':')) return true; + return false; +} + +bool SSmalltalkParser::hasKeywordSelector_() const { + return _token && _token->isKeyword(); +} + +SParseNode* SSmalltalkParser::literalArray_() { + uint32_t position = _token->position().start(); + step_(); + std::vector elements; + while (_token && !_token->is(')') && !_token->isEnd()) { + if (_token->isLiteral()) { + auto* strTok = static_cast(_token.get()); + switch (strTok->literalKind()) { + case SStringToken::LitNumber: { + std::string v = _token->value().toUtf8(); + if (v.find('.') != std::string::npos) { + elements.push_back(LiteralValue::fromFloat(std::stod(v))); + } else { + elements.push_back(LiteralValue::fromInteger(std::stoll(v, nullptr, 0))); + } + break; + } + case SStringToken::LitCharacter: + elements.push_back(LiteralValue::fromCharacter(_token->value()[0])); + break; + case SStringToken::LitSymbol: + elements.push_back(LiteralValue::fromSymbol(_token->value())); + break; + case SStringToken::LitString: + default: + elements.push_back(LiteralValue::fromString(_token->value())); + break; + } + } else if (_token->isName()) { + // pseudoLiteralValue: convert nil/true/false to actual values + egg::string val = _token->value(); + if (val == "nil") { + elements.push_back(LiteralValue::nil()); + } else if (val == "true") { + elements.push_back(LiteralValue::fromBoolean(true)); + } else if (val == "false") { + elements.push_back(LiteralValue::fromBoolean(false)); + } else { + elements.push_back(LiteralValue::fromSymbol(val)); + } + } else if (_token->isKeyword()) { + // literalKeyword: collect multi-part keyword symbol (e.g., at:put:) + egg::string keyword = _token->value(); + step_(); + while (_token && _token->isKeyword()) { + keyword += _token->value(); + step_(); + } + elements.push_back(LiteralValue::fromSymbol(keyword)); + continue; // already stepped past last keyword + } else if (_token->hasSymbol()) { + elements.push_back(LiteralValue::fromSymbol(_token->value())); + } else if (_token->is('-')) { + // negative number in literal array + step_(); + if (_token && _token->isLiteral()) { + auto* strTok = static_cast(_token.get()); + if (strTok->literalKind() == SStringToken::LitNumber) { + std::string v = _token->value().toUtf8(); + if (v.find('.') != std::string::npos) { + elements.push_back(LiteralValue::fromFloat(-std::stod(v))); + } else { + elements.push_back(LiteralValue::fromInteger(-std::stoll(v, nullptr, 0))); + } + } else { + elements.push_back(LiteralValue::fromSymbol("-")); + continue; // don't step, re-process current token + } + } else { + elements.push_back(LiteralValue::fromSymbol("-")); + continue; // don't step, re-process current token + } + } else if (_token->is('(')) { + // nested literal array (without #) + auto* nested = static_cast(literalArray_()); + elements.push_back(nested->literalValue()); + continue; + } else if (_token->is("#(")) { + auto* nested = static_cast(literalArray_()); + elements.push_back(nested->literalValue()); + continue; + } else if (_token->is("#[")) { + auto* nested = static_cast(literalByteArray_()); + elements.push_back(nested->literalValue()); + continue; + } else { + error_("invalid literal entry"); + } + step_(); + } + if (!_token || !_token->is(')')) { + missingToken_(")"); + } + auto lit = new SLiteralNode(_compiler); + lit->literalValue_(LiteralValue::fromArray(std::move(elements))); + lit->position_(Stretch(position, _token->position().end())); + step_(); + return lit; +} + +SParseNode* SSmalltalkParser::literalByteArray_() { + uint32_t position = _token->position().start(); + step_(); + std::vector bytes; + while (_token && !_token->is(']') && !_token->isEnd()) { + if (_token->isLiteral()) { + // Each element should be a number 0-255 + std::string v = _token->value().toUtf8(); + int val = static_cast(std::stol(v, nullptr, 0)); + bytes.push_back(static_cast(val)); + } + step_(); + } + if (!_token || !_token->is(']')) { + missingToken_("]"); + } + auto lit = new SLiteralNode(_compiler); + lit->literalValue_(LiteralValue::fromByteArray(std::move(bytes))); + lit->position_(Stretch(position, _token->position().end())); + step_(); + return lit; +} + +SBraceNode* SSmalltalkParser::bracedArray_() { + uint32_t position = _token->position().start(); + step_(); + SBraceNode* brace = new SBraceNode(_compiler); + brace->position_(Stretch(position, _token->position().start())); + while (_token && !_token->is('}') && !_token->isEnd()) { + SParseNode* expr = expression_(); + if (expr) { + brace->addElement_(expr); + } + if (_token && _token->is('.')) { + step_(); + } + } + if (!_token || !_token->is('}')) { + missingToken_("}"); + } + brace->position_(Stretch(position, _token->position().end())); + step_(); + return brace; +} + +void SSmalltalkParser::addPragmaTo_(SMethodNode* method) { + if (attachPragmaTo_(method)) { + step_(); + } +} + +bool SSmalltalkParser::attachPragmaTo_(SMethodNode* method) { + if (method->isHeadless() || !_token || !_token->is('<')) { + return false; + } + + uint32_t start = _token->position().start(); + step_(); + + SPragmaNode* pragma = nullptr; + + if (_token && _token->isKeyword()) { + egg::string keyword = _token->value(); + if (keyword == "primitive:") { + pragma = pragma_(); + } else { + pragma = symbolicPragma_(); + } + } else { + pragma = symbolicPragma_(); + } + + if (pragma) { + pragma->position_(Stretch(start, _token->position().end())); + method->pragma_(pragma); + } + + if (!_token || !_token->is('>')) { + missingToken_(">"); + } + + return true; +} + +SPragmaNode* SSmalltalkParser::pragma_() { + step_(); + + if (!_token) { + error_("missing pragma value"); + } + + if (_token->isLiteral()) { + return numberedPrimitive_(); + } else if (_token->isName()) { + return namedPrimitive_(); + } + + error_("invalid pragma format"); + return nullptr; +} + +SPragmaNode* SSmalltalkParser::numberedPrimitive_() { + int number = 0; + try { + number = std::stoi(_token->value().toUtf8()); + } catch (...) { + error_("invalid primitive number"); + } + + uint32_t position = _token->position().start(); + SPragmaNode* pragma = new SPragmaNode(_compiler); + pragma->bePrimitive_(number, ""); + pragma->position_(Stretch(position, _token->position().end())); + + step_(); + return pragma; +} + +SPragmaNode* SSmalltalkParser::namedPrimitive_() { + egg::string name = _token->value(); + uint32_t position = _token->position().start(); + + SPragmaNode* pragma = new SPragmaNode(_compiler); + pragma->bePrimitive_(0, name); + pragma->position_(Stretch(position, _token->position().end())); + + step_(); + return pragma; +} + +SPragmaNode* SSmalltalkParser::symbolicPragma_() { + egg::string symbol = _token->value(); + uint32_t position = _token->position().start(); + + SPragmaNode* pragma = new SPragmaNode(_compiler); + pragma->beSymbolic_(symbol); + pragma->position_(Stretch(position, _token->position().end())); + + step_(); + return pragma; +} + +SMethodNode* SSmalltalkParser::buildMethodNode_(SSelectorNode* selector, const std::vector& arguments) { + SMethodNode* method = new SMethodNode(_compiler); + method->selector_(selector); + method->arguments_(arguments); + method->position_(selector->position()); + _compiler->activeScript_(method); + return method; +} + +SMessageNode* SSmalltalkParser::buildMessageNode_(SParseNode* receiver) { + SMessageNode* msg = new SMessageNode(_compiler); + msg->receiver_(receiver); + msg->position_(receiver->position()); + return msg; +} + +SCascadeMessageNode* SSmalltalkParser::buildCascadeMessageNode_(SParseNode* receiver) { + SCascadeMessageNode* msg = new SCascadeMessageNode(_compiler); + msg->receiver_(receiver); + msg->position_(receiver->position()); + return msg; +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/Parser/SSmalltalkParser.h b/runtime/cpp/Compiler/Parser/SSmalltalkParser.h new file mode 100644 index 00000000..f988e14f --- /dev/null +++ b/runtime/cpp/Compiler/Parser/SSmalltalkParser.h @@ -0,0 +1,129 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _SSMALLTALKPARSER_H_ +#define _SSMALLTALKPARSER_H_ + +#include +#include +#include +#include "SToken.h" +#include "../SSmalltalkCompiler.h" +#include "../AST/SParseNode.h" +#include "../AST/SIdentifierNode.h" +#include "../AST/SLiteralNode.h" +#include "../AST/SMessageNode.h" +#include "../AST/SAssignmentNode.h" +#include "../AST/SReturnNode.h" +#include "../AST/SMethodNode.h" +#include "../AST/SBlockNode.h" +#include "../AST/SCascadeNode.h" +#include "../AST/SBraceNode.h" +#include "../AST/SCascadeMessageNode.h" +#include "../AST/SSelectorNode.h" +#include "../AST/SNumberNode.h" +#include "../AST/SStringNode.h" +#include "../AST/SPragmaNode.h" + +namespace Egg { + +class SSmalltalkScanner; + +/** + * Parser for Smalltalk code + * Implements a recursive descent parser + * Corresponds to SmalltalkParser in Smalltalk + */ +class SSmalltalkParser { +private: + SSmalltalkCompiler* _compiler; + SSmalltalkScanner* _scanner; + std::unique_ptr _token; + std::unique_ptr _next; + +public: + SSmalltalkParser(SSmalltalkCompiler* compiler); + ~SSmalltalkParser(); + + SMethodNode* parseMethod_(); + SMethodNode* parseExpression_(); + + SMethodNode* method_(); + SMethodNode* headlessMethod_(); + SMethodNode* methodSignature_(); + SMethodNode* unarySignature_(); + SMethodNode* binarySignature_(); + SMethodNode* keywordSignature_(); + + SParseNode* expression_(); + SParseNode* primary_(); + SParseNode* statement_(); + std::vector statements_(); + + SParseNode* unarySequence_(SParseNode* receiver); + SParseNode* binarySequence_(SParseNode* receiver); + SParseNode* keywordSequence_(SParseNode* receiver); + SParseNode* cascadeSequence_(SMessageNode* message); + + void unaryMessage_(SMessageNode* message); + void binaryMessage_(SMessageNode* message); + void keywordMessage_(SMessageNode* message); + void cascadeMessage_(SMessageNode* message); + + SBlockNode* block_(); + std::vector blockArguments_(); + + SReturnNode* return_(); + SAssignmentNode* assignment_(); + + std::vector temporaries_(); + + void addBodyTo_(SMethodNode* method); + void addTemporariesTo_(SMethodNode* method); + void addStatementsTo_(SMethodNode* method); + void addPragmaTo_(SMethodNode* method); + bool attachPragmaTo_(SMethodNode* method); + + SParseNode* literalArray_(); + SParseNode* literalByteArray_(); + SBraceNode* bracedArray_(); + + SPragmaNode* pragma_(); + SPragmaNode* numberedPrimitive_(); + SPragmaNode* namedPrimitive_(); + SPragmaNode* symbolicPragma_(); + + SParseNode* parenthesizedExpression_(); + bool hasUnarySelector_() const; + bool hasBinarySelector_() const; + bool hasKeywordSelector_() const; + + SToken* step_(); + SToken* peek_(); + SToken* next_(); + void skipDots_(); + + void error_(const std::string& message); + void error_(const std::string& message, uint32_t position); + void missingToken_(const std::string& expected); + void missingExpression_(); + void missingArgument_(); + + template + T* buildNode_(uint32_t position) { + T* node = new T(_compiler); + node->position_(Stretch(position, _token->position().end())); + return node; + } + + SMethodNode* buildMethodNode_(SSelectorNode* selector, + const std::vector& arguments); + SMessageNode* buildMessageNode_(SParseNode* receiver); + SCascadeMessageNode* buildCascadeMessageNode_(SParseNode* receiver); +}; + +} // namespace Egg + +#endif // _SSMALLTALKPARSER_H_ diff --git a/runtime/cpp/Compiler/Parser/SSmalltalkScanner.cpp b/runtime/cpp/Compiler/Parser/SSmalltalkScanner.cpp new file mode 100644 index 00000000..da1fab96 --- /dev/null +++ b/runtime/cpp/Compiler/Parser/SSmalltalkScanner.cpp @@ -0,0 +1,419 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SSmalltalkScanner.h" +#include "../SCompiler.h" +#include "../SSmalltalkCompiler.h" +#include +#include +#include + +namespace Egg { + +SSmalltalkScanner::SSmalltalkScanner(SSmalltalkCompiler* compiler) : _compiler(compiler) {} + +SSmalltalkScanner::~SSmalltalkScanner() {} + +void SSmalltalkScanner::compiler_(SSmalltalkCompiler* compiler) { + _compiler = compiler; +} + +void SSmalltalkScanner::on_(const egg::string& source) { + stream.on_(source); +} + +void SSmalltalkScanner::sourceCode_(const egg::string& source) { + stream.on_(source); +} + +std::unique_ptr SSmalltalkScanner::next() { + return nextToken(); +} + +bool SSmalltalkScanner::canBeInIdentifier_(uint32_t ch) const { + return _compiler->frontend()->canBeInIdentifier_(ch); +} + +bool SSmalltalkScanner::canStartIdentifier_(uint32_t ch) const { + if (!_compiler->frontend()->canStartIdentifier_(ch)) return false; + if (ch == '_') { + uint32_t next = stream.peek(); + return next != 0 && next >= 33; + } + return true; +} + +std::unique_ptr SSmalltalkScanner::end() { + auto token = new SEndToken(Stretch(stream.position() + 1)); + return std::unique_ptr(token); +} + +void SSmalltalkScanner::error_(const std::string& message) { + error_at_(message, stream.position()); +} + +void SSmalltalkScanner::error_at_(const std::string& message, size_t position) { + std::ostringstream oss; + oss << "Scanner error at position " << position << ": " << message; + throw std::runtime_error(oss.str()); +} + +bool SSmalltalkScanner::isBinary_(uint32_t ch) const { + if (ch == 0) return false; + if (ch < 128) { + return ch == '+' || ch == '-' || ch == '<' || ch == '>' || ch == '=' || + ch == '*' || ch == '/' || ch == '\\' || ch == '|' || ch == '&' || + ch == '~' || ch == ',' || ch == '@' || ch == '%' || ch == '?' || + ch == '!' || ch == ':' || ch == '^'; + } + return ch > 255; +} + +std::unique_ptr SSmalltalkScanner::nextArrayPrefix() { + egg::string string = stream.copyFrom_to_(stream.position() - 2, stream.position()); + auto token = new SDelimiterToken(Stretch(0, 0), U""); + return buildToken_at_with_(token, stream.position() - 2, string); +} + +std::unique_ptr SSmalltalkScanner::nextAssignment() { + auto token = new SDelimiterToken(Stretch(0, 0), U""); + return buildToken_at_with_(token, stream.position(), egg::string(U":=")); +} + +std::unique_ptr SSmalltalkScanner::nextBinarySelector() { + stream.back(); + size_t start = stream.position(); + egg::string value = scanBinarySymbol(); + auto token = new SSymbolicToken(Stretch(0, 0), U"", true); + return buildToken_at_with_(token, start, value); +} + +std::unique_ptr SSmalltalkScanner::nextBinarySymbol() { + size_t start = stream.position(); + egg::string value = scanBinarySymbol(); + auto token = new SStringToken(Stretch(0, 0), U"", SStringToken::LitSymbol); + return buildToken_at_with_(token, start, value); +} + +std::unique_ptr SSmalltalkScanner::nextColon() { + size_t start = stream.position(); + uint32_t ch = stream.peek(); + + if ((ch == ' ' || ch == '\t') && !stream.atEnd()) { + stream.next(); // skip space/tab + if (stream.peek() == '=') { + ch = '='; + } else { + stream.back(); + } + } + + if (ch == '=') { + stream.next(); // skip = + auto token = nextAssignment(); + token->position_(Stretch(start, stream.position())); + return token; + } + + if (isBinary_(stream.peek())) { + return nextBinarySelector(); + } + + return nextSpecialCharacter(); +} + +std::unique_ptr SSmalltalkScanner::nextComment() { + size_t start = stream.position(); + + while (!stream.atEnd() && stream.peek() != '"') { + stream.next(); + } + + if (stream.atEnd()) { + error_at_("unfinished comment", start); + } + + stream.position_(start); + egg::string comment = stream.upTo_('"'); + + return nextToken(); +} + +std::unique_ptr SSmalltalkScanner::nextIdentifierOrKeyword() { + stream.back(); // Back to the first character + size_t start = stream.position(); + skipIdentifier(); + + if (stream.peekFor_(':') && stream.peekFor_('=')) { + stream.back(); + stream.back(); + } + + auto token = new SSymbolicToken(Stretch(0, 0), U""); + return buildToken_at_(token, start); +} + +std::unique_ptr SSmalltalkScanner::nextKeyword() { + size_t start = stream.position(); + skipKeyword(); + egg::string string = stream.copyFrom_to_(start, stream.position()); + auto token = new SStringToken(Stretch(0, 0), U"", SStringToken::LitSymbol); + return buildToken_at_with_(token, start, string); +} + +std::unique_ptr SSmalltalkScanner::nextLiteralCharacter() { + if (stream.atEnd()) { + error_("character expected"); + } + uint32_t cp = stream.next(); + egg::string value(1, (char32_t)cp); + auto token = new SStringToken(Stretch(0, 0), U"", SStringToken::LitCharacter); + return buildToken_at_with_(token, stream.position(), value); +} + +std::unique_ptr SSmalltalkScanner::nextLiteralString() { + size_t start = stream.position(); + egg::string value = scanString(); + auto token = new SStringToken(Stretch(0, 0), U""); + return buildToken_at_with_(token, start, value); +} + +std::unique_ptr SSmalltalkScanner::nextNumber() { + stream.back(); + size_t start = stream.position(); + + auto isDigit = [](uint32_t ch) { return ch < 128 && std::isdigit((int)ch); }; + auto isAlpha = [](uint32_t ch) { return ch < 128 && std::isalpha((int)ch); }; + auto isAlnum = [](uint32_t ch) { return ch < 128 && std::isalnum((int)ch); }; + auto isHexDigit = [](uint32_t ch) { return ch < 128 && std::isxdigit((int)ch); }; + + // Read integer part + while (!stream.atEnd() && isDigit(stream.peek())) { + stream.next(); + } + + // Check for 0x hex prefix notation (e.g., 0xFF) + if ((stream.position() - start) == 1 && !stream.atEnd() && (stream.peek() == 'x' || stream.peek() == 'X')) { + stream.next(); // skip 'x'/'X' + while (!stream.atEnd() && isHexDigit(stream.peek())) { + stream.next(); + } + } + // Check for radix notation (e.g., 16rFF) + else if (!stream.atEnd() && (stream.peek() == 'r' || stream.peek() == 'R')) { + size_t savedPos = stream.position(); + stream.next(); // skip 'r' + if (!stream.atEnd() && isAlnum(stream.peek())) { + while (!stream.atEnd() && isAlnum(stream.peek())) { + stream.next(); + } + } else { + stream.position_(savedPos); + } + } + // Check for float (digits followed by '.' followed by digit) + else if (!stream.atEnd() && stream.peek() == '.') { + size_t savedPos = stream.position(); + stream.next(); // skip '.' + if (!stream.atEnd() && isDigit(stream.peek())) { + while (!stream.atEnd() && isDigit(stream.peek())) { + stream.next(); + } + // Check for scientific notation + if (!stream.atEnd() && (stream.peek() == 'e' || stream.peek() == 'E')) { + stream.next(); + if (!stream.atEnd() && (stream.peek() == '+' || stream.peek() == '-')) { + stream.next(); + } + while (!stream.atEnd() && isDigit(stream.peek())) { + stream.next(); + } + } + } else { + stream.position_(savedPos); // '.' is not part of number + } + } + + egg::string value = stream.copyFrom_to_(start, stream.position()); + auto token = new SStringToken(Stretch(0, 0), U"", SStringToken::LitNumber); + return buildToken_at_with_(token, start, value); +} + +std::unique_ptr SSmalltalkScanner::nextQuotedSymbol() { + auto node = nextLiteralString(); + node->literalKind_(SStringToken::LitSymbol); + node->position_(Stretch(node->position().start() - 1, node->position().end())); + return node; +} + +std::unique_ptr SSmalltalkScanner::nextSpecialCharacter() { + auto token = new SDelimiterToken(Stretch(0, 0), U""); + return buildToken_at_(token, stream.position() - 1); +} + +std::unique_ptr SSmalltalkScanner::nextSymbolOrArrayPrefix() { + if (stream.atEnd()) { + error_("character expected"); + } + + uint32_t ch = stream.peek(); + + if (canBeInIdentifier_(ch)) { + return nextKeyword(); + } + + if (isBinary_(ch)) { + return nextBinarySymbol(); + } + + stream.next(); + + if (ch == '[' || ch == '(') { + return nextArrayPrefix(); + } + + if (ch == '\'') { + return nextQuotedSymbol(); + } + + error_("character expected"); + return nullptr; +} + +std::unique_ptr SSmalltalkScanner::nextToken() { + uint32_t first = scanChar(); + + if (first == 0) { + return end(); + } + + if (canStartIdentifier_(first)) { + return nextIdentifierOrKeyword(); + } + + if (first == '_') { + return nextAssignment(); + } + + if (first == ':') { + return nextColon(); + } + + if (first == '\'') { + return nextLiteralString(); + } + + if (first == '$') { + return nextLiteralCharacter(); + } + + if (first == '#') { + return nextSymbolOrArrayPrefix(); + } + + if (first == '"') { + return nextComment(); + } + + if (first < 128 && std::isdigit((int)first)) { + return nextNumber(); + } + + if (first != '^' && isBinary_(first)) { + return nextBinarySelector(); + } + + return nextSpecialCharacter(); +} + +egg::string SSmalltalkScanner::scanBinarySymbol() { + size_t start = stream.position(); + skipBinary(); + egg::string symbol = stream.copyFrom_to_(start, stream.position()); + return symbol; +} + +uint32_t SSmalltalkScanner::scanChar() { + while (!stream.atEnd()) { + uint32_t ch = stream.peek(); + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { + stream.next(); + } else { + break; + } + } + + if (stream.atEnd()) return 0; + return stream.next(); +} + +egg::string SSmalltalkScanner::scanString() { + size_t current = stream.position(); + size_t start = current; + egg::string result; + + while (true) { + egg::string fragment = stream.upTo_('\''); + result += fragment; + + if (current < stream.position()) { + stream.back(); + uint32_t ch = stream.next(); + if (ch != '\'') { + error_at_("string end expected", start); + } + } else { + error_at_("string end expected", start); + } + + if (!stream.peekFor_('\'')) { + break; + } + + result += U'\''; + current = stream.position(); + } + + return result; +} + +void SSmalltalkScanner::skipBinary() { + while (isBinary_(stream.peek())) { + stream.next(); + } +} + +void SSmalltalkScanner::skipIdentifier() { + while (!stream.atEnd()) { + if (!canBeInIdentifier_(stream.peek())) { + return; + } + stream.next(); + } +} + +void SSmalltalkScanner::skipKeyword() { + size_t pos = 0; + while (true) { + skipIdentifier(); + bool continue_loop = false; + + if (stream.peekFor_(':')) { + pos = stream.position(); + if (!stream.atEnd()) { + continue_loop = canStartIdentifier_(stream.peek()); + } + } + + if (!continue_loop) { + if (pos != 0) { + stream.position_(pos); + } + break; + } + } +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/Parser/SSmalltalkScanner.h b/runtime/cpp/Compiler/Parser/SSmalltalkScanner.h new file mode 100644 index 00000000..1585866b --- /dev/null +++ b/runtime/cpp/Compiler/Parser/SSmalltalkScanner.h @@ -0,0 +1,84 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _SSMALLTALKSCANNER_H_ +#define _SSMALLTALKSCANNER_H_ + +#include "SToken.h" +#include "Stream.h" +#include +#include + +namespace Egg { + +class SSmalltalkCompiler; + +class SSmalltalkScanner { +private: + SSmalltalkCompiler* _compiler; + Stream stream; + + template + std::unique_ptr buildToken_(T* token) { + return buildToken_at_(token, stream.position()); + } + + template + std::unique_ptr buildToken_at_(T* token, size_t position) { + egg::string string = stream.copyFrom_to_(position, stream.position()); + return buildToken_at_with_(token, position, string); + } + + template + std::unique_ptr buildToken_at_with_(T* token, size_t position, const egg::string& value) { + token->position_(Stretch(position, stream.position())); + token->value_(value); + return std::unique_ptr(token); + } + + bool canBeInIdentifier_(uint32_t ch) const; + bool canStartIdentifier_(uint32_t ch) const; + bool isBinary_(uint32_t ch) const; + + std::unique_ptr end(); + void error_(const std::string& message); + void error_at_(const std::string& message, size_t position); + + std::unique_ptr nextArrayPrefix(); + std::unique_ptr nextAssignment(); + std::unique_ptr nextBinarySelector(); + std::unique_ptr nextBinarySymbol(); + std::unique_ptr nextColon(); + std::unique_ptr nextComment(); + std::unique_ptr nextIdentifierOrKeyword(); + std::unique_ptr nextKeyword(); + std::unique_ptr nextLiteralCharacter(); + std::unique_ptr nextLiteralString(); + std::unique_ptr nextNumber(); + std::unique_ptr nextQuotedSymbol(); + std::unique_ptr nextSpecialCharacter(); + std::unique_ptr nextSymbolOrArrayPrefix(); + + egg::string scanBinarySymbol(); + uint32_t scanChar(); + egg::string scanString(); + void skipBinary(); + void skipIdentifier(); + void skipKeyword(); + +public: + explicit SSmalltalkScanner(SSmalltalkCompiler* compiler); + ~SSmalltalkScanner(); + + void compiler_(SSmalltalkCompiler* compiler); + std::unique_ptr next(); + std::unique_ptr nextToken(); + void on_(const egg::string& source); + void sourceCode_(const egg::string& source); +}; + +} // namespace Egg + +#endif // _SSMALLTALKSCANNER_H_ diff --git a/runtime/cpp/Compiler/Parser/SToken.h b/runtime/cpp/Compiler/Parser/SToken.h new file mode 100644 index 00000000..f1b1b164 --- /dev/null +++ b/runtime/cpp/Compiler/Parser/SToken.h @@ -0,0 +1,155 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _COMPILER_TOKEN_H_ +#define _COMPILER_TOKEN_H_ + +#include +#include +#include +#include "../Stretch.h" +#include "../Object.h" +#include "../egg_string.h" + +namespace Egg { + +class SSmalltalkCompiler; +class SToken; + +class SToken { +protected: + SSmalltalkCompiler* _compiler; + Stretch _stretch; + std::vector _comments; + +public: + SToken() : _compiler(nullptr) {} + SToken(const Stretch& pos) : _compiler(nullptr), _stretch(pos) {} + virtual ~SToken() {} + + SSmalltalkCompiler* compiler() const { return _compiler; } + void compiler_(SSmalltalkCompiler* comp) { _compiler = comp; } + + Stretch position() const { return _stretch; } + void position_(const Stretch& pos) { _stretch = pos; } + Stretch stretch() const { return _stretch; } + void stretch_(const Stretch& pos) { _stretch = pos; } + + virtual egg::string value() const { return ""; } + virtual void value_(const egg::string& val) {} + + void addComment_(const egg::string& comment) { + _comments.push_back(comment); + } + + const std::vector& comments() const { return _comments; } + + void moveCommentsFrom_(SToken* other) { + if (other) { + _comments.insert(_comments.end(), other->_comments.begin(), other->_comments.end()); + other->_comments.clear(); + } + } + + virtual bool isEnd() const { return false; } + virtual bool isLiteral() const { return false; } + virtual bool isName() const { return false; } + virtual bool isKeyword() const { return false; } + virtual bool isSymbolic() const { return false; } + virtual bool isString() const { return false; } + virtual bool isDelimiter() const { return false; } + virtual bool isComment() const { return false; } + virtual bool isBar() const { return false; } + virtual bool isAssignment() const { return false; } + + virtual bool is_(char ch) const { return false; } + bool is(char ch) const { return is_(ch); } + + virtual bool is_(const egg::string& str) const { return false; } + bool is(const egg::string& str) const { return is_(str); } + + virtual bool endsExpression() const { + return isEnd() || is_(']') || is_(')') || is_('}') || is_('.'); + } + + virtual bool hasSymbol() const { return false; } + + virtual Object* literalValue() const { return nullptr; } +}; + +class SEndToken : public SToken { +public: + SEndToken(const Stretch& pos) : SToken(pos) {} + bool isEnd() const override { return true; } +}; + +class SSymbolicToken : public SToken { +protected: + egg::string _value; + bool _isSymbol; + +public: + SSymbolicToken(const Stretch& pos, const egg::string& val, bool isSymbol = false) + : SToken(pos), _value(val), _isSymbol(isSymbol) {} + + egg::string value() const override { return _value; } + void value_(const egg::string& val) override { _value = val; } + + void beSymbol_() { _isSymbol = true; } + bool isSymbol() const { return _isSymbol; } + + bool isSymbolic() const override { return true; } + bool isKeyword() const override { + return !_value.empty() && _value.back() == ':'; + } + bool isBinary() const { return _isSymbol; } + bool isName() const override { return !isKeyword() && !isBinary(); } + bool hasSymbol() const override { return _isSymbol; } + + bool is_(char ch) const override { + return _value.length() == 1 && _value[0] == (char32_t)ch; + } + + bool is_(const egg::string& str) const override { + return _value == str; + } + + bool isBar() const override { return _value == "|"; } + bool isAssignment() const override { return _value == ":="; } +}; + +class SDelimiterToken : public SSymbolicToken { +public: + SDelimiterToken(const Stretch& pos, const egg::string& val) + : SSymbolicToken(pos, val, false) {} + bool isDelimiter() const override { return true; } + bool isSymbolic() const override { return false; } + bool isName() const override { return false; } +}; + +class SStringToken : public SSymbolicToken { +public: + enum LiteralKind { LitString, LitSymbol, LitNumber, LitCharacter }; + + SStringToken(const Stretch& pos, const egg::string& val, LiteralKind kind = LitString) + : SSymbolicToken(pos, val, false), _kind(kind) {} + bool isLiteral() const override { return true; } + bool isString() const override { return true; } + bool isSymbolic() const override { return false; } + bool isName() const override { return false; } + + // Literal tokens should never match delimiter checks like is_('.') + bool is_(char ch) const override { return false; } + bool is_(const egg::string& str) const override { return false; } + + LiteralKind literalKind() const { return _kind; } + void literalKind_(LiteralKind k) { _kind = k; } +private: + LiteralKind _kind; +}; + +} // namespace Egg + +#endif // _COMPILER_TOKEN_H_ diff --git a/runtime/cpp/Compiler/Parser/Stream.h b/runtime/cpp/Compiler/Parser/Stream.h new file mode 100644 index 00000000..9815d44b --- /dev/null +++ b/runtime/cpp/Compiler/Parser/Stream.h @@ -0,0 +1,95 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _STREAM_H_ +#define _STREAM_H_ + +#include +#include +#include +#include "../egg_string.h" + +namespace Egg { + +class Stream { +private: + egg::string _source; + size_t _position; + +public: + Stream() : _position(0) {} + explicit Stream(const egg::string& source) : _source(source), _position(0) {} + + void on_(const egg::string& source) { + _source = source; + _position = 0; + } + + bool atEnd() const { + return _position >= _source.length(); + } + + uint32_t next() { + if (atEnd()) return 0; + return static_cast(_source[_position++]); + } + + uint32_t peek() const { + if (atEnd()) return 0; + return static_cast(_source[_position]); + } + + void back() { + if (_position > 0) _position--; + } + + size_t position() const { + return _position; + } + + void position_(size_t pos) { + _position = pos; + } + + egg::string copyFrom_to_(size_t start, size_t end) const { + if (end > _source.length()) end = _source.length(); + if (start > end) return ""; + return _source.substr(start, end - start); + } + + egg::string upTo_(uint32_t delimiter) { + size_t start = _position; + while (!atEnd() && peek() != delimiter) { + next(); + } + egg::string result = copyFrom_to_(start, _position); + if (!atEnd() && peek() == delimiter) { + next(); + } + return result; + } + + bool peekFor_(uint32_t ch) { + if (atEnd() || peek() != ch) return false; + next(); + return true; + } + + Stream& skipSeparators() { + while (!atEnd()) { + uint32_t ch = peek(); + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { + next(); + } else { + break; + } + } + return *this; + } +}; + +} // namespace Egg + +#endif // _STREAM_H_ diff --git a/runtime/cpp/Compiler/SCompiler.cpp b/runtime/cpp/Compiler/SCompiler.cpp new file mode 100644 index 00000000..b53b0f9a --- /dev/null +++ b/runtime/cpp/Compiler/SCompiler.cpp @@ -0,0 +1,20 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SCompiler.h" +#include "egg_string.h" + +using namespace Egg; + +SCompiler::SCompiler() : _classBinding(nullptr) { +} + +bool SCompiler::canStartIdentifier_(uint32_t ch) const { + return egg::isLetter(static_cast(ch)) || ch == '_'; +} + +bool SCompiler::canBeInIdentifier_(uint32_t ch) const { + return egg::isLetter(static_cast(ch)) || egg::isDigit(static_cast(ch)) || ch == '_'; +} diff --git a/runtime/cpp/Compiler/SCompiler.h b/runtime/cpp/Compiler/SCompiler.h new file mode 100644 index 00000000..4b785a35 --- /dev/null +++ b/runtime/cpp/Compiler/SCompiler.h @@ -0,0 +1,35 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _SCOMPILER_H_ +#define _SCOMPILER_H_ + +#include + +namespace Egg { + +class HeapObject; + +/** + * Corresponds to SCompiler in Smalltalk. + * Frontend compiler that provides class binding and character classification. + */ +class SCompiler { +private: + HeapObject* _classBinding; + +public: + SCompiler(); + + void classBinding_(HeapObject* classObj) { _classBinding = classObj; } + HeapObject* classBinding() const { return _classBinding; } + + bool canStartIdentifier_(uint32_t ch) const; + bool canBeInIdentifier_(uint32_t ch) const; +}; + +} // namespace Egg + +#endif // _SCOMPILER_H_ diff --git a/runtime/cpp/Compiler/SSmalltalkCompiler.cpp b/runtime/cpp/Compiler/SSmalltalkCompiler.cpp new file mode 100644 index 00000000..9a4f2e32 --- /dev/null +++ b/runtime/cpp/Compiler/SSmalltalkCompiler.cpp @@ -0,0 +1,295 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#include "SSmalltalkCompiler.h" +#include "Parser/SSmalltalkScanner.h" +#include "Parser/SSmalltalkParser.h" +#include "CompilationError.h" +#include "CompilationResult.h" +#include "Stretch.h" +#include "Parser/SToken.h" +#include "SemanticVisitor.h" +#include "AST/SAssignmentNode.h" +#include "AST/SBlockNode.h" +#include "AST/SBraceNode.h" +#include "AST/SCascadeMessageNode.h" +#include "AST/SCascadeNode.h" +#include "AST/SCommentNode.h" +#include "AST/SIdentifierNode.h" +#include "AST/SLiteralNode.h" +#include "AST/SMessageNode.h" +#include "AST/SMethodNode.h" +#include "AST/SNumberNode.h" +#include "AST/SParseNode.h" +#include "AST/SPragmaNode.h" +#include "AST/SReturnNode.h" +#include "AST/SSelectorNode.h" +#include "AST/SScriptNode.h" + +namespace Egg { + +SSmalltalkCompiler::SSmalltalkCompiler() + : _ownedFrontend(std::make_unique()), + _frontend(_ownedFrontend.get()), + _ast(nullptr), _result(nullptr), _headless(false), _blocks(0), _leaf(true), _activeScript(nullptr) { + _scanner = std::make_unique(this); + _parser = std::make_unique(this); +} + +SSmalltalkCompiler::~SSmalltalkCompiler() {} + +void SSmalltalkCompiler::activate_while_(SScriptNode* aScriptNode, std::function aBlock) { + SScriptNode* current = _activeScript; + _activeScript = aScriptNode; + aBlock(); + _activeScript = current; +} + +ScriptScope* SSmalltalkCompiler::activeScope() { + return _activeScript ? _activeScript->scope() : nullptr; +} + +SScriptNode* SSmalltalkCompiler::activeScript() { + return _activeScript; +} + +void SSmalltalkCompiler::activeScript_(SScriptNode* aScriptNode) { + _activeScript = aScriptNode; +} + +SAssignmentNode* SSmalltalkCompiler::assignmentNode() { + return new SAssignmentNode(this); +} + +SMethodNode* SSmalltalkCompiler::ast() { + return _ast; +} + +int SSmalltalkCompiler::blockCount() { + return _blocks; +} + +int SSmalltalkCompiler::blockIndex() { + _blocks += 1; + return _blocks - 1; +} + +SBlockNode* SSmalltalkCompiler::blockNode() { + return new SBlockNode(this); +} + +SBraceNode* SSmalltalkCompiler::braceNode() { + return new SBraceNode(this); +} + +void SSmalltalkCompiler::buildMethod() { + if (_result && _ast) { + _result->method_(_ast->buildMethod()); + } +} + +SCascadeMessageNode* SSmalltalkCompiler::cascadeSMessageNode() { + return new SCascadeMessageNode(this); +} + +SCascadeNode* SSmalltalkCompiler::cascadeNode() { + return new SCascadeNode(this); +} + +SCommentNode* SSmalltalkCompiler::commentNode() { + return new SCommentNode(this); +} + +CompilationError* SSmalltalkCompiler::compilationError_stretch_(const egg::string& aString, Stretch* aStretch) { + CompilationError* error = new CompilationError(aString); + error->compiler_(this); + error->stretch_(aStretch); + return error; +} + +CompilationResult* SSmalltalkCompiler::compileMethod_(const egg::string& aString) { + _source = aString; + if (_frontend) { + parseMethod(); + resolveSemantics(); + buildMethod(); + } else { + parseMethod(); + resolveSemantics(); + buildMethod(); + } + return _result; +} + +SToken* SSmalltalkCompiler::delimiterToken() { + return new SDelimiterToken(Stretch(0, 0), ""); +} + +SEndToken* SSmalltalkCompiler::endToken() { + return new SEndToken(Stretch(0, 0)); +} + +CompilationError* SSmalltalkCompiler::error_at_(const egg::string& aString, int anInteger) { + Stretch* stretch = new Stretch(anInteger, anInteger); + return error_stretch_(aString, stretch); +} + +CompilationError* SSmalltalkCompiler::error_stretch_(const egg::string& aString, Stretch* aStretch) { + auto error = compilationError_stretch_(aString, aStretch); + error->beFatal(); + throw *error; +} + +SCompiler* SSmalltalkCompiler::frontend() { + return _frontend; +} + +void SSmalltalkCompiler::frontend_(SCompiler* aSCompiler) { + _frontend = aSCompiler; +} + +bool SSmalltalkCompiler::hasBlocks() { + return _blocks > 0; +} + +bool SSmalltalkCompiler::hasSends() { + return !_leaf; +} + +SIdentifierNode* SSmalltalkCompiler::identifierNode() { + return new SIdentifierNode(this); +} + +void SSmalltalkCompiler::initialize() { + reset(); +} + +SLiteralNode* SSmalltalkCompiler::literalNode() { + return new SLiteralNode(this); +} + +SMessageNode* SSmalltalkCompiler::messageNode() { + return new SMessageNode(this); +} + +SMethodNode* SSmalltalkCompiler::methodNode() { + return new SMethodNode(this); +} + +void SSmalltalkCompiler::noticeSend() { + _leaf = false; +} + +SNumberNode* SSmalltalkCompiler::numericSLiteralNode() { + return new SNumberNode(this); +} + +void SSmalltalkCompiler::parseFragment() { + _headless = false; + reset(); + scanner()->on_(_source); + try { + _ast = parser()->parseMethod_(); + } catch (...) { + } + if (_result) { + _result->ast_(_ast); + } +} + +SMethodNode* SSmalltalkCompiler::parseFragment_(const egg::string& aString) { + _source = aString; + try { + parseFragment(); + resolveSemantics(); + } catch (CompilationError&) {} + return _ast; +} + +void SSmalltalkCompiler::parseMethod() { + _headless = false; + reset(); + scanner()->on_(_source); + _ast = parser()->parseMethod_(); + if (_result) _result->ast_(_ast); +} + +CompilationResult* SSmalltalkCompiler::parseMethod_(const egg::string& aString) { + _source = aString; + if (_frontend) { + parseMethod(); + resolveSemantics(); + } else { + parseMethod(); + resolveSemantics(); + } + return _result; +} + +SSmalltalkParser* SSmalltalkCompiler::parser() { + return _parser.get(); +} + +SPragmaNode* SSmalltalkCompiler::pragmaNode() { + return new SPragmaNode(this); +} + +void SSmalltalkCompiler::reset() { + resetResult(); + _leaf = true; + _blocks = 0; +} + +void SSmalltalkCompiler::resetResult() { + _result = new CompilationResult(); + _result->compiler_(this); +} + +void SSmalltalkCompiler::resolveSemantics() { + if (_ast) { + SemanticVisitor* visitor = new SemanticVisitor(); + _ast->acceptVisitor_(visitor); + delete visitor; + } +} + +CompilationResult* SSmalltalkCompiler::result() { + return _result; +} + +SReturnNode* SSmalltalkCompiler::returnNode() { + return new SReturnNode(this); +} + +SSmalltalkScanner* SSmalltalkCompiler::scanner() { + return _scanner.get(); +} + +SSelectorNode* SSmalltalkCompiler::selectorNode() { + return new SSelectorNode(this); +} + +egg::string SSmalltalkCompiler::sourceCode() { + return _source; +} + +void SSmalltalkCompiler::sourceCode_(const egg::string& aString) { + _source = aString; +} + +SStringToken* SSmalltalkCompiler::stringToken() { + return new SStringToken(Stretch(0, 0), ""); +} + +bool SSmalltalkCompiler::supportsBraceNodes() { + return true; +} + +void SSmalltalkCompiler::warning_at_(const egg::string& aString, Stretch* aStretch) { + auto error = compilationError_stretch_(aString, aStretch); + error->beWarning(); + throw *error; +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/SSmalltalkCompiler.h b/runtime/cpp/Compiler/SSmalltalkCompiler.h new file mode 100644 index 00000000..6cf0a3a6 --- /dev/null +++ b/runtime/cpp/Compiler/SSmalltalkCompiler.h @@ -0,0 +1,115 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _SSMALLTALKCOMPILER_H_ +#define _SSMALLTALKCOMPILER_H_ +#include "SCompiler.h" +#include "egg_string.h" +#include +#include + +namespace Egg { + +class SAssignmentNode; +class SBlockNode; +class SBraceNode; +class SCascadeMessageNode; +class SCascadeNode; +class SCommentNode; +class CompilationError; +class CompilationResult; +class SIdentifierNode; +class SLiteralNode; +class SMessageNode; +class SMethodNode; +class SNumberNode; +class SParseNode; +class SSmalltalkParser; +class SPragmaNode; +class SReturnNode; +class SSmalltalkScanner; +class SScriptNode; +class ScriptScope; +class SSelectorNode; +class Stretch; +class SToken; +class SEndToken; +class SStringToken; + +/** + * Corresponds to SSmalltalkCompiler in Smalltalk. + * Orchestrates the compilation pipeline: parse → semantic analysis → build method. + */ +class SSmalltalkCompiler { +private: + std::unique_ptr _ownedFrontend; + SCompiler* _frontend; + std::unique_ptr _scanner; + std::unique_ptr _parser; + egg::string _source; + SMethodNode* _ast; + CompilationResult* _result; + bool _headless; + int _blocks; + bool _leaf; + SScriptNode* _activeScript; + +public: + SSmalltalkCompiler(); + ~SSmalltalkCompiler(); + + void activate_while_(SScriptNode* aScriptNode, std::function aBlock); + ScriptScope* activeScope(); + SScriptNode* activeScript(); + void activeScript_(SScriptNode* aParseNode); + SAssignmentNode* assignmentNode(); + SMethodNode* ast(); + int blockCount(); + int blockIndex(); + SBlockNode* blockNode(); + SBraceNode* braceNode(); + void buildMethod(); + SCascadeMessageNode* cascadeSMessageNode(); + SCascadeNode* cascadeNode(); + SCommentNode* commentNode(); + CompilationError* compilationError_stretch_(const egg::string& aString, Stretch* aStretch); + CompilationResult* compileMethod_(const egg::string& aString); + SToken* delimiterToken(); + SEndToken* endToken(); + CompilationError* error_at_(const egg::string& aString, int anInteger); + CompilationError* error_stretch_(const egg::string& aString, Stretch* aStretch); + SCompiler* frontend(); + void frontend_(SCompiler* aSCompiler); + bool hasBlocks(); + bool hasSends(); + SIdentifierNode* identifierNode(); + void initialize(); + SLiteralNode* literalNode(); + SMessageNode* messageNode(); + SMethodNode* methodNode(); + void noticeSend(); + SNumberNode* numericSLiteralNode(); + void parseFragment(); + SMethodNode* parseFragment_(const egg::string& aString); + void parseMethod(); + CompilationResult* parseMethod_(const egg::string& aString); + SSmalltalkParser* parser(); + SPragmaNode* pragmaNode(); + void reset(); + void resetResult(); + void resolveSemantics(); + CompilationResult* result(); + SReturnNode* returnNode(); + SSmalltalkScanner* scanner(); + SSelectorNode* selectorNode(); + egg::string sourceCode(); + void sourceCode_(const egg::string& aString); + SStringToken* stringToken(); + bool supportsBraceNodes(); + void warning_at_(const egg::string& aString, Stretch* aStretch); +}; + +} // namespace Egg + +#endif // _SSMALLTALKCOMPILER_H_ diff --git a/runtime/cpp/Compiler/Stretch.h b/runtime/cpp/Compiler/Stretch.h new file mode 100644 index 00000000..50a1e0fd --- /dev/null +++ b/runtime/cpp/Compiler/Stretch.h @@ -0,0 +1,34 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _STRETCH_H_ +#define _STRETCH_H_ + +#include + +namespace Egg { + +/** + * Represents a range in source code + * Corresponds to Stretch in Smalltalk (start thru: end) + */ +class Stretch { + uint32_t _start; + uint32_t _end; + +public: + Stretch() : _start(0), _end(0) {} + Stretch(uint32_t start, uint32_t end) : _start(start), _end(end) {} + Stretch(uint32_t pos) : _start(pos), _end(pos) {} + + uint32_t start() const { return _start; } + uint32_t end() const { return _end; } + void start_(uint32_t s) { _start = s; } + void end_(uint32_t e) { _end = e; } +}; + +} // namespace Egg + +#endif // _STRETCH_H_ diff --git a/runtime/cpp/Compiler/egg_string.h b/runtime/cpp/Compiler/egg_string.h new file mode 100644 index 00000000..7abd3610 --- /dev/null +++ b/runtime/cpp/Compiler/egg_string.h @@ -0,0 +1,274 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _EGG_STRING_H_ +#define _EGG_STRING_H_ + +#include +#include +#include +#include + +namespace egg { + +/** + * Unicode string type for the Egg compiler pipeline. + * Internally stores UTF-32 code points (std::u32string). + * Implicitly converts from UTF-8 (std::string, const char*) + * and from UTF-32 (std::u32string, const char32_t*). + * + * Use .toUtf8() at output boundaries (error messages, file I/O, + * standard library functions that require narrow strings). + */ +class string : public std::u32string { +private: + // Decode a UTF-8 byte sequence into a UTF-32 code point string + static std::u32string fromUtf8(const char* data, size_t len) { + std::u32string result; + result.reserve(len); // upper bound + size_t i = 0; + while (i < len) { + unsigned char first = static_cast(data[i]); + uint32_t cp; + int bytes; + if ((first & 0x80) == 0) { + cp = first; bytes = 1; + } else if ((first & 0xE0) == 0xC0) { + cp = first & 0x1F; bytes = 2; + } else if ((first & 0xF0) == 0xE0) { + cp = first & 0x0F; bytes = 3; + } else if ((first & 0xF8) == 0xF0) { + cp = first & 0x07; bytes = 4; + } else { + cp = first; bytes = 1; // invalid byte, take as-is + } + for (int j = 1; j < bytes && i + j < len; j++) { + cp = (cp << 6) | (static_cast(data[i + j]) & 0x3F); + } + result += static_cast(cp); + i += bytes; + } + return result; + } + + static std::u32string fromUtf8(const std::string& s) { + return fromUtf8(s.data(), s.size()); + } + +public: + // Inherit all std::u32string constructors + using std::u32string::u32string; + using std::u32string::operator=; + + // Default / copy / move + string() = default; + string(const string&) = default; + string(string&&) = default; + string& operator=(const string&) = default; + string& operator=(string&&) = default; + + // Converting from std::u32string + string(const std::u32string& s) : std::u32string(s) {} + string(std::u32string&& s) : std::u32string(std::move(s)) {} + + // UTF-8 constructors — assume input is UTF-8 + string(const char* s) : std::u32string(fromUtf8(s, std::strlen(s))) {} + string(const std::string& s) : std::u32string(fromUtf8(s)) {} + + // Assignment from UTF-8 + string& operator=(const char* s) { + static_cast(*this) = fromUtf8(s, std::strlen(s)); + return *this; + } + string& operator=(const std::string& s) { + static_cast(*this) = fromUtf8(s); + return *this; + } + + // ---- UTF-8 output ---- + + std::string toUtf8() const { + std::string result; + for (char32_t cp : *this) { + if (cp < 0x80) { + result += static_cast(cp); + } else if (cp < 0x800) { + result += static_cast(0xC0 | (cp >> 6)); + result += static_cast(0x80 | (cp & 0x3F)); + } else if (cp < 0x10000) { + result += static_cast(0xE0 | (cp >> 12)); + result += static_cast(0x80 | ((cp >> 6) & 0x3F)); + result += static_cast(0x80 | (cp & 0x3F)); + } else if (cp < 0x110000) { + result += static_cast(0xF0 | (cp >> 18)); + result += static_cast(0x80 | ((cp >> 12) & 0x3F)); + result += static_cast(0x80 | ((cp >> 6) & 0x3F)); + result += static_cast(0x80 | (cp & 0x3F)); + } + } + return result; + } + + // ---- Override methods that return std::u32string to return egg::string ---- + + string substr(size_type pos = 0, size_type count = npos) const { + return string(std::u32string::substr(pos, count)); + } + + // ---- Concatenation returning egg::string ---- + + string operator+(const string& other) const { + return string(static_cast(*this) + + static_cast(other)); + } + + string operator+(char32_t ch) const { + string result(*this); + result.push_back(ch); + return result; + } + + string operator+(const char* s) const { + return *this + string(s); + } + + friend string operator+(const char* lhs, const string& rhs) { + return string(lhs) + rhs; + } + + // ---- Compound assignment ---- + + string& operator+=(const string& other) { + std::u32string::operator+=(static_cast(other)); + return *this; + } + + string& operator+=(char32_t ch) { + std::u32string::operator+=(ch); + return *this; + } + + string& operator+=(const char* s) { + std::u32string::operator+=(fromUtf8(s, std::strlen(s))); + return *this; + } + + // ---- Comparison with char* (UTF-8) ---- + + bool operator==(const char* s) const { return *this == string(s); } + bool operator!=(const char* s) const { return !(*this == string(s)); } + + friend bool operator==(const char* lhs, const string& rhs) { return rhs == lhs; } + friend bool operator!=(const char* lhs, const string& rhs) { return !(rhs == lhs); } + + // ---- Ordering (for std::map keys) ---- + + bool operator<(const string& other) const { + return static_cast(*this) < + static_cast(other); + } + + // ---- Stream output (writes UTF-8) ---- + + friend std::ostream& operator<<(std::ostream& os, const string& s) { + return os << s.toUtf8(); + } +}; + +// ---- Unicode character classification ---- + +// Unicode-aware isLetter: true for any Unicode letter (Lu, Ll, Lt, Lm, Lo categories) +inline bool isLetter(char32_t ch) { + if (ch < 128) return std::isalpha(static_cast(ch)) != 0; + // Latin Extended / IPA / Spacing Modifier Letters + if (ch >= 0x00C0 && ch <= 0x024F) return true; + // Greek and Coptic + if (ch >= 0x0370 && ch <= 0x03FF) return true; + // Cyrillic + if (ch >= 0x0400 && ch <= 0x04FF) return true; + // Armenian, Hebrew, Arabic, etc. + if (ch >= 0x0530 && ch <= 0x08FF) return true; + // Devanagari through Myanmar + if (ch >= 0x0900 && ch <= 0x109F) return true; + // Georgian, Hangul Jamo, Ethiopic, Cherokee, etc. + if (ch >= 0x10A0 && ch <= 0x1FFF) return true; + // General use: CJK Unified Ideographs + if (ch >= 0x4E00 && ch <= 0x9FFF) return true; + // Hangul syllables + if (ch >= 0xAC00 && ch <= 0xD7AF) return true; + // Latin Extended Additional and beyond + if (ch >= 0x1E00 && ch <= 0x1EFF) return true; + // Other letter-like regions (simplified check) + if (ch >= 0x2C00 && ch <= 0x2DFF) return true; // Glagolitic, Coptic + if (ch >= 0xA000 && ch <= 0xA4CF) return true; // Yi, Lisu + if (ch >= 0xFB00 && ch <= 0xFDFF) return true; // Alphabetic Presentation Forms, Arabic Forms + // Supplementary planes (Emoji excluded, but rare letters) + if (ch >= 0x10000 && ch <= 0x1007F) return true; // Linear B Syllabary + if (ch >= 0x10080 && ch <= 0x100FF) return true; // Linear B Ideograms + if (ch >= 0x10300 && ch <= 0x1034F) return true; // Old Italic + if (ch >= 0x10400 && ch <= 0x1044F) return true; // Deseret + if (ch >= 0x20000 && ch <= 0x2A6DF) return true; // CJK Unified Ideographs Extension B + return false; +} + +// Unicode-aware isDigit: true for any Unicode digit (Nd category) +inline bool isDigit(char32_t ch) { + if (ch < 128) return std::isdigit(static_cast(ch)) != 0; + // Common Unicode digit ranges (Nd category) + // Arabic-Indic digits + if (ch >= 0x0660 && ch <= 0x0669) return true; + // Extended Arabic-Indic digits + if (ch >= 0x06F0 && ch <= 0x06F9) return true; + // Devanagari digits + if (ch >= 0x0966 && ch <= 0x096F) return true; + // Bengali, Gurmukhi, Gujarati, etc. digits (each block has 0x_966-0x_96F pattern) + if (ch >= 0x09E6 && ch <= 0x09EF) return true; + if (ch >= 0x0A66 && ch <= 0x0A6F) return true; + if (ch >= 0x0AE6 && ch <= 0x0AEF) return true; + if (ch >= 0x0B66 && ch <= 0x0B6F) return true; + if (ch >= 0x0BE6 && ch <= 0x0BEF) return true; + if (ch >= 0x0C66 && ch <= 0x0C6F) return true; + if (ch >= 0x0CE6 && ch <= 0x0CEF) return true; + if (ch >= 0x0D66 && ch <= 0x0D6F) return true; + if (ch >= 0x0E50 && ch <= 0x0E59) return true; + if (ch >= 0x0ED0 && ch <= 0x0ED9) return true; + if (ch >= 0x0F20 && ch <= 0x0F29) return true; + if (ch >= 0x1040 && ch <= 0x1049) return true; + if (ch >= 0xFF10 && ch <= 0xFF19) return true; // Fullwidth digits + return false; +} + +// Unicode-aware isUppercase: true for uppercase letters +inline bool isUppercase(char32_t ch) { + if (ch < 128) return std::isupper(static_cast(ch)) != 0; + // Latin Extended uppercase ranges + if (ch >= 0x00C0 && ch <= 0x00D6) return true; + if (ch >= 0x00D8 && ch <= 0x00DE) return true; + // Many Unicode uppercase letters in Lu category follow patterns + // Latin Extended-A (even code points are often uppercase) + if (ch >= 0x0100 && ch <= 0x017E && (ch % 2 == 0)) return true; + // Latin Extended-B + if (ch >= 0x01A0 && ch <= 0x01AF && (ch % 2 == 0)) return true; + // Greek uppercase + if (ch >= 0x0391 && ch <= 0x03A1) return true; + if (ch >= 0x03A3 && ch <= 0x03A9) return true; + // Cyrillic uppercase + if (ch >= 0x0410 && ch <= 0x042F) return true; + return false; +} + +} // namespace egg + +// Allow egg::string as key in std::unordered_map +namespace std { +template<> +struct hash { + size_t operator()(const egg::string& s) const { + return hash()(s); + } +}; +} // namespace std + +#endif // _EGG_STRING_H_ From e26b96e4e243bff7092af2247f0b0426b8fccd5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Tue, 10 Mar 2026 00:13:58 -0300 Subject: [PATCH 02/15] Add AST nodes for Smalltalk compiler - Introduced SMessageNode to represent message send nodes in the AST. - Implemented SMethodNode for method definitions, including method building and literal extraction. - Created SNumberNode and SStringNode for number and string literals, respectively. - Added SReturnNode for handling return statements in methods. - Developed SPragmaNode to manage pragmas, including primitive and FFI pragmas. - Established SSelectorNode for message selectors, supporting binary and keyword selectors. - Enhanced SParseNode and SScriptNode as base classes for AST nodes, providing common functionality. - Introduced SParseNodeVisitor interface for traversing the AST. - Implemented LiteralValue struct to represent various literal types in Smalltalk. --- runtime/cpp/Compiler/AST/SAssignmentNode.cpp | 29 +++ runtime/cpp/Compiler/AST/SAssignmentNode.h | 61 +++++ runtime/cpp/Compiler/AST/SBlockNode.cpp | 68 ++++++ runtime/cpp/Compiler/AST/SBlockNode.h | 50 ++++ runtime/cpp/Compiler/AST/SBraceNode.cpp | 103 ++++++++ runtime/cpp/Compiler/AST/SBraceNode.h | 56 +++++ .../cpp/Compiler/AST/SCascadeMessageNode.cpp | 14 ++ .../cpp/Compiler/AST/SCascadeMessageNode.h | 35 +++ runtime/cpp/Compiler/AST/SCascadeNode.cpp | 36 +++ runtime/cpp/Compiler/AST/SCascadeNode.h | 48 ++++ runtime/cpp/Compiler/AST/SCommentNode.cpp | 18 ++ runtime/cpp/Compiler/AST/SCommentNode.h | 36 +++ runtime/cpp/Compiler/AST/SIdentifierNode.cpp | 91 +++++++ runtime/cpp/Compiler/AST/SIdentifierNode.h | 58 +++++ runtime/cpp/Compiler/AST/SLiteralNode.cpp | 19 ++ runtime/cpp/Compiler/AST/SLiteralNode.h | 51 ++++ runtime/cpp/Compiler/AST/SMessageNode.cpp | 99 ++++++++ runtime/cpp/Compiler/AST/SMessageNode.h | 60 +++++ runtime/cpp/Compiler/AST/SMethodNode.cpp | 192 +++++++++++++++ runtime/cpp/Compiler/AST/SMethodNode.h | 58 +++++ runtime/cpp/Compiler/AST/SNumberNode.cpp | 15 ++ runtime/cpp/Compiler/AST/SNumberNode.h | 46 ++++ runtime/cpp/Compiler/AST/SParseNode.cpp | 49 ++++ runtime/cpp/Compiler/AST/SParseNode.h | 101 ++++++++ runtime/cpp/Compiler/AST/SParseNodeVisitor.h | 54 +++++ runtime/cpp/Compiler/AST/SPragmaNode.cpp | 23 ++ runtime/cpp/Compiler/AST/SPragmaNode.h | 78 ++++++ runtime/cpp/Compiler/AST/SReturnNode.cpp | 24 ++ runtime/cpp/Compiler/AST/SReturnNode.h | 38 +++ runtime/cpp/Compiler/AST/SScriptNode.cpp | 75 ++++++ runtime/cpp/Compiler/AST/SScriptNode.h | 65 +++++ runtime/cpp/Compiler/AST/SSelectorNode.cpp | 23 ++ runtime/cpp/Compiler/AST/SSelectorNode.h | 52 ++++ runtime/cpp/Compiler/AST/SStringNode.cpp | 15 ++ runtime/cpp/Compiler/AST/SStringNode.h | 29 +++ runtime/cpp/Compiler/LiteralValue.h | 227 ++++++++++++++++++ 36 files changed, 2096 insertions(+) create mode 100644 runtime/cpp/Compiler/AST/SAssignmentNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SAssignmentNode.h create mode 100644 runtime/cpp/Compiler/AST/SBlockNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SBlockNode.h create mode 100644 runtime/cpp/Compiler/AST/SBraceNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SBraceNode.h create mode 100644 runtime/cpp/Compiler/AST/SCascadeMessageNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SCascadeMessageNode.h create mode 100644 runtime/cpp/Compiler/AST/SCascadeNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SCascadeNode.h create mode 100644 runtime/cpp/Compiler/AST/SCommentNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SCommentNode.h create mode 100644 runtime/cpp/Compiler/AST/SIdentifierNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SIdentifierNode.h create mode 100644 runtime/cpp/Compiler/AST/SLiteralNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SLiteralNode.h create mode 100644 runtime/cpp/Compiler/AST/SMessageNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SMessageNode.h create mode 100644 runtime/cpp/Compiler/AST/SMethodNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SMethodNode.h create mode 100644 runtime/cpp/Compiler/AST/SNumberNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SNumberNode.h create mode 100644 runtime/cpp/Compiler/AST/SParseNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SParseNode.h create mode 100644 runtime/cpp/Compiler/AST/SParseNodeVisitor.h create mode 100644 runtime/cpp/Compiler/AST/SPragmaNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SPragmaNode.h create mode 100644 runtime/cpp/Compiler/AST/SReturnNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SReturnNode.h create mode 100644 runtime/cpp/Compiler/AST/SScriptNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SScriptNode.h create mode 100644 runtime/cpp/Compiler/AST/SSelectorNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SSelectorNode.h create mode 100644 runtime/cpp/Compiler/AST/SStringNode.cpp create mode 100644 runtime/cpp/Compiler/AST/SStringNode.h create mode 100644 runtime/cpp/Compiler/LiteralValue.h diff --git a/runtime/cpp/Compiler/AST/SAssignmentNode.cpp b/runtime/cpp/Compiler/AST/SAssignmentNode.cpp new file mode 100644 index 00000000..0733c87a --- /dev/null +++ b/runtime/cpp/Compiler/AST/SAssignmentNode.cpp @@ -0,0 +1,29 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SAssignmentNode.h" +#include "SIdentifierNode.h" +#include "SParseNodeVisitor.h" + +namespace Egg { + +SAssignmentNode::SAssignmentNode(SSmalltalkCompiler* compiler) + : SParseNode(compiler), _expression(nullptr) { + initialize(); +} + +void SAssignmentNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitAssignment_(this); +} + +void SAssignmentNode::nodesDo_(std::function block, bool includeDeclarations) { + SParseNode::nodesDo_(block, includeDeclarations); + for (auto assignee : _assignees) { + if (assignee) assignee->nodesDo_(block, includeDeclarations); + } + if (_expression) _expression->nodesDo_(block, includeDeclarations); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SAssignmentNode.h b/runtime/cpp/Compiler/AST/SAssignmentNode.h new file mode 100644 index 00000000..91eb5b3f --- /dev/null +++ b/runtime/cpp/Compiler/AST/SAssignmentNode.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _ASSIGNMENT_NODE_H_ +#define _ASSIGNMENT_NODE_H_ + +#include "SParseNode.h" +#include +#include + +namespace Egg { + +class SIdentifierNode; + +/** + * Assignment node + * Corresponds to SAssignmentNode in Smalltalk + */ +class SAssignmentNode : public SParseNode { +private: + std::vector _assignees; + std::vector _operators; // Placeholder for SDelimiterToken or similar + SParseNode* _expression; + +public: + SAssignmentNode(SSmalltalkCompiler* compiler); + virtual ~SAssignmentNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + void assign_operator_(SIdentifierNode* anIdentifierNode, void* aSDelimiterToken) { + _assignees.push_back(anIdentifierNode); + _operators.push_back(aSDelimiterToken); + } + void assign_with_operator_(SIdentifierNode* anIdentifierNode, SParseNode* aParseNode, void* aSDelimiterToken) { + _assignees.push_back(anIdentifierNode); + _operators.push_back(aSDelimiterToken); + _expression = aParseNode; + } + + const std::vector& assignees() const { return _assignees; } + const std::vector& operators() const { return _operators; } + SParseNode* expression() const { return _expression; } + void expression_(SParseNode* expr) { _expression = expr; } + + bool isAssignment() const override { return true; } + bool hasAssign() const override { return true; } + + void nodesDo_(std::function block, bool includeDeclarations = false) override; + + void initialize() { + _assignees.clear(); + _operators.clear(); + } +}; + +} // namespace Egg + +#endif // _ASSIGNMENT_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SBlockNode.cpp b/runtime/cpp/Compiler/AST/SBlockNode.cpp new file mode 100644 index 00000000..7ec9849a --- /dev/null +++ b/runtime/cpp/Compiler/AST/SBlockNode.cpp @@ -0,0 +1,68 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SBlockNode.h" +#include "SIdentifierNode.h" +#include "SParseNodeVisitor.h" +#include "../Binding/ScriptScope.h" +#include "../Binding/BlockScope.h" +#include "../Binding/ArgumentBinding.h" + +namespace Egg { + +SBlockNode::SBlockNode(SSmalltalkCompiler* compiler) + : SScriptNode(compiler), _inlined(false), _index(0), _parent(nullptr) { + // Matching Smalltalk's SBlockNode >> initialize which does: + // scope := BlockScope on: self + auto scope = new BlockScope(); + scope->script_(this); + _scope = scope; +} + +void SBlockNode::beInlined_() { + _inlined = true; + for (auto arg : _arguments) { + auto argBinding = dynamic_cast(arg->binding()); + if (argBinding) { + argBinding->beInlined_(); + } + } +} + +void SBlockNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitBlock_(this); +} + +void SBlockNode::parent_(SScriptNode* p) { + _parent = p; + if (p) p->addChild_(this); +} + +SScriptNode* SBlockNode::realScript() { + return _inlined ? (_parent ? _parent->realScript() : nullptr) : this; +} + +void SBlockNode::captureHome() { + if (_scope) { + _scope->captureEnvironment_(ast()); + } +} + +bool SBlockNode::usesHome() const { + if (_inlined) { + for (auto child : _children) { + auto block = static_cast(child); + if (block->usesHome()) { + return true; + } + } + return false; + } else { + auto blockScope = dynamic_cast(_scope); + return blockScope && blockScope->capturesHome_(); + } +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SBlockNode.h b/runtime/cpp/Compiler/AST/SBlockNode.h new file mode 100644 index 00000000..95b434ba --- /dev/null +++ b/runtime/cpp/Compiler/AST/SBlockNode.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _BLOCK_NODE_H_ +#define _BLOCK_NODE_H_ + +#include "SScriptNode.h" + +namespace Egg { + +/** + * Block node (closure) + * Corresponds to SBlockNode in Smalltalk + */ +class SBlockNode : public SScriptNode { +private: + bool _inlined; + int _index; + SScriptNode* _parent; + +public: + SBlockNode(SSmalltalkCompiler* compiler); + virtual ~SBlockNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + bool isInlined() const { return _inlined; } + void beInlined_(); + + int index() const { return _index; } + void index_(int idx) { _index = idx; } + + SScriptNode* parent() const { return _parent; } + void parent_(SScriptNode* p); + + bool isBlock() const override { return true; } + bool isEvaluable() const override { return _arguments.empty(); } + bool isNullary() const { return _arguments.empty(); } + + SScriptNode* realScript() override; + void captureHome() override; + + bool usesHome() const; +}; + +} // namespace Egg + +#endif // _BLOCK_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SBraceNode.cpp b/runtime/cpp/Compiler/AST/SBraceNode.cpp new file mode 100644 index 00000000..136dd6f6 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SBraceNode.cpp @@ -0,0 +1,103 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SBraceNode.h" +#include "SParseNodeVisitor.h" +#include "SMessageNode.h" +#include "SCascadeNode.h" +#include "SCascadeMessageNode.h" +#include "SIdentifierNode.h" +#include "SSelectorNode.h" +#include "SNumberNode.h" +#include "../SSmalltalkCompiler.h" +#include "../LiteralValue.h" + +namespace Egg { +SBraceNode::SBraceNode(SSmalltalkCompiler* compiler) + : SParseNode(compiler), _message(nullptr) { +} + +void SBraceNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitBrace_(this); +} + +void SBraceNode::addElement_(SParseNode* elem) { + _elements.push_back(elem); +} + +SParseNode* SBraceNode::asSMessageNode() { + if (_message) return _message; + _message = expanded(); + return _message; +} + +SParseNode* SBraceNode::expanded() { + auto receiver = _compiler->identifierNode(); + receiver->name_("Array"); + int n = _elements.size(); + auto new_ = _compiler->selectorNode(); + new_->symbol_("new:"); + auto argument = _compiler->numericSLiteralNode(); + argument->value_(std::to_string(n)); + auto array = _compiler->messageNode(); + array->receiver_(receiver); + array->selector_(new_); + std::vector arrayArgs; + arrayArgs.push_back(argument); + array->arguments_(arrayArgs); + int i = 0; + std::vector messages; + for (auto elem : _elements) { + i = i + 1; + auto msg = _compiler->cascadeSMessageNode(); + msg->position_(elem->position()); + auto sel = _compiler->selectorNode(); + sel->symbol_("at:put:"); + auto idx = _compiler->numericSLiteralNode(); + idx->value_(std::to_string(i)); + std::vector args; + args.push_back(idx); + args.push_back(elem); + msg->selector_(sel); + msg->arguments_(args); + messages.push_back(msg); + } + auto you = _compiler->selectorNode(); + you->symbol_("yourself"); + auto yourself = _compiler->cascadeSMessageNode(); + yourself->selector_(you); + std::vector yourselfArgs; + yourself->arguments_(yourselfArgs); + messages.push_back(yourself); + auto cascade = _compiler->cascadeNode(); + cascade->receiver_(array); + for (auto msg : messages) { + static_cast(msg)->cascade_(cascade); + } + std::vector cascadeMessages; + for (auto msg : messages) { + cascadeMessages.push_back(static_cast(msg)); + } + cascade->messages_(cascadeMessages); + return cascade; +} + +bool SBraceNode::isEvaluable() const { + for (auto elem : _elements) { + if (!elem->isEvaluable()) { + return false; + } + } + return true; +} + +void SBraceNode::nodesDo_(std::function block, bool includeDeclarations) { + SParseNode::nodesDo_(block, includeDeclarations); + for (auto elem : _elements) { + if (elem) elem->nodesDo_(block, includeDeclarations); + } +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SBraceNode.h b/runtime/cpp/Compiler/AST/SBraceNode.h new file mode 100644 index 00000000..85232783 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SBraceNode.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _BRACE_NODE_H_ +#define _BRACE_NODE_H_ + +#include "SParseNode.h" +#include +#include + +namespace Egg { + +/** + * Brace node ({expr1. expr2}) + * Corresponds to SBraceNode in Smalltalk + */ +class SMessageNode; +class SCascadeNode; +class SIdentifierNode; +class SSelectorNode; +class SNumberNode; +class SCascadeMessageNode; + +class SBraceNode : public SParseNode { +private: + std::vector _elements; + SParseNode* _message; + +public: + SBraceNode(SSmalltalkCompiler* compiler); + virtual ~SBraceNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + std::vector& elements() { return _elements; } + const std::vector& elements() const { return _elements; } + void elements_(const std::vector& elems) { _elements = elems; } + void addElement_(SParseNode* elem); + + SParseNode* message() { return _message; } + void message_(SParseNode* msg) { _message = msg; } + + SParseNode* asSMessageNode(); + SParseNode* expanded(); + + bool isBrace() const override { return true; } + bool isEvaluable() const override; + + void nodesDo_(std::function block, bool includeDeclarations = false) override; +}; + +} // namespace Egg + +#endif // _BRACE_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SCascadeMessageNode.cpp b/runtime/cpp/Compiler/AST/SCascadeMessageNode.cpp new file mode 100644 index 00000000..83a5574a --- /dev/null +++ b/runtime/cpp/Compiler/AST/SCascadeMessageNode.cpp @@ -0,0 +1,14 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SCascadeMessageNode.h" + +namespace Egg { + +SCascadeMessageNode::SCascadeMessageNode(SSmalltalkCompiler* compiler) + : SMessageNode(compiler), _cascade(nullptr) { +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SCascadeMessageNode.h b/runtime/cpp/Compiler/AST/SCascadeMessageNode.h new file mode 100644 index 00000000..e4c7ef85 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SCascadeMessageNode.h @@ -0,0 +1,35 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _CASCADE_MESSAGE_NODE_H_ +#define _CASCADE_MESSAGE_NODE_H_ + +#include "SMessageNode.h" + +namespace Egg { + +class SCascadeNode; + +/** + * Cascade message node (message in a cascade, shares receiver) + * Corresponds to SCascadeMessageNode in Smalltalk + */ +class SCascadeMessageNode : public SMessageNode { +private: + SCascadeNode* _cascade; + +public: + SCascadeMessageNode(SSmalltalkCompiler* compiler); + virtual ~SCascadeMessageNode() {} + + SCascadeNode* cascade() const { return _cascade; } + void cascade_(SCascadeNode* c) { _cascade = c; } + + bool isCascadeMessage() const override { return true; } +}; + +} // namespace Egg + +#endif // _CASCADE_MESSAGE_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SCascadeNode.cpp b/runtime/cpp/Compiler/AST/SCascadeNode.cpp new file mode 100644 index 00000000..4600a19a --- /dev/null +++ b/runtime/cpp/Compiler/AST/SCascadeNode.cpp @@ -0,0 +1,36 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SCascadeNode.h" +#include "SMessageNode.h" +#include "SParseNodeVisitor.h" + +namespace Egg { + +SCascadeNode::SCascadeNode(SSmalltalkCompiler* compiler) + : SParseNode(compiler), _receiver(nullptr) { +} + +void SCascadeNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitCascade_(this); +} + +bool SCascadeNode::hasAssign() const { + if (_receiver && _receiver->hasAssign()) return true; + for (auto msg : _messages) { + if (msg && msg->hasAssign()) return true; + } + return false; +} + +void SCascadeNode::nodesDo_(std::function block, bool includeDeclarations) { + SParseNode::nodesDo_(block, includeDeclarations); + if (_receiver) _receiver->nodesDo_(block, includeDeclarations); + for (auto msg : _messages) { + if (msg) msg->nodesDo_(block, includeDeclarations); + } +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SCascadeNode.h b/runtime/cpp/Compiler/AST/SCascadeNode.h new file mode 100644 index 00000000..12b78e8a --- /dev/null +++ b/runtime/cpp/Compiler/AST/SCascadeNode.h @@ -0,0 +1,48 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _CASCADE_NODE_H_ +#define _CASCADE_NODE_H_ + +#include "SParseNode.h" +#include +#include + +namespace Egg { + +class SMessageNode; + +/** + * Cascade node (multiple messages to same receiver) + * Corresponds to SCascadeNode in Smalltalk + */ +class SCascadeNode : public SParseNode { +private: + SParseNode* _receiver; + std::vector _messages; + +public: + SCascadeNode(SSmalltalkCompiler* compiler); + virtual ~SCascadeNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + SParseNode* receiver() const { return _receiver; } + void receiver_(SParseNode* r) { _receiver = r; } + + const std::vector& messages() const { return _messages; } + void messages_(const std::vector& msgs) { _messages = msgs; } + void addMessage_(SMessageNode* msg) { _messages.push_back(msg); } + + bool isCascade() const override { return true; } + + bool hasAssign() const override; + + void nodesDo_(std::function block, bool includeDeclarations = false) override; +}; + +} // namespace Egg + +#endif // _CASCADE_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SCommentNode.cpp b/runtime/cpp/Compiler/AST/SCommentNode.cpp new file mode 100644 index 00000000..9949328f --- /dev/null +++ b/runtime/cpp/Compiler/AST/SCommentNode.cpp @@ -0,0 +1,18 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SCommentNode.h" +#include "SParseNodeVisitor.h" + +namespace Egg { + +SCommentNode::SCommentNode(SSmalltalkCompiler* compiler) : SParseNode(compiler) { +} + +void SCommentNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitComment_(this); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SCommentNode.h b/runtime/cpp/Compiler/AST/SCommentNode.h new file mode 100644 index 00000000..ca860c2f --- /dev/null +++ b/runtime/cpp/Compiler/AST/SCommentNode.h @@ -0,0 +1,36 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _COMMENT_NODE_H_ +#define _COMMENT_NODE_H_ + +#include "SParseNode.h" +#include + +namespace Egg { + +/** + * Comment node + * Corresponds to SCommentNode in Smalltalk + */ +class SCommentNode : public SParseNode { +private: + egg::string _value; + +public: + SCommentNode(SSmalltalkCompiler* compiler); + virtual ~SCommentNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + egg::string value() const { return _value; } + void value_(const egg::string& aString) { _value = aString; } + + bool isComment() const override { return true; } +}; + +} // namespace Egg + +#endif // _COMMENT_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SIdentifierNode.cpp b/runtime/cpp/Compiler/AST/SIdentifierNode.cpp new file mode 100644 index 00000000..bfd6e5d1 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SIdentifierNode.cpp @@ -0,0 +1,91 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SIdentifierNode.h" +#include "SParseNodeVisitor.h" +#include "../SSmalltalkCompiler.h" +#include "../LiteralValue.h" +#include + +#include "../Binding/Binding.h" +#include "../Binding/ArgumentBinding.h" +#include "../Binding/TemporaryBinding.h" +#include "../Binding/Scope.h" +#include "../Binding/ScriptScope.h" + +namespace Egg { + +SIdentifierNode::SIdentifierNode(SSmalltalkCompiler* compiler) + : SParseNode(static_cast(compiler)), _binding(nullptr) { +} + +void SIdentifierNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitIdentifier_(this); +} + +bool SIdentifierNode::isEvaluable() const { + return isIdentifierLiteral(); +} + +bool SIdentifierNode::isLocal() const { + if (!_binding) return false; + auto k = _binding->kind(); + return k == Binding::Kind::Variable || k == Binding::Kind::Argument || k == Binding::Kind::Temporary; +} + +bool SIdentifierNode::isIdentifierLiteral() const { + if (!_binding) return false; + if (!_binding->isDynamic()) return _binding->isLiteral(); + return false; +} + +bool SIdentifierNode::isMethodArgument() const { + return false; +} + +bool SIdentifierNode::isMethodTemporary() const { + return false; +} + +bool SIdentifierNode::isSelf() const { + return _binding && _binding->name() == "self"; +} + +bool SIdentifierNode::isSuper() const { + return _binding && _binding->name() == "super"; +} + +void SIdentifierNode::beAssigned() { + if (_binding && _binding->canBeAssigned()) return; + std::cerr << "Cannot assign to " << _binding->name() << std::endl; +} + +void SIdentifierNode::checkLowercase() { + if (!_name.empty()) { + char32_t first = _name[0]; + if (egg::isLetter(first) && egg::isUppercase(first)) { + std::cerr << "Warning: variable '" << _name << "' should start with a lowercase letter." << std::endl; + } + } +} + +void SIdentifierNode::defineArgumentIn_(Scope* scope) { + if (scope) { + _binding = scope->defineArgument_(_name); + } +} + +void SIdentifierNode::defineTemporaryIn_(Scope* scope) { + if (scope) { + _binding = scope->defineTemporary_(_name); + } +} + +Binding* SIdentifierNode::resolveAssigning_(bool aBoolean) { + _binding = _compiler->activeScope()->resolve_(_name); + return _binding; +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SIdentifierNode.h b/runtime/cpp/Compiler/AST/SIdentifierNode.h new file mode 100644 index 00000000..f72f3754 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SIdentifierNode.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _IDENTIFIER_NODE_H_ +#define _IDENTIFIER_NODE_H_ + +#include "SParseNode.h" +#include + +namespace Egg { + +class Binding; + +/** + * Identifier node (variable reference) + * Corresponds to SIdentifierNode in Smalltalk + */ +class SIdentifierNode : public SParseNode { +private: + egg::string _name; + Binding* _binding; + bool _assigned = false; + +public: + SIdentifierNode(SSmalltalkCompiler* compiler); + virtual ~SIdentifierNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + egg::string name() const { return _name; } + void name_(const egg::string& n) { _name = n; } + + Binding* binding() const { return _binding; } + void binding_(Binding* b) { _binding = b; } + + bool isIdentifier() const override { return true; } + bool isImmediate() const override { return true; } + bool isEvaluable() const override; + bool isLocal() const; + bool isSelf() const override; + bool isSuper() const override; + + bool isIdentifierLiteral() const; + bool isMethodArgument() const override; + bool isMethodTemporary() const override; + + Binding* resolveAssigning_(bool aBoolean); + void beAssigned(); + void checkLowercase(); + void defineArgumentIn_(Scope* scope); + void defineTemporaryIn_(Scope* scope); +}; + +} // namespace Egg + +#endif // _IDENTIFIER_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SLiteralNode.cpp b/runtime/cpp/Compiler/AST/SLiteralNode.cpp new file mode 100644 index 00000000..f0db7f9f --- /dev/null +++ b/runtime/cpp/Compiler/AST/SLiteralNode.cpp @@ -0,0 +1,19 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SLiteralNode.h" +#include "SParseNodeVisitor.h" + +namespace Egg { + +SLiteralNode::SLiteralNode(SSmalltalkCompiler* compiler) + : SParseNode(compiler) { +} + +void SLiteralNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitLiteral_(this); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SLiteralNode.h b/runtime/cpp/Compiler/AST/SLiteralNode.h new file mode 100644 index 00000000..c555e9ae --- /dev/null +++ b/runtime/cpp/Compiler/AST/SLiteralNode.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _LITERAL_NODE_H_ +#define _LITERAL_NODE_H_ + +#include "SParseNode.h" +#include "../LiteralValue.h" +#include + +namespace Egg { + +/** + * Literal node (number, string, symbol, etc.) + * Corresponds to SLiteralNode in Smalltalk + */ +class SLiteralNode : public SParseNode { +private: + LiteralValue _litValue; + +public: + SLiteralNode(SSmalltalkCompiler* compiler); + virtual ~SLiteralNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + // Typed literal value + const LiteralValue& literalValue() const { return _litValue; } + void literalValue_(const LiteralValue& v) { _litValue = v; } + void literalValue_(LiteralValue&& v) { _litValue = std::move(v); } + + // Backwards‑compatible string accessors + egg::string value() const { return _litValue.printString(); } + void value_(const egg::string& v) { + // Legacy: if called with a plain string, store as String + _litValue = LiteralValue::fromString(v); + } + + void beSymbol_() { _litValue.tag = LiteralValue::Symbol; } + bool hasSymbol() const { return _litValue.isSymbol(); } + + bool isLiteral() const override { return true; } + bool isEvaluable() const override { return true; } + bool isImmediate() const override { return true; } +}; + +} // namespace Egg + +#endif // _LITERAL_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SMessageNode.cpp b/runtime/cpp/Compiler/AST/SMessageNode.cpp new file mode 100644 index 00000000..586e5c6e --- /dev/null +++ b/runtime/cpp/Compiler/AST/SMessageNode.cpp @@ -0,0 +1,99 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SMessageNode.h" +#include "SParseNodeVisitor.h" +#include "SIdentifierNode.h" +#include "SSelectorNode.h" +#include "../Binding/Binding.h" +#include "../SSmalltalkCompiler.h" + +namespace Egg { + +SMessageNode::SMessageNode(SSmalltalkCompiler* compiler) + : SParseNode(compiler), _receiver(nullptr), _selector(nullptr), _inlined(false) { +} + +void SMessageNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitMessage_(this); +} + +bool SMessageNode::hasAssign() const { + if (_receiver && _receiver->hasAssign()) return true; + for (auto arg : _arguments) { + if (arg && arg->hasAssign()) return true; + } + return false; +} + +bool SMessageNode::hasVolatileArguments() const { + for (auto arg : _arguments) { + if (!arg) continue; + if (arg->isIdentifier()) { + auto id = static_cast(arg); + if (id->binding() && id->binding()->canBeAssigned()) { + return true; + } + } else { + if (!arg->isBlock() && !arg->isLiteral()) { + return true; + } + } + } + return false; +} + +bool SMessageNode::hasVolatileReceiver() const { + if (_compiler->hasBlocks()) { + return true; + } + if (!_receiver || !_receiver->isMethodTemporary()) { + return true; + } + for (auto arg : _arguments) { + if (arg && arg->hasAssign()) { + return true; + } + } + return false; +} + +bool SMessageNode::needsStrictEvaluationOrder() const { + if (_arguments.size() == 0) return false; + if (!_receiver) return false; + if (_receiver->isBlock()) return false; + if (_receiver->isLiteral()) return false; + if (_receiver->isSelf()) return false; + if (_receiver->isSuper()) return false; + if (_receiver->isMethodArgument()) return false; + if (!hasVolatileReceiver()) return false; + + bool allImmediateOrBlock = _receiver->isImmediate(); + if (allImmediateOrBlock) { + for (auto arg : _arguments) { + if (!arg || (!arg->isImmediate() && !arg->isBlock())) { + allImmediateOrBlock = false; + break; + } + } + } + if (allImmediateOrBlock) return false; + + if (_receiver->hasAssign()) return true; + if (_receiver->isMessage()) return true; + + return hasVolatileArguments(); +} + +void SMessageNode::nodesDo_(std::function block, bool includeDeclarations) { + SParseNode::nodesDo_(block, includeDeclarations); + if (_receiver) _receiver->nodesDo_(block, includeDeclarations); + for (auto arg : _arguments) { + if (arg) arg->nodesDo_(block, includeDeclarations); + } + if (_selector) _selector->nodesDo_(block, includeDeclarations); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SMessageNode.h b/runtime/cpp/Compiler/AST/SMessageNode.h new file mode 100644 index 00000000..7811bb43 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SMessageNode.h @@ -0,0 +1,60 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _MESSAGE_NODE_H_ +#define _MESSAGE_NODE_H_ + +#include "SParseNode.h" +#include +#include + +namespace Egg { + +class SSelectorNode; + +/** + * Message send node + * Corresponds to SMessageNode in Smalltalk + */ +class SMessageNode : public SParseNode { +private: + SParseNode* _receiver; + SSelectorNode* _selector; + std::vector _arguments; + bool _inlined; + +public: + SMessageNode(SSmalltalkCompiler* compiler); + virtual ~SMessageNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + SParseNode* receiver() const { return _receiver; } + void receiver_(SParseNode* r) { _receiver = r; } + + SSelectorNode* selector() const { return _selector; } + void selector_(SSelectorNode* s) { _selector = s; } + + const std::vector& arguments() const { return _arguments; } + void arguments_(const std::vector& args) { _arguments = args; } + void addArgument_(SParseNode* arg) { _arguments.push_back(arg); } + + bool isInlined() const { return _inlined; } + void beInlined_() { _inlined = true; } + + bool isMessage() const override { return true; } + virtual bool isCascadeMessage() const override { return false; } + + bool hasAssign() const override; + bool hasVolatileArguments() const; + bool hasVolatileReceiver() const; + bool needsStrictEvaluationOrder() const; + + void nodesDo_(std::function block, bool includeDeclarations = false) override; +}; + +} // namespace Egg + +#endif // _MESSAGE_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SMethodNode.cpp b/runtime/cpp/Compiler/AST/SMethodNode.cpp new file mode 100644 index 00000000..b125be0d --- /dev/null +++ b/runtime/cpp/Compiler/AST/SMethodNode.cpp @@ -0,0 +1,192 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SMethodNode.h" +#include "SParseNodeVisitor.h" +#include "SPragmaNode.h" +#include "SBlockNode.h" +#include "SLiteralNode.h" +#include "SMessageNode.h" +#include "SBraceNode.h" +#include "SIdentifierNode.h" +#include "SSelectorNode.h" +#include "../Backend/SCompiledMethod.h" +#include "../Backend/SCompiledBlock.h" +#include "../Binding/BlockScope.h" +#include "../SSmalltalkCompiler.h" +#include "../Binding/MethodScope.h" +#include + +namespace Egg { + +SMethodNode::SMethodNode(SSmalltalkCompiler* compiler) + : SScriptNode(compiler), _selector(nullptr), _pragma(nullptr) { + // Matching Smalltalk's SMethodNode >> compiler: which does: + // scope := MethodScope new script: self + auto scope = new MethodScope(); + scope->script_(this); + _scope = scope; +} + +void SMethodNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitMethod_(this); +} + +void SMethodNode::nodesDo_(std::function block, bool includeDeclarations) { + SScriptNode::nodesDo_(block, includeDeclarations); + if (includeDeclarations && _selector) { + _selector->nodesDo_(block, includeDeclarations); + } + if (_pragma) { + _pragma->nodesDo_(block, includeDeclarations); + } +} + +SCompiledMethod* SMethodNode::buildMethod() { + auto lits = literals(); + auto cm = SCompiledMethod::withAll(lits); + + cm->blockCount_(_compiler->blockCount()); + cm->tempCount_(_scope->stackSize()); + cm->argumentCount_(_arguments.size()); + cm->environmentCount_(_scope->environmentSize()); + cm->capturesSelf_(_scope->capturesSelf()); + cm->hasEnvironment_(needsEnvironment()); + cm->hasFrame_(needsFrame()); + cm->selector_(selectorString()); + cm->source_(_compiler->sourceCode()); + cm->classBinding_(_compiler->frontend() ? _compiler->frontend()->classBinding() : nullptr); + + cm->pragma_(_pragma); + + auto blocks = cm->blocks(); + for (auto block : blocks) { + block->method_(cm); + } + + + return cm; +} + +std::vector SMethodNode::literals() { + std::vector lits; + + auto addUnique = [&](const LiteralValue& lv) { + for (const auto& existing : lits) { + if (existing == lv) return; + } + lits.push_back(lv); + }; + + // Add pragma name if used + if (_pragma && _pragma->isUsed()) { + const egg::string& pragmaName = _pragma->name(); + if (!pragmaName.empty()) { + addUnique(LiteralValue::fromSymbol(pragmaName)); + } + } + + nodesDo_([&](SParseNode* n) { + if (n->isLiteral()) { + auto litNode = static_cast(n); + const auto& lv = litNode->literalValue(); + if (!lv.isNone()) { + // Skip small integers (encoded directly in treecode) + if (lv.tag == LiteralValue::Integer && + lv.intVal >= -16384 && lv.intVal <= 16383) { + // Don't add small integers to literal pool + } else { + addUnique(lv); + } + } + } + if (n->isMessage()) { + auto msg = static_cast(n); + auto sel = msg->selector(); + if (sel && sel->hasSymbol()) { + addUnique(LiteralValue::fromSymbol(sel->symbol())); + } + } + if (n->isBrace()) { + auto brace = static_cast(n); + if (!brace->isLiteral()) { + addUnique(LiteralValue::fromSymbol("Array")); + addUnique(LiteralValue::fromSymbol("new:")); + addUnique(LiteralValue::fromSymbol("at:put:")); + addUnique(LiteralValue::fromSymbol("yourself")); + } + } + if (n->isIdentifier()) { + auto id = static_cast(n); + if (id->binding()) { + auto bindingLit = id->binding()->literal(); + if (bindingLit) { + addUnique(*bindingLit); + } + } + } + if (n->isBlock()) { + auto block = static_cast(n); + if (!block->isInlined()) { + // Build block metadata matching Smalltalk's SBlockNode>>buildBlock + int id = block->index(); + int argCount = block->arguments().size(); + int tempCount = 0; + int envCount = 0; + bool capturesSelf = false; + bool capturesHome = false; + + auto scope = block->scope(); + if (scope) { + tempCount = scope->stackSize(); + envCount = scope->environmentSize(); + capturesSelf = scope->capturesSelf(); + auto blockScope = dynamic_cast(scope); + if (blockScope) { + capturesHome = blockScope->capturesHome_(); + } + } + + addUnique(LiteralValue::fromBlock(id, argCount, tempCount, + envCount, capturesSelf, capturesHome)); + } + } + }, false); + + return lits; +} + +SCompiledMethod* SMethodNode::methodClass() { + if (_pragma) { + } + return new SCompiledMethod(); +} + +bool SMethodNode::needsEnvironment() const { + if (_scope->environmentSize() > 0) { + return true; + } + for (auto child : _children) { + auto block = static_cast(child); + if (block->usesHome()) { + return true; + } + } + return false; +} + +bool SMethodNode::needsFrame() const { + if (_scope->stackSize() > 0) return true; + if (_arguments.size() > 16) return true; + if (_compiler->hasSends()) return true; + if (_compiler->hasBlocks()) return true; + return false; +} + +egg::string SMethodNode::selectorString() const { + return _selector ? _selector->symbol() : ""; +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SMethodNode.h b/runtime/cpp/Compiler/AST/SMethodNode.h new file mode 100644 index 00000000..3ba21822 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SMethodNode.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _METHOD_NODE_H_ +#define _METHOD_NODE_H_ + +#include "SScriptNode.h" +#include "../LiteralValue.h" +#include + +namespace Egg { + +class SPragmaNode; +class SCompiledMethod; +class SSelectorNode; + +/** + * Method node + * Corresponds to SMethodNode in Smalltalk + */ +class SMethodNode : public SScriptNode { +private: + SSelectorNode* _selector; + SPragmaNode* _pragma; + +public: + SMethodNode(SSmalltalkCompiler* compiler); + virtual ~SMethodNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + SSelectorNode* selector() const { return _selector; } + void selector_(SSelectorNode* sel) { _selector = sel; } + + SPragmaNode* pragma() const { return _pragma; } + void pragma_(SPragmaNode* p) { _pragma = p; } + + bool isMethod() const override { return true; } + bool isHeadless() const { return _selector == nullptr; } + + SScriptNode* realScript() override { return this; } + void captureHome() override {} // Empty in method + + SCompiledMethod* buildMethod(); + std::vector literals(); + SCompiledMethod* methodClass(); + bool needsEnvironment() const; + bool needsFrame() const; + egg::string selectorString() const; + + void nodesDo_(std::function block, bool includeDeclarations = false) override; +}; + +} // namespace Egg + +#endif // _METHOD_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SNumberNode.cpp b/runtime/cpp/Compiler/AST/SNumberNode.cpp new file mode 100644 index 00000000..b4e04fab --- /dev/null +++ b/runtime/cpp/Compiler/AST/SNumberNode.cpp @@ -0,0 +1,15 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SNumberNode.h" +#include "SParseNodeVisitor.h" + +namespace Egg { + +void SNumberNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitNumberNode_(this); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SNumberNode.h b/runtime/cpp/Compiler/AST/SNumberNode.h new file mode 100644 index 00000000..646450ab --- /dev/null +++ b/runtime/cpp/Compiler/AST/SNumberNode.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _NUMBER_NODE_H_ +#define _NUMBER_NODE_H_ + +#include "SLiteralNode.h" + +namespace Egg { + +/** + * Number literal node + * Corresponds to SNumberNode in Smalltalk + */ +class SNumberNode : public SLiteralNode { +public: + SNumberNode(SSmalltalkCompiler* compiler) : SLiteralNode(compiler) {} + virtual ~SNumberNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + bool isSNumberNode() const { return true; } + + void negate_() { + const auto& lv = literalValue(); + if (lv.isInteger()) { + literalValue_(LiteralValue::fromInteger(-lv.asInteger())); + } else if (lv.isFloat()) { + literalValue_(LiteralValue::fromFloat(-lv.asFloat())); + } else { + // Legacy fallback + egg::string val = value(); + if (!val.empty() && val[0] != '-') { + value_("-" + val); + } else if (!val.empty() && val[0] == '-') { + value_(val.substr(1)); + } + } + } +}; + +} // namespace Egg + +#endif // _NUMBER_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SParseNode.cpp b/runtime/cpp/Compiler/AST/SParseNode.cpp new file mode 100644 index 00000000..c13eec4a --- /dev/null +++ b/runtime/cpp/Compiler/AST/SParseNode.cpp @@ -0,0 +1,49 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SParseNode.h" +#include "SMethodNode.h" +#include "../SSmalltalkCompiler.h" + +namespace Egg { + +void SParseNode::allNodesDo_includingDeclarations_(std::function block) { + nodesDo_(block, true); +} + +SParseNode* SParseNode::ast() { + return compiler() ? dynamic_cast(compiler()->ast()) : nullptr; +} + +SParseNode* SParseNode::nodesDetect_(std::function predicate, std::function ifAbsent) { + SParseNode* found = nullptr; + nodesDo_([&](SParseNode* node) { + if (!found && predicate(node)) found = node; + }, false); + return found ? found : ifAbsent(); +} + +SParseNode* SParseNode::nodeWithLiteral_(const egg::string& value) { + return nodesDetect_([ + &value + ](SParseNode* n) { + return (n->isLiteral() || n->isSelector()) && n->valueEquals_(value); + }, []() { return nullptr; }); +} + +SParseNode* SParseNode::variableNamed_(const egg::string& name) { + SParseNode* result = nullptr; + allNodesDo_includingDeclarations_([&](SParseNode* node) { + if (node->isIdentifier() && node->nameEquals_(name)) result = node; + }); + return result; +} + +bool SParseNode::valueEquals_(const egg::string&) const { return false; } +bool SParseNode::nameEquals_(const egg::string&) const { return false; } +bool SParseNode::isMethodArgument() const { return false; } +bool SParseNode::isMethodTemporary() const { return false; } + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SParseNode.h b/runtime/cpp/Compiler/AST/SParseNode.h new file mode 100644 index 00000000..7098d94f --- /dev/null +++ b/runtime/cpp/Compiler/AST/SParseNode.h @@ -0,0 +1,101 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _PARSE_NODE_H_ +#define _PARSE_NODE_H_ + +#include +#include +#include +#include +#include "../Stretch.h" +#include "../Parser/SToken.h" + +namespace Egg { + +class SSmalltalkCompiler; +class Binding; +class Scope; +class SParseNodeVisitor; + +/** + * Base class for all parse tree nodes (AST nodes) + * Corresponds to SParseNode in Smalltalk + */ +class SParseNode { +protected: + Stretch _position; + SSmalltalkCompiler* _compiler; + std::vector _comments; + +public: + SParseNode(SSmalltalkCompiler* compiler) : _compiler(compiler) {} + virtual ~SParseNode() {} + + virtual void allNodesDo_includingDeclarations_(std::function block); + virtual SParseNode* nodesDetect_(std::function predicate, std::function ifAbsent); + virtual SParseNode* nodeWithLiteral_(const egg::string& value); + virtual SParseNode* variableNamed_(const egg::string& name); + virtual bool isMethodArgument() const; + virtual bool isMethodTemporary() const; + virtual bool valueEquals_(const egg::string&) const; + virtual bool nameEquals_(const egg::string&) const; + + virtual SParseNode* ast(); + + virtual void acceptVisitor_(SParseNodeVisitor* visitor) = 0; + + Stretch position() const { return _position; } + void position_(const Stretch& pos) { _position = pos; } + + SSmalltalkCompiler* compiler() const { return _compiler; } + + void addComment_(const egg::string& comment) { + _comments.push_back(comment); + } + + void moveCommentsFrom_(SParseNode* other) { + if (other) { + _comments.insert(_comments.end(), other->_comments.begin(), other->_comments.end()); + other->_comments.clear(); + } + } + + void moveCommentsFrom_(SToken* token) { + if (token) { + const auto& tc = token->comments(); + _comments.insert(_comments.end(), tc.begin(), tc.end()); + } + } + + virtual bool isAssignment() const { return false; } + virtual bool isBlock() const { return false; } + virtual bool isBrace() const { return false; } + virtual bool isCascade() const { return false; } + virtual bool isCascadeMessage() const { return false; } + virtual bool isComment() const { return false; } + virtual bool isIdentifier() const { return false; } + virtual bool isLiteral() const { return false; } + virtual bool isMessage() const { return false; } + virtual bool isMethod() const { return false; } + virtual bool isReturn() const { return false; } + virtual bool isSelector() const { return false; } + virtual bool isSelf() const { return false; } + virtual bool isSuper() const { return false; } + virtual bool isPragma() const { return false; } + virtual bool isArray() const { return false; } + + virtual bool hasAssign() const { return false; } + virtual bool isEvaluable() const { return false; } + virtual bool isImmediate() const { return false; } + + virtual void nodesDo_(std::function block, bool includeDeclarations = false) { + block(this); + } +}; + +} // namespace Egg + +#endif // _PARSE_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SParseNodeVisitor.h b/runtime/cpp/Compiler/AST/SParseNodeVisitor.h new file mode 100644 index 00000000..404a7385 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SParseNodeVisitor.h @@ -0,0 +1,54 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _PARSE_NODE_VISITOR_H_ +#define _PARSE_NODE_VISITOR_H_ + +namespace Egg { + +class SIdentifierNode; +class SLiteralNode; +class SMessageNode; +class SAssignmentNode; +class SReturnNode; +class SMethodNode; +class SBlockNode; +class SCascadeNode; +class SBraceNode; +class SSelectorNode; +class SNumberNode; +class SStringNode; +class SPragmaNode; +class SCommentNode; + +/** + * Visitor interface for parse tree traversal + */ +class SParseNodeVisitor { +public: + virtual ~SParseNodeVisitor() {} + + virtual void visitIdentifier_(SIdentifierNode* node) = 0; + virtual void visitLiteral_(SLiteralNode* node) = 0; + virtual void visitMessage_(SMessageNode* node) = 0; + virtual void visitAssignment_(SAssignmentNode* node) = 0; + virtual void visitReturn_(SReturnNode* node) = 0; + virtual void visitMethod_(SMethodNode* node) = 0; + virtual void visitBlock_(SBlockNode* node) = 0; + virtual void visitCascade_(SCascadeNode* node) = 0; + virtual void visitBrace_(SBraceNode* node) = 0; + virtual void visitComment_(SCommentNode* node) = 0; + virtual void visitSelector_(SSelectorNode* node) = 0; + virtual void visitNumberNode_(SNumberNode* node) = 0; + virtual void visitString_(SStringNode* node) = 0; + virtual void visitPragma_(SPragmaNode* node) = 0; + virtual void visitPrimitivePragma_(SPragmaNode* node) = 0; + virtual void visitFFIPragma_(SPragmaNode* node) = 0; + virtual void visitSymbolicPragma_(SPragmaNode* node) = 0; +}; + +} // namespace Egg + +#endif // _PARSE_NODE_VISITOR_H_ diff --git a/runtime/cpp/Compiler/AST/SPragmaNode.cpp b/runtime/cpp/Compiler/AST/SPragmaNode.cpp new file mode 100644 index 00000000..fb82ca47 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SPragmaNode.cpp @@ -0,0 +1,23 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SPragmaNode.h" +#include "SParseNodeVisitor.h" + +namespace Egg { + +void SPragmaNode::acceptVisitor_(SParseNodeVisitor* visitor) { + if (isPrimitive()) { + visitor->visitPrimitivePragma_(this); + } else if (isFFI()) { + visitor->visitFFIPragma_(this); + } else if (isSymbolic()) { + visitor->visitSymbolicPragma_(this); + } else { + visitor->visitPragma_(this); + } +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SPragmaNode.h b/runtime/cpp/Compiler/AST/SPragmaNode.h new file mode 100644 index 00000000..4927f3d3 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SPragmaNode.h @@ -0,0 +1,78 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _PRAGMA_NODE_H_ +#define _PRAGMA_NODE_H_ + +#include "SParseNode.h" +#include + +namespace Egg { + +/** + * Pragma node () + * Corresponds to SPragmaNode in Smalltalk + */ +class SPragmaNode : public SParseNode { +public: + enum class Type { + None, + Primitive, + FFI, + Symbolic + }; + +private: + Type _type; + egg::string _name; + int _primitiveNumber; // For primitive pragmas + void* _info; // For FFI descriptors (placeholder for now) + +public: + SPragmaNode(SSmalltalkCompiler* compiler) + : SParseNode(compiler), _type(Type::None), _primitiveNumber(0), _info(nullptr) {} + virtual ~SPragmaNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + Type type() const { return _type; } + void type_(Type t) { _type = t; } + + egg::string name() const { return _name; } + void name_(const egg::string& n) { _name = n; } + + int primitiveNumber() const { return _primitiveNumber; } + void primitiveNumber_(int n) { _primitiveNumber = n; } + + void* info() const { return _info; } + void info_(void* i) { _info = i; } + + bool isPragma() const override { return true; } + bool isPrimitive() const { return _type == Type::Primitive; } + bool isFFI() const { return _type == Type::FFI; } + bool isSymbolic() const { return _type == Type::Symbolic; } + bool isUsed() const { return _type != Type::None; } + + void bePrimitive_(int number, const egg::string& name) { + _type = Type::Primitive; + _primitiveNumber = number; + _name = name; + } + + void beFFI_(const egg::string& name, void* descriptor) { + _type = Type::FFI; + _name = name; + _info = descriptor; + } + + void beSymbolic_(const egg::string& symbol) { + _type = Type::Symbolic; + _name = symbol; + } +}; + +} // namespace Egg + +#endif // _PRAGMA_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SReturnNode.cpp b/runtime/cpp/Compiler/AST/SReturnNode.cpp new file mode 100644 index 00000000..eaa3f111 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SReturnNode.cpp @@ -0,0 +1,24 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SReturnNode.h" +#include "SParseNodeVisitor.h" + +namespace Egg { + +SReturnNode::SReturnNode(SSmalltalkCompiler* compiler) + : SParseNode(compiler), _expression(nullptr) { +} + +void SReturnNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitReturn_(this); +} + +void SReturnNode::nodesDo_(std::function block, bool includeDeclarations) { + SParseNode::nodesDo_(block, includeDeclarations); + if (_expression) _expression->nodesDo_(block, includeDeclarations); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SReturnNode.h b/runtime/cpp/Compiler/AST/SReturnNode.h new file mode 100644 index 00000000..f5079212 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SReturnNode.h @@ -0,0 +1,38 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _RETURN_NODE_H_ +#define _RETURN_NODE_H_ + +#include "SParseNode.h" +#include + +namespace Egg { + +/** + * Return statement node + * Corresponds to SReturnNode in Smalltalk + */ +class SReturnNode : public SParseNode { +private: + SParseNode* _expression; + +public: + SReturnNode(SSmalltalkCompiler* compiler); + virtual ~SReturnNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + SParseNode* expression() const { return _expression; } + void expression_(SParseNode* expr) { _expression = expr; } + + bool isReturn() const override { return true; } + + void nodesDo_(std::function block, bool includeDeclarations = false) override; +}; + +} // namespace Egg + +#endif // _RETURN_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SScriptNode.cpp b/runtime/cpp/Compiler/AST/SScriptNode.cpp new file mode 100644 index 00000000..5fd6b929 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SScriptNode.cpp @@ -0,0 +1,75 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SScriptNode.h" +#include "SIdentifierNode.h" +#include "../Binding/Binding.h" +#include "../Binding/ScriptScope.h" + +namespace Egg { + +SScriptNode::SScriptNode(SSmalltalkCompiler* compiler) + : SParseNode(compiler), _scope(nullptr) { +} + +bool SScriptNode::hasAssign() const { + for (auto stmt : _statements) { + if (stmt && stmt->hasAssign()) return true; + } + return false; +} + +void SScriptNode::nodesDo_(std::function block, bool includeDeclarations) { + SParseNode::nodesDo_(block, includeDeclarations); + if (includeDeclarations) { + for (auto arg : _arguments) { + if (arg) arg->nodesDo_(block, includeDeclarations); + } + for (auto temp : _temporaries) { + if (temp) temp->nodesDo_(block, includeDeclarations); + } + } + for (auto stmt : _statements) { + if (stmt) stmt->nodesDo_(block, includeDeclarations); + } +} + +void SScriptNode::reference_(Binding* aBinding) { + if (aBinding) { + aBinding->beReferencedFrom_(this); + } +} + +void SScriptNode::useSelf_() { + _scope->captureSelf_(); +} + +void SScriptNode::bindLocals() { + if (_scope) { + for (auto arg : _arguments) { + if (arg) arg->defineArgumentIn_(_scope); + } + for (auto temp : _temporaries) { + if (temp) { + temp->checkLowercase(); + temp->defineTemporaryIn_(_scope); + } + } + } + for (auto child : _children) { + if (child) child->bindLocals(); + } +} + +void SScriptNode::positionLocals() { + if (_scope) { + _scope->positionLocals_(); + } + for (auto child : _children) { + if (child) child->positionLocals(); + } +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SScriptNode.h b/runtime/cpp/Compiler/AST/SScriptNode.h new file mode 100644 index 00000000..2f69b5c9 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SScriptNode.h @@ -0,0 +1,65 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _SCRIPT_NODE_H_ +#define _SCRIPT_NODE_H_ + +#include "SParseNode.h" +#include +#include + +namespace Egg { + +class SIdentifierNode; +class ScriptScope; + +/** + * Base class for scripts (methods and blocks) + * Corresponds to SScriptNode in Smalltalk + */ +class SScriptNode : public SParseNode { +protected: + std::vector _statements; + std::vector _arguments; + std::vector _temporaries; + std::vector _children; // nested blocks + ScriptScope* _scope; + +public: + SScriptNode(SSmalltalkCompiler* compiler); + virtual ~SScriptNode() {} + + const std::vector& statements() const { return _statements; } + void addStatement_(SParseNode* stmt) { _statements.push_back(stmt); } + void statements_(const std::vector& stmts) { _statements = stmts; } + + const std::vector& arguments() const { return _arguments; } + void arguments_(const std::vector& args) { _arguments = args; } + + const std::vector& temporaries() const { return _temporaries; } + void temporaries_(const std::vector& temps) { _temporaries = temps; } + + const std::vector& children() const { return _children; } + void addChild_(SScriptNode* child) { _children.push_back(child); } + + ScriptScope* scope() const { return _scope; } + void scope_(ScriptScope* s) { _scope = s; } + + virtual SScriptNode* realScript() = 0; + virtual void captureHome() = 0; + + void reference_(Binding* aBinding); + void useSelf_(); + void bindLocals(); + void positionLocals(); + + bool hasAssign() const override; + + void nodesDo_(std::function block, bool includeDeclarations = false) override; +}; + +} // namespace Egg + +#endif // _SCRIPT_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SSelectorNode.cpp b/runtime/cpp/Compiler/AST/SSelectorNode.cpp new file mode 100644 index 00000000..38c84fc0 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SSelectorNode.cpp @@ -0,0 +1,23 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SSelectorNode.h" +#include "SParseNodeVisitor.h" +#include "../egg_string.h" + +namespace Egg { + +void SSelectorNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitSelector_(this); +} + +bool SSelectorNode::isBinary() const { + if (_symbol.empty()) return false; + + char32_t first = _symbol[0]; + return !egg::isLetter(first) && !egg::isDigit(first) && first != '_' && first != ':'; +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SSelectorNode.h b/runtime/cpp/Compiler/AST/SSelectorNode.h new file mode 100644 index 00000000..7ced9de8 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SSelectorNode.h @@ -0,0 +1,52 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _SELECTOR_NODE_H_ +#define _SELECTOR_NODE_H_ + +#include "SParseNode.h" +#include +#include + +namespace Egg { + +/** + * Selector node (message selector) + * Corresponds to SSelectorNode in Smalltalk + */ +class SSelectorNode : public SParseNode { +private: + egg::string _symbol; + std::vector _keywords; // For keyword selectors with multiple parts + +public: + SSelectorNode(SSmalltalkCompiler* compiler) : SParseNode(compiler) {} + virtual ~SSelectorNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + egg::string symbol() const { return _symbol; } + void symbol_(const egg::string& s) { _symbol = s; } + + const std::vector& keywords() const { return _keywords; } + void addKeyword_(SSelectorNode* kw) { _keywords.push_back(kw); } + + bool isSelector() const override { return true; } + bool isBinary() const; + bool hasSymbol() const { return !_symbol.empty(); } + + egg::string value() const { return _symbol; } + + void nodesDo_(std::function block, bool includeDeclarations = false) override { + SParseNode::nodesDo_(block, includeDeclarations); + for (auto kw : _keywords) { + if (kw) kw->nodesDo_(block, includeDeclarations); + } + } +}; + +} // namespace Egg + +#endif // _SELECTOR_NODE_H_ diff --git a/runtime/cpp/Compiler/AST/SStringNode.cpp b/runtime/cpp/Compiler/AST/SStringNode.cpp new file mode 100644 index 00000000..f47872a7 --- /dev/null +++ b/runtime/cpp/Compiler/AST/SStringNode.cpp @@ -0,0 +1,15 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SStringNode.h" +#include "SParseNodeVisitor.h" + +namespace Egg { + +void SStringNode::acceptVisitor_(SParseNodeVisitor* visitor) { + visitor->visitString_(this); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SStringNode.h b/runtime/cpp/Compiler/AST/SStringNode.h new file mode 100644 index 00000000..1fb93fff --- /dev/null +++ b/runtime/cpp/Compiler/AST/SStringNode.h @@ -0,0 +1,29 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _STRING_NODE_H_ +#define _STRING_NODE_H_ + +#include "SLiteralNode.h" + +namespace Egg { + +/** + * String literal node + * Corresponds to SStringNode in Smalltalk + */ +class SStringNode : public SLiteralNode { +public: + SStringNode(SSmalltalkCompiler* compiler) : SLiteralNode(compiler) {} + virtual ~SStringNode() {} + + void acceptVisitor_(SParseNodeVisitor* visitor) override; + + bool isSStringNode() const { return true; } +}; + +} // namespace Egg + +#endif // _STRING_NODE_H_ diff --git a/runtime/cpp/Compiler/LiteralValue.h b/runtime/cpp/Compiler/LiteralValue.h new file mode 100644 index 00000000..d1413718 --- /dev/null +++ b/runtime/cpp/Compiler/LiteralValue.h @@ -0,0 +1,227 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _LITERAL_VALUE_H_ +#define _LITERAL_VALUE_H_ + +#include +#include +#include +#include +#include "egg_string.h" + +namespace Egg { + +/** + * Tagged union representing a Smalltalk literal value. + * Used by SLiteralNode and SCompiledMethod to carry typed values + * instead of opaque strings. + */ +struct LiteralValue { + enum Tag { + None, + Integer, + Float, + String, + Symbol, + Character, + Array, + ByteArray, + Boolean, + Nil, + Block + }; + + Tag tag; + + union { + int64_t intVal; + double floatVal; + uint32_t charVal; // Unicode code point + bool boolVal; + }; + + // Block metadata (used when tag == Block) + struct BlockInfo { + int id; // 0-based block index + int argCount; + int tempCount; + int envCount; + bool capturesSelf; + bool capturesHome; + }; + BlockInfo blockInfo; + + // Strings and symbols share this field (union can't hold egg::string) + egg::string strVal; + + // For literal arrays + std::vector elements; + + // For byte arrays + std::vector bytes; + + LiteralValue() : tag(None), intVal(0), blockInfo{0, 0, 0, 0, false, false} {} + + // ----- factory helpers ----- + + static LiteralValue fromInteger(int64_t v) { + LiteralValue lit; + lit.tag = Integer; + lit.intVal = v; + return lit; + } + + static LiteralValue fromFloat(double v) { + LiteralValue lit; + lit.tag = Float; + lit.floatVal = v; + return lit; + } + + static LiteralValue fromString(const egg::string& v) { + LiteralValue lit; + lit.tag = String; + lit.strVal = v; + return lit; + } + + static LiteralValue fromSymbol(const egg::string& v) { + LiteralValue lit; + lit.tag = Symbol; + lit.strVal = v; + return lit; + } + + static LiteralValue fromCharacter(uint32_t codePoint) { + LiteralValue lit; + lit.tag = Character; + lit.charVal = codePoint; + return lit; + } + + static LiteralValue fromArray(std::vector elems) { + LiteralValue lit; + lit.tag = Array; + lit.elements = std::move(elems); + return lit; + } + + static LiteralValue fromByteArray(std::vector b) { + LiteralValue lit; + lit.tag = ByteArray; + lit.bytes = std::move(b); + return lit; + } + + static LiteralValue fromBoolean(bool v) { + LiteralValue lit; + lit.tag = Boolean; + lit.boolVal = v; + return lit; + } + + static LiteralValue nil() { + LiteralValue lit; + lit.tag = Nil; + return lit; + } + + static LiteralValue fromBlock(int id, int argCount, int tempCount, + int envCount, bool capturesSelf, bool capturesHome) { + LiteralValue lit; + lit.tag = Block; + lit.blockInfo = {id, argCount, tempCount, envCount, capturesSelf, capturesHome}; + return lit; + } + + // ----- accessors ----- + + bool isNone() const { return tag == None; } + bool isInteger() const { return tag == Integer; } + bool isFloat() const { return tag == Float; } + bool isString() const { return tag == String; } + bool isSymbol() const { return tag == Symbol; } + bool isCharacter() const { return tag == Character; } + bool isArray() const { return tag == Array; } + bool isByteArray() const { return tag == ByteArray; } + bool isBoolean() const { return tag == Boolean; } + bool isNil() const { return tag == Nil; } + bool isBlock() const { return tag == Block; } + bool isNumber() const { return tag == Integer || tag == Float; } + + const BlockInfo& asBlock() const { assert(tag == Block); return blockInfo; } + + int64_t asInteger() const { assert(tag == Integer); return intVal; } + double asFloat() const { assert(tag == Float); return floatVal; } + uint32_t asCharacter() const { assert(tag == Character); return charVal; } + bool asBoolean() const { assert(tag == Boolean); return boolVal; } + + const egg::string& asString() const { + assert(tag == String || tag == Symbol); + return strVal; + } + + const std::vector& asArray() const { + assert(tag == Array); + return elements; + } + + const std::vector& asByteArray() const { + assert(tag == ByteArray); + return bytes; + } + + /** + * Returns a printable representation suitable for display / debugging. + * Also used as a backwards-compatible "value()" for code that + * previously relied on the string representation. + */ + egg::string printString() const { + switch (tag) { + case None: return ""; + case Integer: return egg::string(std::to_string(intVal)); + case Float: return egg::string(std::to_string(floatVal)); + case String: return strVal; + case Symbol: return strVal; + case Character: { egg::string s; s += (char32_t)charVal; return s; } + case Boolean: return boolVal ? "true" : "false"; + case Nil: return "nil"; + case Array: return "#(...)"; + case ByteArray: return "#[...]"; + case Block: return "a CompiledBlock"; + } + return ""; + } + + // UTF-8 printable string for debug output + std::string printStringUtf8() const { + return printString().toUtf8(); + } + + bool operator==(const LiteralValue& other) const { + if (tag != other.tag) return false; + switch (tag) { + case None: return true; + case Integer: return intVal == other.intVal; + case Float: return floatVal == other.floatVal; + case String: return strVal == other.strVal; + case Symbol: return strVal == other.strVal; + case Character: return charVal == other.charVal; + case Boolean: return boolVal == other.boolVal; + case Nil: return true; + case Array: return elements == other.elements; + case ByteArray: return bytes == other.bytes; + case Block: return blockInfo.id == other.blockInfo.id; + } + return false; + } + + bool operator!=(const LiteralValue& other) const { return !(*this == other); } +}; + +} // namespace Egg + +#endif // _LITERAL_VALUE_H_ From b8b8ff5a06e04ae7998791cbb3e98fd15c6f57d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Tue, 10 Mar 2026 00:17:42 -0300 Subject: [PATCH 03/15] Implement binding classes and environments for Smalltalk compiler --- .../cpp/Compiler/Binding/ArgumentBinding.h | 47 +++++++ .../Compiler/Binding/ArgumentEnvironment.h | 42 ++++++ .../cpp/Compiler/Binding/ArrayEnvironment.h | 43 ++++++ runtime/cpp/Compiler/Binding/Binding.h | 60 +++++++++ runtime/cpp/Compiler/Binding/ClassBinding.h | 23 ++++ .../cpp/Compiler/Binding/ConstantBinding.h | 25 ++++ runtime/cpp/Compiler/Binding/FieldBinding.h | 23 ++++ runtime/cpp/Compiler/Binding/GlobalBinding.h | 23 ++++ runtime/cpp/Compiler/Binding/LocalBinding.cpp | 47 +++++++ runtime/cpp/Compiler/Binding/LocalBinding.h | 53 ++++++++ .../cpp/Compiler/Binding/LocalEnvironment.h | 29 ++++ runtime/cpp/Compiler/Binding/MethodBinding.h | 23 ++++ .../Binding/PseudoVariableBindings.cpp | 63 +++++++++ .../Compiler/Binding/PseudoVariableBindings.h | 125 ++++++++++++++++++ .../cpp/Compiler/Binding/StackEnvironment.h | 29 ++++ .../cpp/Compiler/Binding/TemporaryBinding.h | 40 ++++++ 16 files changed, 695 insertions(+) create mode 100644 runtime/cpp/Compiler/Binding/ArgumentBinding.h create mode 100644 runtime/cpp/Compiler/Binding/ArgumentEnvironment.h create mode 100644 runtime/cpp/Compiler/Binding/ArrayEnvironment.h create mode 100644 runtime/cpp/Compiler/Binding/Binding.h create mode 100644 runtime/cpp/Compiler/Binding/ClassBinding.h create mode 100644 runtime/cpp/Compiler/Binding/ConstantBinding.h create mode 100644 runtime/cpp/Compiler/Binding/FieldBinding.h create mode 100644 runtime/cpp/Compiler/Binding/GlobalBinding.h create mode 100644 runtime/cpp/Compiler/Binding/LocalBinding.cpp create mode 100644 runtime/cpp/Compiler/Binding/LocalBinding.h create mode 100644 runtime/cpp/Compiler/Binding/LocalEnvironment.h create mode 100644 runtime/cpp/Compiler/Binding/MethodBinding.h create mode 100644 runtime/cpp/Compiler/Binding/PseudoVariableBindings.cpp create mode 100644 runtime/cpp/Compiler/Binding/PseudoVariableBindings.h create mode 100644 runtime/cpp/Compiler/Binding/StackEnvironment.h create mode 100644 runtime/cpp/Compiler/Binding/TemporaryBinding.h diff --git a/runtime/cpp/Compiler/Binding/ArgumentBinding.h b/runtime/cpp/Compiler/Binding/ArgumentBinding.h new file mode 100644 index 00000000..8f87a137 --- /dev/null +++ b/runtime/cpp/Compiler/Binding/ArgumentBinding.h @@ -0,0 +1,47 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _ARGUMENTBINDING_H_ +#define _ARGUMENTBINDING_H_ +#include "LocalBinding.h" +#include "ArgumentEnvironment.h" + +namespace Egg { + +class ArgumentBinding : public LocalBinding { +public: + ArgumentBinding(const egg::string& name, uint32_t position) + : LocalBinding(Kind::Argument, name, position) { + _environment = new ArgumentEnvironment(); + } + + void beInlined_() { + delete _environment; + _environment = new InlinedArgEnvironment(); + } + + bool isLiteral() const override { return true; } + bool canBeAssigned() const override { return false; } + bool isArgument() const { return true; } + bool isInlined() const { return _environment && _environment->isInlinedArgument(); } + + void encodeUsing_(TreecodeEncoder* encoder) override; + + Binding* copy_() override { + auto copy = new ArgumentBinding(name(), position()); + copy->index_(_index); + if (_environment) { + if (auto argEnv = dynamic_cast(_environment)) { + copy->_environment = new ArgumentEnvironment(*argEnv); + } else if (auto inlinedEnv = dynamic_cast(_environment)) { + copy->_environment = new InlinedArgEnvironment(*inlinedEnv); + } + } + return copy; + } +}; + +} // namespace Egg + +#endif // _ARGUMENTBINDING_H_ diff --git a/runtime/cpp/Compiler/Binding/ArgumentEnvironment.h b/runtime/cpp/Compiler/Binding/ArgumentEnvironment.h new file mode 100644 index 00000000..0cc8a5fe --- /dev/null +++ b/runtime/cpp/Compiler/Binding/ArgumentEnvironment.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _ARGUMENTENVIRONMENT_H_ +#define _ARGUMENTENVIRONMENT_H_ +#include "StackEnvironment.h" +#include "ArrayEnvironment.h" +#include "../CompilerTypes.h" + +namespace Egg { + +/** + * Environment for argument bindings + * Corresponds to ArgumentEnvironment in Smalltalk + */ +class ArgumentEnvironment : public StackEnvironment { +public: + ArgumentEnvironment() {} + virtual ~ArgumentEnvironment() {} + + int captureType() const override { return CaptureLocalArgument; } + uint8_t environmentType() const override { return AstBindingType::ArgumentBindingId; } +}; + +/** + * Environment for inlined argument bindings + * Corresponds to InlinedArgEnvironment in Smalltalk + */ +class InlinedArgEnvironment : public StackEnvironment { +public: + InlinedArgEnvironment() {} + virtual ~InlinedArgEnvironment() {} + + int captureType() const override { return CaptureInlinedArgument; } + bool isInlinedArgument() const override { return true; } + uint8_t environmentType() const override { return AstBindingType::ArgumentBindingId; } +}; + +} // namespace Egg + +#endif // _ARGUMENTENVIRONMENT_H_ diff --git a/runtime/cpp/Compiler/Binding/ArrayEnvironment.h b/runtime/cpp/Compiler/Binding/ArrayEnvironment.h new file mode 100644 index 00000000..ebcf036d --- /dev/null +++ b/runtime/cpp/Compiler/Binding/ArrayEnvironment.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _ARRAYENVIRONMENT_H_ +#define _ARRAYENVIRONMENT_H_ +#include "LocalEnvironment.h" +#include "../CompilerTypes.h" + +namespace Egg { + +enum CaptureType { + CaptureEnvironmentValue = 3, + CaptureLocalArgument = 1, + CaptureInlinedArgument = 4 +}; + +/** + * Array-allocated environment for captured variables + * Corresponds to ArrayEnvironment in Smalltalk + */ +class ArrayEnvironment : public LocalEnvironment { +private: + int _index; + +public: + ArrayEnvironment() : _index(-1) {} + virtual ~ArrayEnvironment() {} + + int captureType() const override { return CaptureEnvironmentValue; } + uint8_t environmentType() const override { return AstBindingType::TemporaryBindingId; } + + int* index() override { return &_index; } + void index_(int idx) { _index = idx; } + + bool isCurrent() const { return _index < 0; } + bool isIndirect() const { return !isCurrent(); } + bool isStack() const override { return false; } +}; + +} // namespace Egg + +#endif // _ARRAYENVIRONMENT_H_ diff --git a/runtime/cpp/Compiler/Binding/Binding.h b/runtime/cpp/Compiler/Binding/Binding.h new file mode 100644 index 00000000..71c40907 --- /dev/null +++ b/runtime/cpp/Compiler/Binding/Binding.h @@ -0,0 +1,60 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _BINDING_H_ +#define _BINDING_H_ +#include +#include +#include "../LiteralValue.h" + +namespace Egg { + +class SScriptNode; +class TreecodeEncoder; + +class Binding { +public: + enum class Kind { + Unknown, + Variable, + Argument, + Temporary, + Field, + Method, + Class, + Global, + Constant + }; + + Binding(Kind kind, const egg::string& name, uint32_t position) + : _kind(kind), _name(name), _position(position) {} + + Kind kind() const { return _kind; } + const egg::string& name() const { return _name; } + uint32_t position() const { return _position; } + + virtual bool isDynamic() const { return false; } + virtual bool isLiteral() const { return false; } + virtual bool isLocal() const { return false; } + virtual bool canBeAssigned() const { return true; } + virtual void beReferencedFrom_(SScriptNode* aScriptNode) { + } + + // Returns the literal value the binding contributes to the method's literal pool. + // Returns nullptr if the binding doesn't need a literal (e.g., local vars, self, nil). + virtual const LiteralValue* literal() const { return nullptr; } + + virtual void encodeUsing_(TreecodeEncoder* encoder) = 0; // Pure virtual + + virtual Binding* copy_() = 0; // Pure virtual, subclasses must implement + +private: + Kind _kind; + egg::string _name; + uint32_t _position; +}; + +} // namespace Egg + +#endif // _BINDING_H_ diff --git a/runtime/cpp/Compiler/Binding/ClassBinding.h b/runtime/cpp/Compiler/Binding/ClassBinding.h new file mode 100644 index 00000000..57794a87 --- /dev/null +++ b/runtime/cpp/Compiler/Binding/ClassBinding.h @@ -0,0 +1,23 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _CLASSBINDING_H_ +#define _CLASSBINDING_H_ +#include "Binding.h" + +namespace Egg { + +class ClassBinding : public Binding { +public: + ClassBinding(const egg::string& name, uint32_t position) + : Binding(Kind::Class, name, position) {} + + Binding* copy_() override { + return new ClassBinding(name(), position()); + } +}; + +} // namespace Egg + +#endif // _CLASSBINDING_H_ diff --git a/runtime/cpp/Compiler/Binding/ConstantBinding.h b/runtime/cpp/Compiler/Binding/ConstantBinding.h new file mode 100644 index 00000000..631703bf --- /dev/null +++ b/runtime/cpp/Compiler/Binding/ConstantBinding.h @@ -0,0 +1,25 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _CONSTANTBINDING_H_ +#define _CONSTANTBINDING_H_ +#include "Binding.h" + +namespace Egg { + +class ConstantBinding : public Binding { +public: + ConstantBinding(const egg::string& name, uint32_t position) + : Binding(Kind::Constant, name, position) {} + + bool isLiteral() const { return true; } + + Binding* copy_() override { + return new ConstantBinding(name(), position()); + } +}; + +} // namespace Egg + +#endif // _CONSTANTBINDING_H_ diff --git a/runtime/cpp/Compiler/Binding/FieldBinding.h b/runtime/cpp/Compiler/Binding/FieldBinding.h new file mode 100644 index 00000000..268bcced --- /dev/null +++ b/runtime/cpp/Compiler/Binding/FieldBinding.h @@ -0,0 +1,23 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _FIELDBINDING_H_ +#define _FIELDBINDING_H_ +#include "Binding.h" + +namespace Egg { + +class FieldBinding : public Binding { +public: + FieldBinding(const egg::string& name, uint32_t position) + : Binding(Kind::Field, name, position) {} + + Binding* copy_() override { + return new FieldBinding(name(), position()); + } +}; + +} // namespace Egg + +#endif // _FIELDBINDING_H_ diff --git a/runtime/cpp/Compiler/Binding/GlobalBinding.h b/runtime/cpp/Compiler/Binding/GlobalBinding.h new file mode 100644 index 00000000..73cc180c --- /dev/null +++ b/runtime/cpp/Compiler/Binding/GlobalBinding.h @@ -0,0 +1,23 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _GLOBALBINDING_H_ +#define _GLOBALBINDING_H_ +#include "Binding.h" + +namespace Egg { + +class GlobalBinding : public Binding { +public: + GlobalBinding(const egg::string& name, uint32_t position) + : Binding(Kind::Global, name, position) {} + + Binding* copy_() override { + return new GlobalBinding(name(), position()); + } +}; + +} // namespace Egg + +#endif // _GLOBALBINDING_H_ diff --git a/runtime/cpp/Compiler/Binding/LocalBinding.cpp b/runtime/cpp/Compiler/Binding/LocalBinding.cpp new file mode 100644 index 00000000..abe805ea --- /dev/null +++ b/runtime/cpp/Compiler/Binding/LocalBinding.cpp @@ -0,0 +1,47 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#include "LocalBinding.h" +#include "ArgumentBinding.h" +#include "TemporaryBinding.h" +#include "ArrayEnvironment.h" +#include "../TreecodeEncoder.h" + +namespace Egg { + +void LocalBinding::beInArray_() { + delete _environment; + _environment = new ArrayEnvironment(); +} + +int* LocalBinding::environment() { + return _environment ? _environment->index() : nullptr; +} + +int* LocalBinding::environmentIndex() { + return _environment ? _environment->index() : nullptr; +} + +void LocalBinding::environmentIndex_(int idx) { + if (_environment) { + auto arrayEnv = dynamic_cast(_environment); + if (arrayEnv) { + arrayEnv->index_(idx); + } + } +} + +int LocalBinding::environmentCaptureType() const { + return _environment ? _environment->captureType() : 0; +} + +void ArgumentBinding::encodeUsing_(TreecodeEncoder* encoder) { + encoder->encodeArgument_env_(_index, _environment); +} + +void TemporaryBinding::encodeUsing_(TreecodeEncoder* encoder) { + encoder->encodeTemporary_env_(_index, _environment); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/Binding/LocalBinding.h b/runtime/cpp/Compiler/Binding/LocalBinding.h new file mode 100644 index 00000000..b342df7f --- /dev/null +++ b/runtime/cpp/Compiler/Binding/LocalBinding.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _LOCALBINDING_H_ +#define _LOCALBINDING_H_ +#include "Binding.h" +#include "LocalEnvironment.h" + +namespace Egg { + +class SIdentifierNode; + +/** + * Base class for local bindings (arguments and temporaries) + * Corresponds to LocalBinding in Smalltalk + */ +class LocalBinding : public Binding { +protected: + int _index; + LocalEnvironment* _environment; + SIdentifierNode* _declaration; + +public: + LocalBinding(Kind kind, const egg::string& name, uint32_t position) + : Binding(kind, name, position), _index(-1), _environment(nullptr), _declaration(nullptr) {} + + virtual ~LocalBinding() { + delete _environment; + } + + void beInArray_(); + + SIdentifierNode* declaration() const { return _declaration; } + void declaration_(SIdentifierNode* node) { _declaration = node; } + + int* environment(); + int* environmentIndex(); + void environmentIndex_(int idx); + LocalEnvironment* environmentObject() const { return _environment; } + int environmentCaptureType() const; + + int index() const { return _index; } + void index_(int idx) { _index = idx; } + + bool isInArray() const { return !isInStack(); } + bool isInStack() const { return _environment && _environment->isStack(); } + bool isLocal() const override { return true; } +}; + +} // namespace Egg + +#endif // _LOCALBINDING_H_ diff --git a/runtime/cpp/Compiler/Binding/LocalEnvironment.h b/runtime/cpp/Compiler/Binding/LocalEnvironment.h new file mode 100644 index 00000000..5e4e4635 --- /dev/null +++ b/runtime/cpp/Compiler/Binding/LocalEnvironment.h @@ -0,0 +1,29 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _LOCALENVIRONMENT_H_ +#define _LOCALENVIRONMENT_H_ + +namespace Egg { + +/** + * Base class for local variable environments + * Corresponds to LocalEnvironment in Smalltalk + */ +class LocalEnvironment { +public: + LocalEnvironment() {} + virtual ~LocalEnvironment() {} + + virtual bool isInlinedArgument() const { return false; } + virtual bool isStack() const = 0; // Pure virtual + virtual int* index() = 0; // Pure virtual, returns nullptr for stack, pointer to int for array + virtual int captureType() const = 0; // Pure virtual + + virtual uint8_t environmentType() const = 0; // Pure virtual +}; + +} // namespace Egg + +#endif // _LOCALENVIRONMENT_H_ diff --git a/runtime/cpp/Compiler/Binding/MethodBinding.h b/runtime/cpp/Compiler/Binding/MethodBinding.h new file mode 100644 index 00000000..6d4471c9 --- /dev/null +++ b/runtime/cpp/Compiler/Binding/MethodBinding.h @@ -0,0 +1,23 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _METHODBINDING_H_ +#define _METHODBINDING_H_ +#include "Binding.h" + +namespace Egg { + +class MethodBinding : public Binding { +public: + MethodBinding(const egg::string& name, uint32_t position) + : Binding(Kind::Method, name, position) {} + + Binding* copy_() override { + return new MethodBinding(name(), position()); + } +}; + +} // namespace Egg + +#endif // _METHODBINDING_H_ diff --git a/runtime/cpp/Compiler/Binding/PseudoVariableBindings.cpp b/runtime/cpp/Compiler/Binding/PseudoVariableBindings.cpp new file mode 100644 index 00000000..cdfac9f2 --- /dev/null +++ b/runtime/cpp/Compiler/Binding/PseudoVariableBindings.cpp @@ -0,0 +1,63 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#include "PseudoVariableBindings.h" +#include "../TreecodeEncoder.h" +#include + +namespace Egg { + +void NilBinding::encodeUsing_(TreecodeEncoder* encoder) { + encoder->encodeNil(); +} + +void TrueBinding::encodeUsing_(TreecodeEncoder* encoder) { + encoder->encodeTrue(); +} + +void FalseBinding::encodeUsing_(TreecodeEncoder* encoder) { + encoder->encodeFalse(); +} + +void SelfBinding::encodeUsing_(TreecodeEncoder* encoder) { + encoder->encodeSelf(); +} + +void SelfBinding::beReferencedFrom_(SScriptNode* aScriptNode) { + aScriptNode->useSelf_(); +} + +void SuperBinding::beReferencedFrom_(SScriptNode* aScriptNode) { + aScriptNode->useSelf_(); +} + +void SuperBinding::encodeUsing_(TreecodeEncoder* encoder) { + encoder->encodeSuper(); +} + +void DynamicBinding::encodeUsing_(TreecodeEncoder* encoder) { + encoder->encodeDynamicVar_(name()); +} + +void DynamicBinding::beReferencedFrom_(SScriptNode* aScriptNode) { + aScriptNode->useSelf_(); +} + +void NestedDynamicBinding::encodeUsing_(TreecodeEncoder* encoder) { + encoder->encodeNestedDynamicVar_(name()); +} + +DynamicBinding* DynamicBinding::named_(const egg::string& name) { + size_t dotPos = name.find('.'); + if (dotPos == egg::string::npos) { + return new DynamicBinding(name); + } + + egg::string first = name.substr(0, dotPos); + egg::string second = name.substr(dotPos + 1); + std::vector names = {first, second}; + return new NestedDynamicBinding(names); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/Binding/PseudoVariableBindings.h b/runtime/cpp/Compiler/Binding/PseudoVariableBindings.h new file mode 100644 index 00000000..0c8f437b --- /dev/null +++ b/runtime/cpp/Compiler/Binding/PseudoVariableBindings.h @@ -0,0 +1,125 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _PSEUDOVARIABLEBINDINGS_H_ +#define _PSEUDOVARIABLEBINDINGS_H_ +#include "Binding.h" +#include "../AST/SScriptNode.h" +#include + +namespace Egg { + +class TreecodeEncoder; + +class NilBinding : public Binding { +public: + NilBinding() : Binding(Kind::Constant, "nil", 0) {} + bool isLiteral() const override { return true; } + + void encodeUsing_(TreecodeEncoder* encoder) override; + + Binding* copy_() override { + return new NilBinding(); + } +}; + +class TrueBinding : public Binding { +public: + TrueBinding() : Binding(Kind::Constant, "true", 0) {} + bool isLiteral() const override { return true; } + + void encodeUsing_(TreecodeEncoder* encoder) override; + + Binding* copy_() override { + return new TrueBinding(); + } +}; + +class FalseBinding : public Binding { +public: + FalseBinding() : Binding(Kind::Constant, "false", 0) {} + bool isLiteral() const override { return true; } + + void encodeUsing_(TreecodeEncoder* encoder) override; + + Binding* copy_() override { + return new FalseBinding(); + } +}; + +class SelfBinding : public Binding { +public: + SelfBinding() : Binding(Kind::Argument, "self", 0) {} + bool canBeAssigned() const override { return false; } + + void beReferencedFrom_(SScriptNode* aScriptNode) override; + + void encodeUsing_(TreecodeEncoder* encoder) override; + + Binding* copy_() override { + return new SelfBinding(); + } +}; + +class SuperBinding : public Binding { +public: + SuperBinding() : Binding(Kind::Argument, "super", 0) {} + bool canBeAssigned() const override { return false; } + + void beReferencedFrom_(SScriptNode* aScriptNode) override; + + void encodeUsing_(TreecodeEncoder* encoder) override; + + Binding* copy_() override { + return new SuperBinding(); + } +}; + +class DynamicBinding : public Binding { +public: + DynamicBinding(const egg::string& name) : Binding(Kind::Global, name, 0) {} + + static DynamicBinding* named_(const egg::string& name); + + bool isDynamic() const override { return true; } + + void beReferencedFrom_(SScriptNode* aScriptNode) override; + + void encodeUsing_(TreecodeEncoder* encoder) override; + + Binding* copy_() override { + return new DynamicBinding(name()); + } +}; + +class NestedDynamicBinding : public DynamicBinding { +private: + std::vector _names; + +public: + NestedDynamicBinding(const std::vector& names) + : DynamicBinding(buildCompositeName(names)), _names(names) {} + + const std::vector& names() const { return _names; } + + void encodeUsing_(TreecodeEncoder* encoder) override; + + Binding* copy_() override { + return new NestedDynamicBinding(_names); + } + +private: + static egg::string buildCompositeName(const std::vector& names) { + egg::string result; + for (size_t i = 0; i < names.size(); ++i) { + if (i > 0) result += "."; + result += names[i]; + } + return result; + } +}; + +} // namespace Egg + +#endif // _PSEUDOVARIABLEBINDINGS_H_ diff --git a/runtime/cpp/Compiler/Binding/StackEnvironment.h b/runtime/cpp/Compiler/Binding/StackEnvironment.h new file mode 100644 index 00000000..27eb7397 --- /dev/null +++ b/runtime/cpp/Compiler/Binding/StackEnvironment.h @@ -0,0 +1,29 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _STACKENVIRONMENT_H_ +#define _STACKENVIRONMENT_H_ +#include "LocalEnvironment.h" +#include "../CompilerTypes.h" + +namespace Egg { + +/** + * Stack-allocated environment for local variables + * Corresponds to StackEnvironment in Smalltalk + */ +class StackEnvironment : public LocalEnvironment { +public: + StackEnvironment() {} + virtual ~StackEnvironment() {} + + int* index() override { return nullptr; } + bool isStack() const override { return true; } + int captureType() const override { return 0; } // Base implementation + uint8_t environmentType() const override { return AstBindingType::TemporaryBindingId; } +}; + +} // namespace Egg + +#endif // _STACKENVIRONMENT_H_ diff --git a/runtime/cpp/Compiler/Binding/TemporaryBinding.h b/runtime/cpp/Compiler/Binding/TemporaryBinding.h new file mode 100644 index 00000000..170767a6 --- /dev/null +++ b/runtime/cpp/Compiler/Binding/TemporaryBinding.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +*/ +#ifndef _TEMPORARYBINDING_H_ +#define _TEMPORARYBINDING_H_ +#include "LocalBinding.h" +#include "StackEnvironment.h" + +namespace Egg { + +class TemporaryBinding : public LocalBinding { +public: + TemporaryBinding(const egg::string& name, uint32_t position) + : LocalBinding(Kind::Temporary, name, position) { + _environment = new StackEnvironment(); + } + + bool isLiteral() const override { return true; } + bool isTemporary() const { return true; } + + void encodeUsing_(TreecodeEncoder* encoder) override; + + Binding* copy_() override { + auto copy = new TemporaryBinding(name(), position()); + copy->index_(_index); + if (_environment) { + if (auto stackEnv = dynamic_cast(_environment)) { + copy->_environment = new StackEnvironment(*stackEnv); + } else if (auto arrayEnv = dynamic_cast(_environment)) { + copy->_environment = new ArrayEnvironment(*arrayEnv); + } + } + return copy; + } +}; + +} // namespace Egg + +#endif // _TEMPORARYBINDING_H_ From 777250fd4b6853a0dc5be8da7f4f9c6658753919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Tue, 10 Mar 2026 00:22:53 -0300 Subject: [PATCH 04/15] Add more compiler classes and bootstrap-related components for AST processing - Introduced TonelReader.h for parsing Tonel-format .st files into ClassSpec. - Added CompilerTypes.h to define AST node and binding type identifiers. - Implemented MessageInliner for control-flow optimization in Message nodes. - Created SemanticVisitor for analyzing and transforming the AST, integrating MessageInliner. - Developed TreecodeEncoder to convert AST nodes into Egg treecode format. - Added necessary methods and structures to support encoding and inlining functionalities. --- runtime/cpp/Bootstrap/CodeSpecs.cpp | 76 ++++ runtime/cpp/Bootstrap/CodeSpecs.h | 148 ++++++++ runtime/cpp/Bootstrap/TonelReader.cpp | 352 +++++++++++++++++ runtime/cpp/Bootstrap/TonelReader.h | 61 +++ runtime/cpp/Compiler/CompilerTypes.h | 85 +++++ runtime/cpp/Compiler/MessageInliner.cpp | 273 +++++++++++++ runtime/cpp/Compiler/MessageInliner.h | 42 ++ runtime/cpp/Compiler/SemanticVisitor.cpp | 207 ++++++++++ runtime/cpp/Compiler/SemanticVisitor.h | 68 ++++ runtime/cpp/Compiler/TreecodeEncoder.cpp | 464 +++++++++++++++++++++++ runtime/cpp/Compiler/TreecodeEncoder.h | 111 ++++++ 11 files changed, 1887 insertions(+) create mode 100644 runtime/cpp/Bootstrap/CodeSpecs.cpp create mode 100644 runtime/cpp/Bootstrap/CodeSpecs.h create mode 100644 runtime/cpp/Bootstrap/TonelReader.cpp create mode 100644 runtime/cpp/Bootstrap/TonelReader.h create mode 100644 runtime/cpp/Compiler/CompilerTypes.h create mode 100644 runtime/cpp/Compiler/MessageInliner.cpp create mode 100644 runtime/cpp/Compiler/MessageInliner.h create mode 100644 runtime/cpp/Compiler/SemanticVisitor.cpp create mode 100644 runtime/cpp/Compiler/SemanticVisitor.h create mode 100644 runtime/cpp/Compiler/TreecodeEncoder.cpp create mode 100644 runtime/cpp/Compiler/TreecodeEncoder.h diff --git a/runtime/cpp/Bootstrap/CodeSpecs.cpp b/runtime/cpp/Bootstrap/CodeSpecs.cpp new file mode 100644 index 00000000..6614434f --- /dev/null +++ b/runtime/cpp/Bootstrap/CodeSpecs.cpp @@ -0,0 +1,76 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "CodeSpecs.h" +#include + +namespace Egg { + +// ---- ModuleSpec ---- + +ModuleSpec::~ModuleSpec() { + for (auto& [_, cls] : _classes) { + delete cls->metaclass(); + delete cls; + } +} + +void ModuleSpec::addClass(ClassSpec* cls) { + _classes[cls->name()] = cls; + cls->module(this); +} + +ClassSpec* ModuleSpec::resolveClass(const egg::string& name) const { + auto it = _classes.find(name); + return (it != _classes.end()) ? it->second : nullptr; +} + +// ---- ClassSpec ---- + +ClassSpec* ClassSpec::superclass() const { + if (_supername.empty() || _supername == U"nil") return nullptr; + if (!_module) return nullptr; + return _module->resolveClass(_supername); +} + +// ---- MetaclassSpec ---- + +const egg::string& MetaclassSpec::name() const { + static const egg::string empty; + if (!_instanceClass) return empty; + return _instanceClass->name(); +} + +ClassSpec* MetaclassSpec::superclass() const { + if (!_instanceClass) return nullptr; + return _instanceClass->superclass(); +} + +// ---- SpeciesSpec ---- + +uint32_t SpeciesSpec::instSize() const { + uint32_t count = _instanceVariables.size(); + auto super = superclass(); + while (super) { + count += super->instVarNames().size(); + super = super->superclass(); + } + return count; +} + +std::vector SpeciesSpec::allInstVarNames() const { + std::vector chain; + for (auto s = this; s != nullptr; + s = static_cast(s->superclass())) + chain.push_back(s); + std::reverse(chain.begin(), chain.end()); + std::vector result; + for (auto s : chain) + for (auto& iv : s->instVarNames()) + result.push_back(iv); + return result; +} + +} // namespace Egg diff --git a/runtime/cpp/Bootstrap/CodeSpecs.h b/runtime/cpp/Bootstrap/CodeSpecs.h new file mode 100644 index 00000000..dd03dd71 --- /dev/null +++ b/runtime/cpp/Bootstrap/CodeSpecs.h @@ -0,0 +1,148 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + + C++ port of modules/CodeSpecs/*.st — only the subset needed + for kernel bootstrapping (class sizes, format flags, hierarchy). + */ + +#ifndef _CODE_SPECS_H_ +#define _CODE_SPECS_H_ + +#include +#include +#include +#include "../Compiler/egg_string.h" + +namespace Egg { + +class ClassSpec; +class MetaclassSpec; +class ModuleSpec; + +// ---- MethodSpec ---- +// Mirrors modules/CodeSpecs/MethodSpec.st — minimal subset for bootstrapping + +class MethodSpec { +public: + MethodSpec() = default; + MethodSpec(const egg::string& source) : _source(source) {} + + const egg::string& source() const { return _source; } + void source(const egg::string& s) { _source = s; } + +private: + egg::string _source; +}; + +// ---- SpeciesSpec (base for ClassSpec and MetaclassSpec) ---- +// Mirrors modules/CodeSpecs/SpeciesSpec.st + +class SpeciesSpec { +public: + enum FormatFlags { + IsArrayed = 0x1, + IsBytes = 0x2 + }; + + SpeciesSpec() : _format(0), _module(nullptr) {} + virtual ~SpeciesSpec() = default; + + const std::vector& instVarNames() const { return _instanceVariables; } + void instVarNames(const std::vector& ivars) { _instanceVariables = ivars; } + int format() const { return _format; } + void beArrayed() { _format |= IsArrayed; } + void beBytes() { _format |= IsBytes; } + bool instancesAreArrayed() const { return _format & IsArrayed; } + bool instancesAreBytes() const { return _format & IsBytes; } + + void addMethod(const MethodSpec& m) { _methods.push_back(m); } + const std::vector& methods() const { return _methods; } + + ModuleSpec* module() const { return _module; } + void module(ModuleSpec* m) { _module = m; } + + virtual const egg::string& name() const = 0; + virtual ClassSpec* superclass() const = 0; + + uint32_t instSize() const; + std::vector allInstVarNames() const; + +protected: + std::vector _instanceVariables; + std::vector _methods; + int _format; + ModuleSpec* _module; +}; + +// ---- MetaclassSpec ---- +// Mirrors modules/CodeSpecs/MetaclassSpec.st + +class MetaclassSpec : public SpeciesSpec { +public: + MetaclassSpec() : _instanceClass(nullptr) {} + + ClassSpec* instanceClass() const { return _instanceClass; } + void instanceClass(ClassSpec* cls) { _instanceClass = cls; } + + const egg::string& name() const override; + ClassSpec* superclass() const override; + +private: + ClassSpec* _instanceClass; +}; + +// ---- ClassSpec ---- +// Mirrors modules/CodeSpecs/ClassSpec.st + +class ClassSpec : public SpeciesSpec { +public: + ClassSpec() : _metaclass(nullptr), _variable(false), _pointers(true) {} + + const egg::string& name() const override { return _name; } + void name(const egg::string& n) { _name = n; } + + const egg::string& supername() const { return _supername; } + void supername(const egg::string& s) { _supername = s; } + + ClassSpec* superclass() const override; + + MetaclassSpec* metaclass() const { return _metaclass; } + void metaclass(MetaclassSpec* mc) { _metaclass = mc; } + + bool isVariable() const { return _variable; } + void isVariable(bool v) { _variable = v; } + bool isPointers() const { return _pointers; } + void isPointers(bool p) { _pointers = p; } + + const std::vector& classVarNames() const { return _classVarNames; } + void classVarNames(const std::vector& cvars) { _classVarNames = cvars; } + +private: + egg::string _name; + egg::string _supername; + MetaclassSpec* _metaclass; + bool _variable; + bool _pointers; + std::vector _classVarNames; +}; + +// ---- ModuleSpec (minimal) ---- +// Mirrors modules/CodeSpecs/ModuleSpec.st — only class registry & resolution + +class ModuleSpec { +public: + ModuleSpec() = default; + ~ModuleSpec(); + + void addClass(ClassSpec* cls); + ClassSpec* resolveClass(const egg::string& name) const; + const std::map& classes() const { return _classes; } + +private: + std::map _classes; +}; + +} // namespace Egg + +#endif // _CODE_SPECS_H_ diff --git a/runtime/cpp/Bootstrap/TonelReader.cpp b/runtime/cpp/Bootstrap/TonelReader.cpp new file mode 100644 index 00000000..f1c39aa3 --- /dev/null +++ b/runtime/cpp/Bootstrap/TonelReader.cpp @@ -0,0 +1,352 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + + Stream-based Tonel reader modelled after modules/Tonel/TonelReader.st. + */ + +#include "TonelReader.h" +#include "CodeSpecs.h" + +namespace Egg { + +// ── Stream primitives ──────────────────────────────────────────────── + +bool TonelReader::atEnd() const { + return _pos >= _source.length(); +} + +char32_t TonelReader::peek() const { + return _source[_pos]; +} + +char32_t TonelReader::next() { + return _source[_pos++]; +} + +void TonelReader::skipSeparators() { + while (!atEnd() && (_source[_pos] == U' ' || _source[_pos] == U'\t' || + _source[_pos] == U'\n' || _source[_pos] == U'\r')) + _pos++; +} + +void TonelReader::skipLine() { + while (!atEnd() && _source[_pos] != U'\n') _pos++; + if (!atEnd()) _pos++; // skip the \n +} + +// ── Tonel structure ────────────────────────────────────────────────── + +ClassSpec* TonelReader::parseFile(const std::string& utf8Source) { + _source = egg::string(utf8Source); + _pos = 0; + + readComments(); + egg::string type = readType(); + auto fields = readDefinition(); + + auto* spec = new ClassSpec(); + auto* meta = new MetaclassSpec(); + meta->instanceClass(spec); + spec->metaclass(meta); + + spec->name(fields.count("name") ? fields["name"] : egg::string("")); + if (type == "Class") { + spec->supername(fields.count("superclass") ? fields["superclass"] : egg::string("")); + } + + // #type field: #variable (pointer-indexed), #bytes (byte-indexed) + if (fields.count("type")) { + egg::string t = fields["type"]; + if (t == "variable") { + spec->isVariable(true); + spec->isPointers(true); + } else if (t == "bytes") { + spec->isVariable(true); + spec->isPointers(false); + } + } + + // instVars / classVars / classInstVars are parsed from the STON array + // already stored as comma-separated names during parseSTONArray() + auto splitNames = [](const egg::string& csv) -> std::vector { + std::vector out; + if (csv.empty()) return out; + size_t start = 0; + while (start < csv.length()) { + size_t comma = csv.find(U',', start); + if (comma == egg::string::npos) comma = csv.length(); + if (comma > start) + out.push_back(csv.substr(start, comma - start)); + start = comma + 1; + } + return out; + }; + + if (fields.count("instVars")) + spec->instVarNames(splitNames(fields["instVars"])); + if (fields.count("classVars")) + spec->classVarNames(splitNames(fields["classVars"])); + if (fields.count("classInstVars")) + meta->instVarNames(splitNames(fields["classInstVars"])); + + readMethods(spec, meta); + return spec; +} + +// Mirrors TonelReader >> readComments +void TonelReader::readComments() { + skipSeparators(); + if (!atEnd() && peek() == U'"') { + next(); // skip opening " + skipComment(); + } +} + +// Mirrors TonelReader >> readType (via ReadStream >> nextWordOrNumber) +egg::string TonelReader::readType() { + skipSeparators(); + size_t start = _pos; + while (!atEnd() && ((_source[_pos] >= U'A' && _source[_pos] <= U'Z') || + (_source[_pos] >= U'a' && _source[_pos] <= U'z') || + (_source[_pos] >= U'0' && _source[_pos] <= U'9'))) + _pos++; + return _source.substr(start, _pos - start); +} + +// Mirrors TonelReader >> readDefinition (parses STON map) +std::map TonelReader::readDefinition() { + skipSeparators(); + return parseSTONMap(); +} + +// Mirrors TonelReader >> readMethods +void TonelReader::readMethods(ClassSpec* spec, MetaclassSpec* meta) { + while (true) { + skipSeparators(); + if (atEnd()) break; + readMethod(spec, meta); + } +} + +// Mirrors TonelReader >> readMethod +void TonelReader::readMethod(ClassSpec* spec, MetaclassSpec* meta) { + // 1. STON metadata { #category : #accessing } + skipSeparators(); + parseSTONMap(); // metadata — we don't need category during bootstrap + + // 2. ClassName [class] >> selector...signature [ + skipSeparators(); + // Read up to " >> " + egg::string className; + { + size_t sepPos = _source.find(egg::string(" >> "), _pos); + if (sepPos == egg::string::npos) return; + className = _source.substr(_pos, sepPos - _pos); + _pos = sepPos + 4; // skip " >> " + } + + // Trim className + while (!className.empty() && (className.back() == U' ' || className.back() == U'\t')) + className.pop_back(); + + bool isClassSide = false; + if (className.length() > 6) { + egg::string tail = className.substr(className.length() - 5, 5); + if (tail == "class" && + (className.length() == 5 || className[className.length() - 6] == U' ')) + isClassSide = true; + } + + // 3. Read signature up to [ + egg::string signature; + { + size_t bracketPos = _source.find(U'[', _pos); + if (bracketPos == egg::string::npos) return; + signature = _source.substr(_pos, bracketPos - _pos); + _pos = bracketPos + 1; // skip '[' + } + // Trim signature + while (!signature.empty() && (signature.back() == U' ' || signature.back() == U'\t' || + signature.back() == U'\n' || signature.back() == U'\r')) + signature.pop_back(); + while (!signature.empty() && (signature.front() == U' ' || signature.front() == U'\t' || + signature.front() == U'\n' || signature.front() == U'\r')) + signature.erase(signature.begin()); + + // 4. Read body via nextBlock (we already consumed the '[') + egg::string body = nextBlock(); + + // 5. Build method source = signature \n\t body + egg::string methodSource = signature + "\n\t" + body; + + if (isClassSide) + meta->addMethod(MethodSpec(methodSource)); + else + spec->addMethod(MethodSpec(methodSource)); +} + +// ── nextBlock ──────────────────────────────────────────────────────── +// Mirrors TonelReader >> nextBlock. +// Called after the opening '[' has already been consumed. +// Reads until the matching ']', handling nesting and literals. + +egg::string TonelReader::nextBlock() { + // Skip the rest of the line that contained '[' + size_t bodyStart = _pos; + while (bodyStart < _source.length() && + (_source[bodyStart] == U'\n' || _source[bodyStart] == U'\r')) + bodyStart++; + _pos = bodyStart; + + int nested = 1; + char32_t prev = 0; + while (!atEnd() && nested > 0) { + char32_t ch = next(); + if (ch == U'[' && prev != U'$') { + nested++; + } else if (ch == U']' && prev != U'$') { + nested--; + if (nested == 0) break; + } else if (ch == U'\'' && prev != U'$') { + skipString(); + } else if (ch == U'"' && prev != U'$') { + skipComment(); + } + prev = ch; + } + + // _pos now points just past the closing ']' + size_t bodyEnd = _pos - 1; // exclude ']' + + // Trim trailing whitespace/newlines from body, but NOT spaces (for $ literals) + while (bodyEnd > bodyStart && (_source[bodyEnd - 1] == U'\n' || _source[bodyEnd - 1] == U'\r' || + _source[bodyEnd - 1] == U'\t')) + bodyEnd--; + + if (bodyEnd <= bodyStart) return egg::string(""); + return _source.substr(bodyStart, bodyEnd - bodyStart); +} + +// ── Literal skipping ───────────────────────────────────────────────── + +// Mirrors TonelReader >> skipString +void TonelReader::skipString() { + skipToMatch(U'\''); +} + +// Mirrors TonelReader >> skipComment +void TonelReader::skipComment() { + skipToMatch(U'"'); +} + +// Mirrors TonelReader >> skipToMatch: +// Skips until a non-doubled occurrence of `ch` is found. +void TonelReader::skipToMatch(char32_t ch) { + while (!atEnd()) { + char32_t c = next(); + if (c == ch) { + if (!atEnd() && peek() == ch) { + next(); // doubled – skip and continue + } else { + return; // unmatched – done + } + } + } +} + +// ── Minimal STON parser ────────────────────────────────────────────── +// Just enough to parse Tonel definition headers and method metadata. + +std::map TonelReader::parseSTONMap() { + std::map map; + skipSeparators(); + if (atEnd() || peek() != U'{') return map; + next(); // skip '{' + + while (true) { + skipSeparators(); + if (atEnd() || peek() == U'}') { if (!atEnd()) next(); break; } + + egg::string key = parseSTONValue(); + skipSeparators(); + if (!atEnd() && peek() == U':') next(); // skip ':' + skipSeparators(); + egg::string value = parseSTONValue(); + + map[key] = value; + + skipSeparators(); + if (!atEnd() && peek() == U',') next(); // skip ',' + } + return map; +} + +egg::string TonelReader::parseSTONValue() { + skipSeparators(); + if (atEnd()) return ""; + char32_t ch = peek(); + if (ch == U'#') return parseSTONSymbol(); + if (ch == U'\'') return parseSTONString(); + if (ch == U'[') { + auto arr = parseSTONArray(); + // Encode as comma-separated string for ClassSpec consumption + egg::string result; + for (size_t i = 0; i < arr.size(); i++) { + if (i > 0) result += ","; + result += arr[i]; + } + return result; + } + // Bare word / number + size_t start = _pos; + while (!atEnd() && peek() != U' ' && peek() != U'\t' && peek() != U'\n' && + peek() != U'\r' && peek() != U',' && peek() != U'}' && peek() != U':') + _pos++; + return _source.substr(start, _pos - start); +} + +egg::string TonelReader::parseSTONSymbol() { + next(); // skip '#' + if (!atEnd() && peek() == U'\'') return parseSTONString(); // #'foo bar' + size_t start = _pos; + while (!atEnd() && peek() != U' ' && peek() != U'\t' && peek() != U'\n' && + peek() != U'\r' && peek() != U',' && peek() != U'}' && peek() != U':' && + peek() != U']') + _pos++; + return _source.substr(start, _pos - start); +} + +egg::string TonelReader::parseSTONString() { + next(); // skip opening ' + egg::string result; + while (!atEnd()) { + char32_t c = next(); + if (c == U'\'') { + if (!atEnd() && peek() == U'\'') { + next(); // escaped '' + result += U'\''; + } else { + break; + } + } else { + result += c; + } + } + return result; +} + +std::vector TonelReader::parseSTONArray() { + std::vector arr; + next(); // skip '[' + while (true) { + skipSeparators(); + if (atEnd() || peek() == U']') { if (!atEnd()) next(); break; } + arr.push_back(parseSTONValue()); + skipSeparators(); + if (!atEnd() && peek() == U',') next(); + } + return arr; +} + +} // namespace Egg diff --git a/runtime/cpp/Bootstrap/TonelReader.h b/runtime/cpp/Bootstrap/TonelReader.h new file mode 100644 index 00000000..43a6a93b --- /dev/null +++ b/runtime/cpp/Bootstrap/TonelReader.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + + Stream-based Tonel reader modelled after modules/Tonel/TonelReader.st. + */ + +#ifndef _TONEL_READER_H_ +#define _TONEL_READER_H_ + +#include +#include +#include +#include "CodeSpecs.h" + +namespace Egg { + +class TonelReader { +public: + TonelReader() {} + + // Parse a complete Tonel-format .st file and return a ClassSpec. + // The returned ClassSpec is heap-allocated; caller takes ownership. + ClassSpec* parseFile(const std::string& utf8Source); + +private: + // Stream-style helpers + bool atEnd() const; + char32_t peek() const; + char32_t next(); + void skipSeparators(); + void skipLine(); + + // Tonel structure (mirrors TonelReader.st >> read) + void readComments(); + egg::string readType(); + std::map readDefinition(); + void readMethods(ClassSpec* spec, MetaclassSpec* meta); + void readMethod(ClassSpec* spec, MetaclassSpec* meta); + + // Block / literal skipping (mirrors TonelReader.st) + egg::string nextBlock(); + void skipString(); + void skipComment(); + void skipToMatch(char32_t ch); + + // Minimal STON helpers + std::map parseSTONMap(); + egg::string parseSTONValue(); + egg::string parseSTONSymbol(); + egg::string parseSTONString(); + std::vector parseSTONArray(); + + // State + egg::string _source; + size_t _pos = 0; +}; + +} // namespace Egg + +#endif // _TONEL_READER_H_ diff --git a/runtime/cpp/Compiler/CompilerTypes.h b/runtime/cpp/Compiler/CompilerTypes.h new file mode 100644 index 00000000..2dc35200 --- /dev/null +++ b/runtime/cpp/Compiler/CompilerTypes.h @@ -0,0 +1,85 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _COMPILER_TYPES_H_ +#define _COMPILER_TYPES_H_ + +#include + +namespace Egg { + +/** + * AST Node Type identifiers used in treecode encoding + */ +enum AstNodeType : uint8_t { + MethodId = 101, + BlockId = 102, + IdentifierId = 103, + LiteralId = 104, + MessageId = 105, + CascadeId = 106, + BraceId = 107, + AssignmentId = 108, + ReturnId = 109, + PragmaId = 110 +}; + +/** + * Binding Type identifiers used in treecode encoding + */ +enum AstBindingType : uint8_t { + NilBindingId = 1, + TrueBindingId = 2, + FalseBindingId = 3, + ArgumentBindingId = 4, + TemporaryBindingId = 5, + SelfBindingId = 6, + SuperBindingId = 7, + DynamicVarId = 14, + NestedDynamicVarId = 15, + PushRid = 50, + PopRid = 51 +}; + +/** + * Closure Element Type identifiers + * Must match CompilerModule >> initializeClosureElementIds + */ +enum ClosureElementType : uint8_t { + ClosureSelf = 0, + ClosureLocalArgument = 1, + ClosureEnvironment = 2, + ClosureEnvironmentValue = 3, + ClosureInlinedArgument = 4 +}; + +/** + * Compiled Method flags + */ +struct CompiledMethodFlags { + static constexpr uint32_t ArgCount = 0x0000001F; // bits 0-4 + static constexpr uint32_t TempCount = 0x000003E0; // bits 5-9 + static constexpr uint32_t EnvCount = 0x00007C00; // bits 10-14 + static constexpr uint32_t BlockCount = 0x000F8000; // bits 15-19 + static constexpr uint32_t CapturesSelf = 0x00100000; // bit 20 + static constexpr uint32_t HasEnvironment = 0x00200000; // bit 21 + static constexpr uint32_t HasFrame = 0x00400000; // bit 22 + static constexpr uint32_t Debuggable = 0x00800000; // bit 23 +}; + +/** + * Compiled Block flags + */ +struct CompiledBlockFlags { + static constexpr uint32_t ArgCount = 0x0000001F; // bits 0-4 + static constexpr uint32_t TempCount = 0x000003E0; // bits 5-9 + static constexpr uint32_t EnvCount = 0x00007C00; // bits 10-14 + static constexpr uint32_t CapturesSelf = 0x00100000; // bit 20 + static constexpr uint32_t UsesHome = 0x00200000; // bit 21 +}; + +} // namespace Egg + +#endif // _COMPILER_TYPES_H_ diff --git a/runtime/cpp/Compiler/MessageInliner.cpp b/runtime/cpp/Compiler/MessageInliner.cpp new file mode 100644 index 00000000..1a3f7ac2 --- /dev/null +++ b/runtime/cpp/Compiler/MessageInliner.cpp @@ -0,0 +1,273 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "MessageInliner.h" +#include "AST/SMessageNode.h" +#include "AST/SBlockNode.h" +#include "AST/SIdentifierNode.h" +#include "AST/SNumberNode.h" +#include "AST/SSelectorNode.h" +#include + +namespace Egg { + +void MessageInliner::inline_(SMessageNode* aMessageNode) { + _message = aMessageNode; + + if (_message->receiver()->isSuper()) { + return; + } + + if (_message->isCascadeMessage() && _message->receiver()->isBlock()) { + return; + } + + SSelectorNode* selectorNode = static_cast(_message->selector()); + egg::string s = selectorNode->value(); + + if (s == "ifTrue:" || s == "ifFalse:" || s == "or:" || s == "and:" || + s == "timesRepeat:" || s == "andNot:" || s == "orNot:" || s == "ifNil:") { + inlineConditional(); + return; + } + + if (s == "ifTrue:ifFalse:" || s == "ifFalse:ifTrue:") { + inlineConditional(); + return; + } + + if (s == "ifNotNil:") { + inlineIfNotNil(); + return; + } + + if (s == "ifNil:ifNotNil:") { + inlineIfNilIfNotNil(); + return; + } + + if (s == "ifNotNil:ifNil:") { + inlineIfNotNilIfNil(); + return; + } + + if (s == "whileTrue:" || s == "whileFalse:") { + inlineWhile(); + return; + } + + if (s == "whileTrue" || s == "whileFalse") { + inlineUnitaryWhile(); + return; + } + + if (s == "repeat") { + inlineRepeat(); + return; + } + + if (s == "to:do:") { + inlineToDo(); + return; + } + + std::vector keywords; + size_t pos = 0; + while (pos < s.length()) { + size_t colon = s.find(':', pos); + if (colon == egg::string::npos) break; + keywords.push_back(s.substr(pos, colon - pos)); + pos = colon + 1; + } + + if (!keywords.empty()) { + if (keywords.back().empty()) { + keywords.pop_back(); + } + + bool allAnd = std::all_of(keywords.begin(), keywords.end(), + [](const egg::string& k) { return k == "and"; }); + if (allAnd) { + inlineConditional(); + return; + } + + bool allOr = std::all_of(keywords.begin(), keywords.end(), + [](const egg::string& k) { return k == "or"; }); + if (allOr) { + inlineConditional(); + return; + } + + if (keywords.size() > 1) { + egg::string last = keywords.back(); + bool allButLastAnd = std::all_of(keywords.begin(), keywords.end() - 1, + [](const egg::string& k) { return k == "and"; }); + if (allButLastAnd && (last == "ifTrue" || last == "ifFalse")) { + inlineConditional(); + return; + } + + bool allButLastOr = std::all_of(keywords.begin(), keywords.end() - 1, + [](const egg::string& k) { return k == "or"; }); + if (allButLastOr && (last == "ifTrue" || last == "ifFalse")) { + inlineConditional(); + return; + } + } + } +} + +void MessageInliner::inlineConditional() { + auto& arguments = _message->arguments(); + if (arguments.size() < 1) return; + + for (auto arg : arguments) { + if (!arg->isEvaluable()) return; + } + + _message->beInlined_(); + + for (auto arg : arguments) { + if (arg->isBlock()) { + static_cast(arg)->beInlined_(); + } + } +} + +void MessageInliner::inlineIfNotNil() { + auto& arguments = _message->arguments(); + if (arguments.size() != 1) return; + + SParseNode* arg = arguments[0]; + bool isValidArg = arg->isEvaluable() || + (arg->isBlock() && static_cast(arg)->arguments().size() == 1); + if (!isValidArg) return; + + _message->beInlined_(); + if (arg->isBlock()) { + static_cast(arg)->beInlined_(); + } +} + +void MessageInliner::inlineIfNilIfNotNil() { + auto& arguments = _message->arguments(); + if (arguments.size() != 2) return; + + if (!arguments[0]->isEvaluable()) return; + + SParseNode* arg = arguments[1]; + bool isValidArg = arg->isEvaluable() || + (arg->isBlock() && static_cast(arg)->arguments().size() == 1); + if (!isValidArg) return; + + _message->beInlined_(); + + for (auto a : arguments) { + if (a->isBlock()) { + static_cast(a)->beInlined_(); + } + } +} + +void MessageInliner::inlineIfNotNilIfNil() { + auto& arguments = _message->arguments(); + if (arguments.size() != 2) return; + + if (!arguments[1]->isEvaluable()) return; + + SParseNode* arg = arguments[0]; + bool isValidArg = arg->isEvaluable() || + (arg->isBlock() && static_cast(arg)->arguments().size() == 1); + if (!isValidArg) return; + + _message->beInlined_(); + + for (auto a : arguments) { + if (a->isBlock()) { + static_cast(a)->beInlined_(); + } + } +} + +void MessageInliner::inlineRepeat() { + SParseNode* receiver = _message->receiver(); + if (!receiver->isEvaluable()) return; + + auto& arguments = _message->arguments(); + if (!arguments.empty()) return; + + if (!receiver->isBlock()) return; + + _message->beInlined_(); + static_cast(receiver)->beInlined_(); +} + +void MessageInliner::inlineToDo() { + auto& arguments = _message->arguments(); + if (arguments.size() != 2) return; + + SParseNode* last = arguments[1]; + if (!last->isBlock()) return; + + SBlockNode* block = static_cast(last); + if (block->arguments().size() != 1) return; + + _message->beInlined_(); + block->beInlined_(); +} + +void MessageInliner::inlineToByDo() { + auto& arguments = _message->arguments(); + if (arguments.size() != 3) return; + + SParseNode* arg = arguments[2]; + if (!arg->isBlock()) return; + + SBlockNode* block = static_cast(arg); + if (block->arguments().size() != 1) return; + + SParseNode* step = arguments[1]; + + _message->beInlined_(); + block->beInlined_(); +} + +void MessageInliner::inlineUnitaryWhile() { + SParseNode* receiver = _message->receiver(); + if (!receiver->isEvaluable()) return; + + auto& arguments = _message->arguments(); + if (arguments.size() != 0) return; + + inlineConditional(); + + if (receiver->isBlock()) { + _message->beInlined_(); + static_cast(receiver)->beInlined_(); + } +} + +void MessageInliner::inlineWhile() { + SParseNode* receiver = _message->receiver(); + if (!receiver->isEvaluable()) return; + + auto& arguments = _message->arguments(); + if (arguments.size() != 1) return; + + SParseNode* last = arguments[0]; + if (!last->isBlock()) return; + + SBlockNode* block = static_cast(last); + if (!block->isNullary()) return; + + inlineConditional(); + + if (receiver->isBlock()) { + static_cast(receiver)->beInlined_(); + } +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/MessageInliner.h b/runtime/cpp/Compiler/MessageInliner.h new file mode 100644 index 00000000..fc05e979 --- /dev/null +++ b/runtime/cpp/Compiler/MessageInliner.h @@ -0,0 +1,42 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _MESSAGE_INLINER_H_ +#define _MESSAGE_INLINER_H_ + +#include + +namespace Egg { + +class SMessageNode; + +/** + * Message inliner for control-flow optimization + * Corresponds to MessageInliner in Smalltalk + */ +class MessageInliner { +private: + SMessageNode* _message; + + void inlineConditional(); + void inlineIfNotNil(); + void inlineIfNilIfNotNil(); + void inlineIfNotNilIfNil(); + void inlineRepeat(); + void inlineToDo(); + void inlineToByDo(); + void inlineUnitaryWhile(); + void inlineWhile(); + +public: + MessageInliner() : _message(nullptr) {} + virtual ~MessageInliner() {} + + void inline_(SMessageNode* aMessageNode); +}; + +} // namespace Egg + +#endif // _MESSAGE_INLINER_H_ diff --git a/runtime/cpp/Compiler/SemanticVisitor.cpp b/runtime/cpp/Compiler/SemanticVisitor.cpp new file mode 100644 index 00000000..fa062f1e --- /dev/null +++ b/runtime/cpp/Compiler/SemanticVisitor.cpp @@ -0,0 +1,207 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SemanticVisitor.h" +#include "AST/SIdentifierNode.h" +#include "AST/SAssignmentNode.h" +#include "AST/SMessageNode.h" +#include "AST/SBlockNode.h" +#include "AST/SMethodNode.h" +#include "AST/SReturnNode.h" +#include "AST/SBraceNode.h" +#include "AST/SCascadeNode.h" +#include "AST/SCommentNode.h" +#include "AST/SSelectorNode.h" +#include "AST/SNumberNode.h" +#include "AST/SStringNode.h" +#include "AST/SPragmaNode.h" +#include "AST/SLiteralNode.h" +#include "SSmalltalkCompiler.h" +#include "Binding/Binding.h" +#include "Binding/ScriptScope.h" + +namespace Egg { + +SemanticVisitor::SemanticVisitor() { + _inliner = new MessageInliner(); +} + +SemanticVisitor::~SemanticVisitor() { + delete _inliner; +} + + +void SemanticVisitor::analyzeAssignment_(SAssignmentNode* anAssignmentNode) { + auto& assignees = anAssignmentNode->assignees(); + for (auto v : assignees) { + analyzeIdentifier_assignee_(v, true); + } +} + +void SemanticVisitor::analyzeBlock_while_(SBlockNode* aBlockNode, std::function aBlock) { + if (!aBlockNode->isInlined()) { + aBlockNode->index_(aBlockNode->compiler()->blockIndex()); + } + analyzeScript_while_(aBlockNode, aBlock); +} + +void SemanticVisitor::analyzeIdentifier_(SIdentifierNode* anIdentifierNode) { + analyzeIdentifier_assignee_(anIdentifierNode, false); +} + +void SemanticVisitor::analyzeIdentifier_assignee_(SIdentifierNode* anIdentifierNode, bool aBoolean) { + anIdentifierNode->resolveAssigning_(aBoolean); + if (aBoolean) { + anIdentifierNode->beAssigned(); + } + + auto compiler = anIdentifierNode->compiler(); + SScriptNode* script = compiler->activeScript(); + Binding* binding = anIdentifierNode->binding(); + + if (script && binding) { + script->reference_(binding); + } + + if (binding && binding->isLocal()) { + binding = script->scope()->captureLocal_(binding); + } + + anIdentifierNode->binding_(binding); +} + +void SemanticVisitor::analyzeMessage_(SMessageNode* aMessageNode) { + _inliner->inline_(aMessageNode); + if (!aMessageNode->isInlined()) { + aMessageNode->compiler()->noticeSend(); + } +} + +void SemanticVisitor::analyzeMethod_while_(SMethodNode* aMethodNode, std::function aBlock) { + analyzeScript_while_(aMethodNode, aBlock); +} + +void SemanticVisitor::analyzeReturn_(SReturnNode* aReturnNode) { + auto compiler = aReturnNode->compiler(); + SScriptNode* activeScript = compiler->activeScript(); + if (activeScript) { + SScriptNode* realScript = activeScript->realScript(); + if (realScript) { + realScript->captureHome(); + } + } +} + +void SemanticVisitor::analyzeScript_while_(SScriptNode* aScriptNode, std::function aBlock) { + aScriptNode->compiler()->activate_while_(aScriptNode, aBlock); +} + + +void SemanticVisitor::visitAssignment_(SAssignmentNode* node) { + analyzeAssignment_(node); + SParseNode* expression = node->expression(); + if (expression) { + expression->acceptVisitor_(this); + } +} + +void SemanticVisitor::visitBlock_(SBlockNode* node) { + analyzeBlock_while_(node, [this, node]() { + auto& statements = node->statements(); + for (auto stmt : statements) { + stmt->acceptVisitor_(this); + } + }); +} + +void SemanticVisitor::visitBrace_(SBraceNode* node) { + if (!node->isLiteral()) { + SParseNode* asMessage = node->asSMessageNode(); + if (asMessage && asMessage->isMessage()) { + asMessage->acceptVisitor_(this); + } + } +} + +void SemanticVisitor::visitCascade_(SCascadeNode* node) { + SParseNode* receiver = node->receiver(); + if (receiver) { + receiver->acceptVisitor_(this); + } + + auto& messages = node->messages(); + for (auto msg : messages) { + msg->acceptVisitor_(this); + } +} + +void SemanticVisitor::visitIdentifier_(SIdentifierNode* node) { + analyzeIdentifier_(node); +} + +void SemanticVisitor::visitMessage_(SMessageNode* node) { + analyzeMessage_(node); + + SParseNode* receiver = node->receiver(); + if (receiver) { + receiver->acceptVisitor_(this); + } + + auto& arguments = node->arguments(); + for (auto arg : arguments) { + arg->acceptVisitor_(this); + } +} + +void SemanticVisitor::visitMethod_(SMethodNode* node) { + analyzeMethod_while_(node, [this, node]() { + node->bindLocals(); + + auto& statements = node->statements(); + for (auto s : statements) { + s->acceptVisitor_(this); + } + + node->positionLocals(); + }); +} + +void SemanticVisitor::visitReturn_(SReturnNode* node) { + SParseNode* expression = node->expression(); + if (expression) { + expression->acceptVisitor_(this); + } + analyzeReturn_(node); +} + + +void SemanticVisitor::visitLiteral_(SLiteralNode* node) { +} + +void SemanticVisitor::visitComment_(SCommentNode* node) { +} + +void SemanticVisitor::visitSelector_(SSelectorNode* node) { +} + +void SemanticVisitor::visitNumberNode_(SNumberNode* node) { +} + +void SemanticVisitor::visitString_(SStringNode* node) { +} + +void SemanticVisitor::visitPragma_(SPragmaNode* node) { +} + +void SemanticVisitor::visitPrimitivePragma_(SPragmaNode* node) { +} + +void SemanticVisitor::visitFFIPragma_(SPragmaNode* node) { +} + +void SemanticVisitor::visitSymbolicPragma_(SPragmaNode* node) { +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/SemanticVisitor.h b/runtime/cpp/Compiler/SemanticVisitor.h new file mode 100644 index 00000000..d36e2b42 --- /dev/null +++ b/runtime/cpp/Compiler/SemanticVisitor.h @@ -0,0 +1,68 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _SEMANTIC_VISITOR_H_ +#define _SEMANTIC_VISITOR_H_ + +#include "AST/SParseNodeVisitor.h" +#include "MessageInliner.h" +#include + +namespace Egg { + +class SIdentifierNode; +class SAssignmentNode; +class SMessageNode; +class SBlockNode; +class SMethodNode; +class SReturnNode; +class SScriptNode; +class SBraceNode; +class SCascadeNode; +class SCommentNode; + +/** + * Semantic visitor for analyzing and transforming the AST + * Corresponds to SSemanticVisitor in Smalltalk + */ +class SemanticVisitor : public SParseNodeVisitor { +private: + MessageInliner* _inliner; + + void analyzeAssignment_(SAssignmentNode* anAssignmentNode); + void analyzeBlock_while_(SBlockNode* aBlockNode, std::function aBlock); + void analyzeIdentifier_(SIdentifierNode* anIdentifierNode); + void analyzeIdentifier_assignee_(SIdentifierNode* anIdentifierNode, bool aBoolean); + void analyzeMessage_(SMessageNode* aMessageNode); + void analyzeMethod_while_(SMethodNode* aMethodNode, std::function aBlock); + void analyzeReturn_(SReturnNode* aReturnNode); + void analyzeScript_while_(SScriptNode* aScriptNode, std::function aBlock); + +public: + SemanticVisitor(); + virtual ~SemanticVisitor(); + + void visitIdentifier_(SIdentifierNode* node) override; + void visitLiteral_(SLiteralNode* node) override; + void visitMessage_(SMessageNode* node) override; + void visitAssignment_(SAssignmentNode* node) override; + void visitReturn_(SReturnNode* node) override; + void visitMethod_(SMethodNode* node) override; + void visitBlock_(SBlockNode* node) override; + void visitCascade_(SCascadeNode* node) override; + void visitBrace_(SBraceNode* node) override; + void visitComment_(SCommentNode* node) override; + void visitSelector_(SSelectorNode* node) override; + void visitNumberNode_(SNumberNode* node) override; + void visitString_(SStringNode* node) override; + void visitPragma_(SPragmaNode* node) override; + void visitPrimitivePragma_(SPragmaNode* node) override; + void visitFFIPragma_(SPragmaNode* node) override; + void visitSymbolicPragma_(SPragmaNode* node) override; +}; + +} // namespace Egg + +#endif // _SEMANTIC_VISITOR_H_ diff --git a/runtime/cpp/Compiler/TreecodeEncoder.cpp b/runtime/cpp/Compiler/TreecodeEncoder.cpp new file mode 100644 index 00000000..ce0d9b50 --- /dev/null +++ b/runtime/cpp/Compiler/TreecodeEncoder.cpp @@ -0,0 +1,464 @@ +#include "TreecodeEncoder.h" +#include "AST/SParseNode.h" +#include "AST/SScriptNode.h" +#include "Binding/LocalBinding.h" +#include "Binding/ArrayEnvironment.h" +#include "Binding/BlockScope.h" +#include +#include + +namespace Egg { + +TreecodeEncoder::TreecodeEncoder() : method_(nullptr), script_(nullptr) { +} + +std::vector TreecodeEncoder::encodeMethod(SMethodNode* method) { + stream_.clear(); + literals_.clear(); + + visitMethod_(method); + return stream_; +} + +void TreecodeEncoder::visitMethod_(SMethodNode* node) { + nextTypePut(MethodId); + + if (node->pragma() && node->pragma()->isUsed()) { + nextTypePut(PragmaId); + const egg::string& pragmaName = node->pragma()->name(); + if (!pragmaName.empty()) { + nextSymbolPut(pragmaName); + } else { + nextPut(0); + } + } + + visitScript_(node); +} + +void TreecodeEncoder::visitBlock_(SBlockNode* node) { + nextTypePut(BlockId); + + bool inlined = node->isInlined(); + nextBooleanPut(inlined); + + if (inlined) { + const auto& args = node->arguments(); + std::vector argIndices; + for (auto* arg : args) { + if (auto* idNode = dynamic_cast(arg)) { + if (auto* localBinding = dynamic_cast(idNode->binding())) { + argIndices.push_back(static_cast(localBinding->index())); + } + } + } + nextPutAll(argIndices); + } else { + int index = compiledBlockIndexOf_(node); + nextPut(static_cast(index)); + + std::vector captured = encodeClosureElements_(node); + nextPutAll(captured); + } + + visitScript_(node); +} + +void TreecodeEncoder::visitMessage_(SMessageNode* node) { + nextTypePut(MessageId); + + nextBooleanPut(node->isInlined()); + + egg::string selectorStr = node->selector()->symbol(); + nextSymbolPut(selectorStr); + + if (node->receiver()) { + node->receiver()->acceptVisitor_(this); + } + + const auto& args = node->arguments(); + nextIntegerPut(args.size()); + for (auto* arg : args) { + if (arg) { + arg->acceptVisitor_(this); + } + } +} + +void TreecodeEncoder::visitIdentifier_(SIdentifierNode* node) { + nextTypePut(IdentifierId); + + if (node->binding()) { + node->binding()->encodeUsing_(this); + } else { + // No binding resolved — check for pseudo variables first + auto name = node->name(); + if (name == "self") { + encodeSelf(); + } else if (name == "super") { + encodeSuper(); + } else if (name == "nil") { + encodeNil(); + } else if (name == "true") { + encodeTrue(); + } else if (name == "false") { + encodeFalse(); + } else { + // Unknown identifier — treat as dynamic variable (resolved at runtime) + encodeDynamicVar_(name); + } + } +} + +void TreecodeEncoder::visitLiteral_(SLiteralNode* node) { + nextTypePut(LiteralId); + + const auto& lv = node->literalValue(); + int index = findLiteralIndex_ifAbsent_(lv, [&](){ + return addLiteral(lv); + }); + nextIntegerPut(index); +} + +void TreecodeEncoder::visitAssignment_(SAssignmentNode* node) { + nextTypePut(AssignmentId); + + const auto& assignees = node->assignees(); + nextIntegerPut(assignees.size()); + + for (auto* assignee : assignees) { + if (assignee) { + assignee->acceptVisitor_(this); + } + } + + if (node->expression()) { + node->expression()->acceptVisitor_(this); + } +} + +void TreecodeEncoder::visitReturn_(SReturnNode* node) { + nextTypePut(ReturnId); + + bool isMethod = script_ && script_->realScript()->isMethod(); + nextBooleanPut(isMethod); + + if (node->expression()) { + node->expression()->acceptVisitor_(this); + } +} + +void TreecodeEncoder::visitCascade_(SCascadeNode* node) { + nextTypePut(CascadeId); + + if (node->receiver()) { + node->receiver()->acceptVisitor_(this); + } + + const auto& messages = node->messages(); + nextIntegerPut(messages.size()); + + for (auto* msg : messages) { + egg::string selectorStr = msg->selector()->symbol(); + + nextSymbolPut(selectorStr); + + const auto& args = msg->arguments(); + nextIntegerPut(args.size()); + for (auto* arg : args) { + if (arg) { + arg->acceptVisitor_(this); + } + } + } +} + +void TreecodeEncoder::visitStatements(const std::vector& statements) { + nextIntegerPut(statements.size()); + for (auto* stmt : statements) { + if (stmt) { + stmt->acceptVisitor_(this); + } + } +} + +void TreecodeEncoder::visitScript_(SScriptNode* node) { + SScriptNode* prev = script_; + script_ = node; + + const auto& statements = node->statements(); + nextIntegerPut(statements.size()); + for (auto* stmt : statements) { + if (stmt) { + stmt->acceptVisitor_(this); + } + } + + script_ = prev; +} + +void TreecodeEncoder::visitBrace_(SBraceNode* node) { + auto cascadeNode = dynamic_cast(node->asSMessageNode()); + if (cascadeNode) { + visitCascade_(cascadeNode); + } else { + nextTypePut(BraceId); + } +} + +void TreecodeEncoder::nextPut(uint8_t byte) { + stream_.push_back(byte); +} + +void TreecodeEncoder::nextPutAll(const std::vector& bytes) { + nextIntegerPut(bytes.size()); + stream_.insert(stream_.end(), bytes.begin(), bytes.end()); +} + +void TreecodeEncoder::nextIntegerPut(int64_t value) { + if (value > 127 || value < -127) { + nextBigIntegerPut(value); + return; + } + + uint8_t byte = (value >= 0) ? value : (value + 0x100); + nextPut(byte); +} + +void TreecodeEncoder::nextBigIntegerPut(int64_t value) { + nextPut(0x80); + + for (int i = 0; i < 8; i++) { + nextPut((value >> (i * 8)) & 0xFF); + } +} + +void TreecodeEncoder::nextBooleanPut(bool value) { + nextPut(value ? 1 : 0); +} + +void TreecodeEncoder::nextSymbolPut(const egg::string& symbol) { + auto symLv = LiteralValue::fromSymbol(symbol); + int index = findLiteralIndex_ifAbsent_(symLv, [&](){ + return addLiteral(symLv); + }); + nextIntegerPut(index); +} + +void TreecodeEncoder::nextLiteralPut(const egg::string& literal) { + auto litLv = LiteralValue::fromString(literal); + int index = literalIndexOf(litLv); + if (index < 0) { + index = addLiteral(litLv); + } + nextIntegerPut(index); +} + +void TreecodeEncoder::nextTypePut(uint8_t typeId) { + nextPut(typeId); +} + +int TreecodeEncoder::literalIndexOf(const LiteralValue& literal) { + for (size_t i = 0; i < literals_.size(); i++) { + if (literals_[i] == literal) { + return i + 1; // 1-based + } + } + return -1; +} + +int TreecodeEncoder::addLiteral(const LiteralValue& literal) { + literals_.push_back(literal); + return literals_.size(); // 1-based +} + +void TreecodeEncoder::visitSelector_(SSelectorNode* node) { + nextSymbolPut(node->symbol()); +} + +void TreecodeEncoder::visitNumberNode_(SNumberNode* node) { + visitLiteral_(node); +} + +void TreecodeEncoder::visitString_(SStringNode* node) { + visitLiteral_(node); +} + + +void TreecodeEncoder::visitPragma_(SPragmaNode* node) { + nextTypePut(PragmaId); + + if (node->isPrimitive()) { + nextIntegerPut(static_cast(SPragmaNode::Type::Primitive)); + nextIntegerPut(node->primitiveNumber()); + nextSymbolPut(node->name()); + } else if (node->isFFI()) { + nextIntegerPut(static_cast(SPragmaNode::Type::FFI)); + nextSymbolPut(node->name()); + } else if (node->isSymbolic()) { + nextIntegerPut(static_cast(SPragmaNode::Type::Symbolic)); + nextSymbolPut(node->name()); + } +} + +void TreecodeEncoder::visitPrimitivePragma_(SPragmaNode* node) { + visitPragma_(node); +} + +void TreecodeEncoder::visitFFIPragma_(SPragmaNode* node) { + visitPragma_(node); +} + +void TreecodeEncoder::visitSymbolicPragma_(SPragmaNode* node) { + visitPragma_(node); +} + +void TreecodeEncoder::visitComment_(SCommentNode* node) { +} + + +void TreecodeEncoder::encodeNil() { + stream_.push_back(NilBindingId); +} + +void TreecodeEncoder::encodeTrue() { + stream_.push_back(TrueBindingId); +} + +void TreecodeEncoder::encodeFalse() { + stream_.push_back(FalseBindingId); +} + +void TreecodeEncoder::encodeSelf() { + stream_.push_back(SelfBindingId); +} + +void TreecodeEncoder::encodeSuper() { + stream_.push_back(SuperBindingId); +} + +void TreecodeEncoder::encodePushR() { + stream_.push_back(PushRid); +} + +void TreecodeEncoder::encodePopR() { + stream_.push_back(PopRid); +} + +void TreecodeEncoder::encodeDynamicVar_(const egg::string& name) { + stream_.push_back(DynamicVarId); + nextSymbolPut(name); +} + +void TreecodeEncoder::encodeNestedDynamicVar_(const egg::string& name) { + stream_.push_back(NestedDynamicVarId); + nextLiteralPut(name); +} + +void TreecodeEncoder::encodeArgument_env_(int index, LocalEnvironment* environment) { + int encoded = encodedEnvironment_(environment); + nextPut(ArgumentBindingId); + nextIntegerPut(index); + nextIntegerPut(encoded); +} + +void TreecodeEncoder::encodeTemporary_env_(int index, LocalEnvironment* environment) { + int encoded = encodedEnvironment_(environment); + nextPut(TemporaryBindingId); + nextIntegerPut(index); + nextIntegerPut(encoded); +} + +int TreecodeEncoder::encodedEnvironment_(LocalEnvironment* environment) { + if (environment->isStack()) { + return environment->isInlinedArgument() ? -1 : -2; + } + + auto arrayEnv = dynamic_cast(environment); + if (arrayEnv && arrayEnv->isCurrent()) { + return 0; + } + + int* indexPtr = environment->index(); + return indexPtr ? *indexPtr : 0; +} + +std::vector TreecodeEncoder::encodeClosureElements_(SBlockNode* node) { + std::vector result; + + auto scope = dynamic_cast(node->scope()); + if (!scope) return result; + + auto parent = node->parent()->realScript(); + + if (scope->capturesSelf()) { + result.push_back(ClosureSelf); + } + + auto capturedEnvs = scope->capturedEnvironments_(); + for (auto* env : capturedEnvs) { + if (env == parent) { + result.push_back(ClosureEnvironment); + } else { + int* idx = scope->environmentIndexOf_(dynamic_cast(env)); + if (idx) { + result.push_back(ClosureEnvironmentValue); + result.push_back(static_cast(*idx)); + } + } + } + + auto capturedArgs = scope->capturedArguments_(); + for (auto* binding : capturedArgs) { + // Resolve from parent scope to get original binding (matches Smalltalk: + // binding := aBlockNode parent scope resolve: a name) + auto parentBinding = node->parent()->scope()->resolve_(binding->name()); + if (auto* localBinding = dynamic_cast(parentBinding)) { + result.push_back(localBinding->environmentObject()->captureType()); + result.push_back(static_cast(localBinding->index())); + } + } + + return result; +} + +int TreecodeEncoder::compiledBlockIndexOf_(SBlockNode* node) { + // Search the literals for a Block literal with matching id, like + // Smalltalk's compiledBlockIndexOf: which does findFirst: on the method's literals + int blockId = node->index(); + for (size_t i = 0; i < literals_.size(); i++) { + if (literals_[i].isBlock() && literals_[i].asBlock().id == blockId) { + return i + 1; // 1-based index + } + } + + // Block not found in literals — add it now + int argCount = node->arguments().size(); + int tempCount = 0; + int envCount = 0; + bool capturesSelf = false; + bool capturesHome = false; + + auto scope = node->scope(); + if (scope) { + tempCount = scope->stackSize(); + envCount = scope->environmentSize(); + capturesSelf = scope->capturesSelf(); + auto blockScope = dynamic_cast(scope); + if (blockScope) { + capturesHome = blockScope->capturesHome_(); + } + } + + auto blockLit = LiteralValue::fromBlock(blockId, argCount, tempCount, + envCount, capturesSelf, capturesHome); + return addLiteral(blockLit); +} + +int TreecodeEncoder::findLiteralIndex_ifAbsent_(const LiteralValue& literal, std::function block) { + int index = literalIndexOf(literal); + if (index >= 0) return index; + return block(); +} + +} // namespace Egg diff --git a/runtime/cpp/Compiler/TreecodeEncoder.h b/runtime/cpp/Compiler/TreecodeEncoder.h new file mode 100644 index 00000000..1e48b211 --- /dev/null +++ b/runtime/cpp/Compiler/TreecodeEncoder.h @@ -0,0 +1,111 @@ +#ifndef TREECODE_ENCODER_H +#define TREECODE_ENCODER_H + +#include "AST/SParseNode.h" +#include "AST/SParseNodeVisitor.h" +#include "AST/SMethodNode.h" +#include "AST/SBlockNode.h" +#include "AST/SMessageNode.h" +#include "AST/SIdentifierNode.h" +#include "AST/SLiteralNode.h" +#include "AST/SAssignmentNode.h" +#include "AST/SReturnNode.h" +#include "AST/SCascadeNode.h" +#include "AST/SBraceNode.h" +#include "AST/SCascadeMessageNode.h" +#include "AST/SSelectorNode.h" +#include "AST/SNumberNode.h" +#include "AST/SStringNode.h" +#include "AST/SPragmaNode.h" +#include "AST/SCommentNode.h" +#include "Backend/SCompiledMethod.h" +#include "Binding/LocalEnvironment.h" +#include "CompilerTypes.h" +#include +#include +#include +#include +#include + +namespace Egg { + +class SScriptNode; +class LocalEnvironment; + +using TreecodeId = AstNodeType; +using BindingId = AstBindingType; + +/** + * TreecodeEncoder converts AST nodes to the Egg treecode format. + * This is a tree-based intermediate representation used by the Egg runtime. + */ +class TreecodeEncoder : public SParseNodeVisitor { +public: + TreecodeEncoder(); + + std::vector encodeMethod(SMethodNode* method); + + void visitMethod_(SMethodNode* node) override; + void visitBlock_(SBlockNode* node) override; + void visitMessage_(SMessageNode* node) override; + void visitIdentifier_(SIdentifierNode* node) override; + void visitLiteral_(SLiteralNode* node) override; + void visitAssignment_(SAssignmentNode* node) override; + void visitReturn_(SReturnNode* node) override; + void visitCascade_(SCascadeNode* node) override; + void visitBrace_(SBraceNode* node) override; + void visitSelector_(SSelectorNode* node) override; + void visitNumberNode_(SNumberNode* node) override; + void visitString_(SStringNode* node) override; + void visitPragma_(SPragmaNode* node) override; + void visitPrimitivePragma_(SPragmaNode* node) override; + void visitFFIPragma_(SPragmaNode* node) override; + void visitSymbolicPragma_(SPragmaNode* node) override; + void visitComment_(SCommentNode* node) override; + + const std::vector& treecode() const { return stream_; } + const std::vector& literals() const { return literals_; } + + void encodeNil(); + void encodeTrue(); + void encodeFalse(); + void encodeSelf(); + void encodeSuper(); + void encodePushR(); + void encodePopR(); + void encodeDynamicVar_(const egg::string& name); + void encodeNestedDynamicVar_(const egg::string& name); + void encodeArgument_env_(int index, LocalEnvironment* environment); + void encodeTemporary_env_(int index, LocalEnvironment* environment); + +private: + std::vector stream_; // Output stream for treecode bytes + std::vector literals_; // Typed literal pool for the method + SCompiledMethod* method_; // Current method being compiled + SScriptNode* script_; // Current script node (for context tracking) + + void nextPut(uint8_t byte); + void nextPutAll(const std::vector& bytes); + void nextIntegerPut(int64_t value); + void nextBigIntegerPut(int64_t value); + void nextBooleanPut(bool value); + void nextSymbolPut(const egg::string& symbol); + void nextLiteralPut(const egg::string& literal); + void nextTypePut(uint8_t typeId); + + int encodedEnvironment_(LocalEnvironment* environment); + + int compiledBlockIndexOf_(SBlockNode* node); + std::vector encodeClosureElements_(SBlockNode* node); + + void visitStatements(const std::vector& statements); + void visitScript_(SScriptNode* node); + + int findLiteralIndex_ifAbsent_(const LiteralValue& literal, std::function block); + int literalIndexOf(const LiteralValue& literal); + int addLiteral(const LiteralValue& literal); +}; + +} // namespace Egg + +#endif // TREECODE_ENCODER_H From 7881081c684d50cc0a5171b7cecc093cd33a66c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Wed, 11 Mar 2026 00:21:25 -0300 Subject: [PATCH 05/15] Add BootstrappedKernel, fix may small literal-related things and other subtleties --- runtime/cpp/Bootstrap/BootstrappedKernel.cpp | 47 ++++++++++ runtime/cpp/Bootstrap/BootstrappedKernel.h | 33 +++++++ runtime/cpp/Compiler/AST/SBraceNode.cpp | 4 +- .../cpp/Compiler/AST/SCascadeMessageNode.h | 3 +- runtime/cpp/Compiler/AST/SLiteralNode.h | 5 +- runtime/cpp/Compiler/AST/SMethodNode.cpp | 6 ++ runtime/cpp/Compiler/AST/SNumberNode.h | 17 ---- .../cpp/Compiler/Backend/SCompiledMethod.h | 4 +- runtime/cpp/Compiler/Binding/Binding.h | 5 +- runtime/cpp/Compiler/TreecodeEncoder.cpp | 88 ++++++------------- runtime/cpp/Compiler/TreecodeEncoder.h | 11 ++- .../cpp/Evaluator/SExpressionLinearizer.cpp | 7 +- runtime/cpp/Evaluator/TreecodeDecoder.h | 5 +- 13 files changed, 136 insertions(+), 99 deletions(-) create mode 100644 runtime/cpp/Bootstrap/BootstrappedKernel.cpp create mode 100644 runtime/cpp/Bootstrap/BootstrappedKernel.h diff --git a/runtime/cpp/Bootstrap/BootstrappedKernel.cpp b/runtime/cpp/Bootstrap/BootstrappedKernel.cpp new file mode 100644 index 00000000..2c9b9ee6 --- /dev/null +++ b/runtime/cpp/Bootstrap/BootstrappedKernel.cpp @@ -0,0 +1,47 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "BootstrappedKernel.h" +#include +#include + +namespace Egg { + +std::istringstream BootstrappedKernel::createDummyStream() { + // Create a minimal valid EGG_IS stream so ImageSegment constructor doesn't crash. + // We'll override all the header fields after construction. + ImageSegmentHeader dummyHeader; + memset(&dummyHeader, 0, sizeof(dummyHeader)); + + // Set signature + const char* sig = "EGG_IS\n"; + memcpy(dummyHeader.signature, sig, 8); + + // Set minimal size (just the header) + dummyHeader.size = sizeof(ImageSegmentHeader); + dummyHeader.reservedSize = sizeof(ImageSegmentHeader); + dummyHeader.baseAddress = 0; + dummyHeader.module = nullptr; + + std::string data(reinterpret_cast(&dummyHeader), sizeof(dummyHeader)); + return std::istringstream(data); +} + +BootstrappedKernel::BootstrappedKernel(uintptr_t base, uintptr_t size, uintptr_t objectsEnd) + : ImageSegment(nullptr) // bypass load - we pass nullptr to skip it +{ + // Override the header with actual bootstrap segment info + const char* sig = "EGG_IS\n"; + memcpy(header.signature, sig, 8); + header.baseAddress = base; + // Offset _currentBase so that spaceStart() (= _currentBase + sizeof(ImageSegmentHeader)) + // returns 'base', where bootstrap objects actually begin (no on-disk header in memory). + _currentBase = base - sizeof(ImageSegmentHeader); + header.size = objectsEnd - _currentBase; + header.reservedSize = size + sizeof(ImageSegmentHeader); + header.module = nullptr; +} + +} // namespace Egg diff --git a/runtime/cpp/Bootstrap/BootstrappedKernel.h b/runtime/cpp/Bootstrap/BootstrappedKernel.h new file mode 100644 index 00000000..424960f1 --- /dev/null +++ b/runtime/cpp/Bootstrap/BootstrappedKernel.h @@ -0,0 +1,33 @@ +/* + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _BOOTSTRAPPED_KERNEL_H_ +#define _BOOTSTRAPPED_KERNEL_H_ + +#include "../ImageSegment.h" +#include +#include + +namespace Egg { + +/** + * BootstrappedKernel wraps a GCSpace containing bootstrapped kernel objects + * and presents it as an ImageSegment for Runtime consumption. + */ +class BootstrappedKernel : public ImageSegment { +public: + BootstrappedKernel(uintptr_t base, uintptr_t size, uintptr_t objectsEnd); + + void addExport(const std::string& name, HeapObject* obj) { + _exports[name] = obj; + } + +private: + static std::istringstream createDummyStream(); +}; + +} // namespace Egg + +#endif // _BOOTSTRAPPED_KERNEL_H_ diff --git a/runtime/cpp/Compiler/AST/SBraceNode.cpp b/runtime/cpp/Compiler/AST/SBraceNode.cpp index 136dd6f6..765dd75c 100644 --- a/runtime/cpp/Compiler/AST/SBraceNode.cpp +++ b/runtime/cpp/Compiler/AST/SBraceNode.cpp @@ -40,7 +40,7 @@ SParseNode* SBraceNode::expanded() { auto new_ = _compiler->selectorNode(); new_->symbol_("new:"); auto argument = _compiler->numericSLiteralNode(); - argument->value_(std::to_string(n)); + argument->value_(n); auto array = _compiler->messageNode(); array->receiver_(receiver); array->selector_(new_); @@ -56,7 +56,7 @@ SParseNode* SBraceNode::expanded() { auto sel = _compiler->selectorNode(); sel->symbol_("at:put:"); auto idx = _compiler->numericSLiteralNode(); - idx->value_(std::to_string(i)); + idx->value_(i); std::vector args; args.push_back(idx); args.push_back(elem); diff --git a/runtime/cpp/Compiler/AST/SCascadeMessageNode.h b/runtime/cpp/Compiler/AST/SCascadeMessageNode.h index e4c7ef85..8539da83 100644 --- a/runtime/cpp/Compiler/AST/SCascadeMessageNode.h +++ b/runtime/cpp/Compiler/AST/SCascadeMessageNode.h @@ -7,6 +7,7 @@ #define _CASCADE_MESSAGE_NODE_H_ #include "SMessageNode.h" +#include "SCascadeNode.h" namespace Egg { @@ -25,7 +26,7 @@ class SCascadeMessageNode : public SMessageNode { virtual ~SCascadeMessageNode() {} SCascadeNode* cascade() const { return _cascade; } - void cascade_(SCascadeNode* c) { _cascade = c; } + void cascade_(SCascadeNode* c) { _cascade = c; receiver_(c->receiver()); } bool isCascadeMessage() const override { return true; } }; diff --git a/runtime/cpp/Compiler/AST/SLiteralNode.h b/runtime/cpp/Compiler/AST/SLiteralNode.h index c555e9ae..95d3bda0 100644 --- a/runtime/cpp/Compiler/AST/SLiteralNode.h +++ b/runtime/cpp/Compiler/AST/SLiteralNode.h @@ -33,10 +33,7 @@ class SLiteralNode : public SParseNode { // Backwards‑compatible string accessors egg::string value() const { return _litValue.printString(); } - void value_(const egg::string& v) { - // Legacy: if called with a plain string, store as String - _litValue = LiteralValue::fromString(v); - } + void value_(const LiteralValue& v) { _litValue = v; } void beSymbol_() { _litValue.tag = LiteralValue::Symbol; } bool hasSymbol() const { return _litValue.isSymbol(); } diff --git a/runtime/cpp/Compiler/AST/SMethodNode.cpp b/runtime/cpp/Compiler/AST/SMethodNode.cpp index b125be0d..4d6643fd 100644 --- a/runtime/cpp/Compiler/AST/SMethodNode.cpp +++ b/runtime/cpp/Compiler/AST/SMethodNode.cpp @@ -14,6 +14,7 @@ #include "SSelectorNode.h" #include "../Backend/SCompiledMethod.h" #include "../Backend/SCompiledBlock.h" +#include "../TreecodeEncoder.h" #include "../Binding/BlockScope.h" #include "../SSmalltalkCompiler.h" #include "../Binding/MethodScope.h" @@ -66,6 +67,11 @@ SCompiledMethod* SMethodNode::buildMethod() { block->method_(cm); } + // Encode treecodes (matching Smalltalk: encoder := TreecodeEncoder new method: cm) + TreecodeEncoder encoder; + encoder.method_(cm); + auto treecodes = encoder.encodeMethod(this); + cm->treecodes_(treecodes); return cm; } diff --git a/runtime/cpp/Compiler/AST/SNumberNode.h b/runtime/cpp/Compiler/AST/SNumberNode.h index 646450ab..e902b563 100644 --- a/runtime/cpp/Compiler/AST/SNumberNode.h +++ b/runtime/cpp/Compiler/AST/SNumberNode.h @@ -22,23 +22,6 @@ class SNumberNode : public SLiteralNode { void acceptVisitor_(SParseNodeVisitor* visitor) override; bool isSNumberNode() const { return true; } - - void negate_() { - const auto& lv = literalValue(); - if (lv.isInteger()) { - literalValue_(LiteralValue::fromInteger(-lv.asInteger())); - } else if (lv.isFloat()) { - literalValue_(LiteralValue::fromFloat(-lv.asFloat())); - } else { - // Legacy fallback - egg::string val = value(); - if (!val.empty() && val[0] != '-') { - value_("-" + val); - } else if (!val.empty() && val[0] == '-') { - value_(val.substr(1)); - } - } - } }; } // namespace Egg diff --git a/runtime/cpp/Compiler/Backend/SCompiledMethod.h b/runtime/cpp/Compiler/Backend/SCompiledMethod.h index db1641d9..23b157d0 100644 --- a/runtime/cpp/Compiler/Backend/SCompiledMethod.h +++ b/runtime/cpp/Compiler/Backend/SCompiledMethod.h @@ -103,11 +103,11 @@ class SCompiledMethod { const std::vector& literals() const { return _literals; } - int literalIndexOf(const LiteralValue& lit) const { + int indexOf(const LiteralValue& lit) const { for (size_t i = 0; i < _literals.size(); i++) { if (_literals[i] == lit) return static_cast(i + 1); // 1-based } - return -1; + return 0; } std::vector blocks() const; diff --git a/runtime/cpp/Compiler/Binding/Binding.h b/runtime/cpp/Compiler/Binding/Binding.h index 71c40907..975ec4b8 100644 --- a/runtime/cpp/Compiler/Binding/Binding.h +++ b/runtime/cpp/Compiler/Binding/Binding.h @@ -6,6 +6,7 @@ #define _BINDING_H_ #include #include +#include #include "../LiteralValue.h" namespace Egg { @@ -42,8 +43,8 @@ class Binding { } // Returns the literal value the binding contributes to the method's literal pool. - // Returns nullptr if the binding doesn't need a literal (e.g., local vars, self, nil). - virtual const LiteralValue* literal() const { return nullptr; } + // Returns nullopt if the binding doesn't need a literal (e.g., local vars, self, nil). + virtual std::optional literal() const { return std::nullopt; } virtual void encodeUsing_(TreecodeEncoder* encoder) = 0; // Pure virtual diff --git a/runtime/cpp/Compiler/TreecodeEncoder.cpp b/runtime/cpp/Compiler/TreecodeEncoder.cpp index ce0d9b50..66bdda7d 100644 --- a/runtime/cpp/Compiler/TreecodeEncoder.cpp +++ b/runtime/cpp/Compiler/TreecodeEncoder.cpp @@ -9,13 +9,19 @@ namespace Egg { -TreecodeEncoder::TreecodeEncoder() : method_(nullptr), script_(nullptr) { +TreecodeEncoder::TreecodeEncoder() : _method(nullptr), script_(nullptr) { +} + +void TreecodeEncoder::method_(SCompiledMethod* method) { + _method = method; +} + +SCompiledMethod* TreecodeEncoder::method() const { + return _method; } std::vector TreecodeEncoder::encodeMethod(SMethodNode* method) { stream_.clear(); - literals_.clear(); - visitMethod_(method); return stream_; } @@ -104,20 +110,19 @@ void TreecodeEncoder::visitIdentifier_(SIdentifierNode* node) { } else if (name == "false") { encodeFalse(); } else { - // Unknown identifier — treat as dynamic variable (resolved at runtime) encodeDynamicVar_(name); } } } void TreecodeEncoder::visitLiteral_(SLiteralNode* node) { - nextTypePut(LiteralId); - const auto& lv = node->literalValue(); - int index = findLiteralIndex_ifAbsent_(lv, [&](){ - return addLiteral(lv); - }); + int index = _method->indexOf(lv); + nextTypePut(LiteralId); nextIntegerPut(index); + if (index == 0) { + nextIntegerPut(lv.intVal); + } } void TreecodeEncoder::visitAssignment_(SAssignmentNode* node) { @@ -228,7 +233,7 @@ void TreecodeEncoder::nextIntegerPut(int64_t value) { void TreecodeEncoder::nextBigIntegerPut(int64_t value) { nextPut(0x80); - for (int i = 0; i < 8; i++) { + for (int i = 7; i >= 0; i--) { nextPut((value >> (i * 8)) & 0xFF); } } @@ -239,18 +244,15 @@ void TreecodeEncoder::nextBooleanPut(bool value) { void TreecodeEncoder::nextSymbolPut(const egg::string& symbol) { auto symLv = LiteralValue::fromSymbol(symbol); - int index = findLiteralIndex_ifAbsent_(symLv, [&](){ - return addLiteral(symLv); - }); + int index = _method->indexOf(symLv); + ASSERT(index != 0); nextIntegerPut(index); } void TreecodeEncoder::nextLiteralPut(const egg::string& literal) { auto litLv = LiteralValue::fromString(literal); - int index = literalIndexOf(litLv); - if (index < 0) { - index = addLiteral(litLv); - } + int index = _method->indexOf(litLv); + ASSERT(index != 0); nextIntegerPut(index); } @@ -258,20 +260,6 @@ void TreecodeEncoder::nextTypePut(uint8_t typeId) { nextPut(typeId); } -int TreecodeEncoder::literalIndexOf(const LiteralValue& literal) { - for (size_t i = 0; i < literals_.size(); i++) { - if (literals_[i] == literal) { - return i + 1; // 1-based - } - } - return -1; -} - -int TreecodeEncoder::addLiteral(const LiteralValue& literal) { - literals_.push_back(literal); - return literals_.size(); // 1-based -} - void TreecodeEncoder::visitSelector_(SSelectorNode* node) { nextSymbolPut(node->symbol()); } @@ -423,42 +411,16 @@ std::vector TreecodeEncoder::encodeClosureElements_(SBlockNode* node) { } int TreecodeEncoder::compiledBlockIndexOf_(SBlockNode* node) { - // Search the literals for a Block literal with matching id, like - // Smalltalk's compiledBlockIndexOf: which does findFirst: on the method's literals + // Search the method's literals for a Block literal with matching id + // (matches Smalltalk's compiledBlockIndexOf: which searches method's pool) int blockId = node->index(); - for (size_t i = 0; i < literals_.size(); i++) { - if (literals_[i].isBlock() && literals_[i].asBlock().id == blockId) { + const auto& literals = _method->literals(); + for (size_t i = 0; i < literals.size(); i++) { + if (literals[i].isBlock() && literals[i].asBlock().id == blockId) { return i + 1; // 1-based index } } - - // Block not found in literals — add it now - int argCount = node->arguments().size(); - int tempCount = 0; - int envCount = 0; - bool capturesSelf = false; - bool capturesHome = false; - - auto scope = node->scope(); - if (scope) { - tempCount = scope->stackSize(); - envCount = scope->environmentSize(); - capturesSelf = scope->capturesSelf(); - auto blockScope = dynamic_cast(scope); - if (blockScope) { - capturesHome = blockScope->capturesHome_(); - } - } - - auto blockLit = LiteralValue::fromBlock(blockId, argCount, tempCount, - envCount, capturesSelf, capturesHome); - return addLiteral(blockLit); -} - -int TreecodeEncoder::findLiteralIndex_ifAbsent_(const LiteralValue& literal, std::function block) { - int index = literalIndexOf(literal); - if (index >= 0) return index; - return block(); + throw std::runtime_error("TreecodeEncoder: block not found in method pool"); } } // namespace Egg diff --git a/runtime/cpp/Compiler/TreecodeEncoder.h b/runtime/cpp/Compiler/TreecodeEncoder.h index 1e48b211..3aea2153 100644 --- a/runtime/cpp/Compiler/TreecodeEncoder.h +++ b/runtime/cpp/Compiler/TreecodeEncoder.h @@ -64,7 +64,9 @@ class TreecodeEncoder : public SParseNodeVisitor { void visitComment_(SCommentNode* node) override; const std::vector& treecode() const { return stream_; } - const std::vector& literals() const { return literals_; } + + void method_(SCompiledMethod* method); + SCompiledMethod* method() const; void encodeNil(); void encodeTrue(); @@ -80,8 +82,7 @@ class TreecodeEncoder : public SParseNodeVisitor { private: std::vector stream_; // Output stream for treecode bytes - std::vector literals_; // Typed literal pool for the method - SCompiledMethod* method_; // Current method being compiled + SCompiledMethod* _method; // The method whose literal pool we index into SScriptNode* script_; // Current script node (for context tracking) void nextPut(uint8_t byte); @@ -101,9 +102,7 @@ class TreecodeEncoder : public SParseNodeVisitor { void visitStatements(const std::vector& statements); void visitScript_(SScriptNode* node); - int findLiteralIndex_ifAbsent_(const LiteralValue& literal, std::function block); - int literalIndexOf(const LiteralValue& literal); - int addLiteral(const LiteralValue& literal); + }; } // namespace Egg diff --git a/runtime/cpp/Evaluator/SExpressionLinearizer.cpp b/runtime/cpp/Evaluator/SExpressionLinearizer.cpp index 76bb6dfb..3c64a770 100644 --- a/runtime/cpp/Evaluator/SExpressionLinearizer.cpp +++ b/runtime/cpp/Evaluator/SExpressionLinearizer.cpp @@ -443,7 +443,12 @@ void SExpressionLinearizer::visitBlock(SBlock *anSBlock) { auto prevInBlock = this->_inBlock; auto prevOperations = this->_operations; auto prevStackTop = _stackTop; - this->_stackTop = _runtime->blockTempCount_(anSBlock->compiledCode()); + if (anSBlock->isInlined()) { + // Inlined blocks have no compiled code object; their temps are part of + // the enclosing script's frame, so we keep the current stack top. + } else { + this->_stackTop = _runtime->blockTempCount_(anSBlock->compiledCode()); + } this->_inBlock = true; this->_operations = newPlatformCode(); auto statements = anSBlock->statements(); diff --git a/runtime/cpp/Evaluator/TreecodeDecoder.h b/runtime/cpp/Evaluator/TreecodeDecoder.h index c32fab2a..11ae1b44 100644 --- a/runtime/cpp/Evaluator/TreecodeDecoder.h +++ b/runtime/cpp/Evaluator/TreecodeDecoder.h @@ -80,7 +80,10 @@ class TreecodeDecoder { case AstNodeTypes::IdentifierId: return this->decodeIdentifier(); case AstNodeTypes::MessageId: return this->decodeMessage(); case AstNodeTypes::ReturnId: return this->decodeReturn(); - default: ASSERT(false); return nullptr; + default: + std::cerr << "ERROR: Unknown AST node type: " << (int)id << std::endl; + ASSERT(false); + return nullptr; } } From 4bb85bfb418223bebc00ee06918268046a2891ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Wed, 11 Mar 2026 00:23:07 -0300 Subject: [PATCH 06/15] Refactor binding classes and improve expression handling in SemanticVisitor --- .../Compiler/Binding/PseudoVariableBindings.h | 5 +++- runtime/cpp/Compiler/LiteralValue.h | 3 +++ runtime/cpp/Compiler/SemanticVisitor.cpp | 25 ++++--------------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/runtime/cpp/Compiler/Binding/PseudoVariableBindings.h b/runtime/cpp/Compiler/Binding/PseudoVariableBindings.h index 0c8f437b..c3f65bd5 100644 --- a/runtime/cpp/Compiler/Binding/PseudoVariableBindings.h +++ b/runtime/cpp/Compiler/Binding/PseudoVariableBindings.h @@ -78,7 +78,8 @@ class SuperBinding : public Binding { class DynamicBinding : public Binding { public: - DynamicBinding(const egg::string& name) : Binding(Kind::Global, name, 0) {} + DynamicBinding(const egg::string& name) + : Binding(Kind::Global, name, 0) {} static DynamicBinding* named_(const egg::string& name); @@ -88,6 +89,8 @@ class DynamicBinding : public Binding { void encodeUsing_(TreecodeEncoder* encoder) override; + std::optional literal() const override { return LiteralValue::fromSymbol(name()); } + Binding* copy_() override { return new DynamicBinding(name()); } diff --git a/runtime/cpp/Compiler/LiteralValue.h b/runtime/cpp/Compiler/LiteralValue.h index d1413718..178eefe8 100644 --- a/runtime/cpp/Compiler/LiteralValue.h +++ b/runtime/cpp/Compiler/LiteralValue.h @@ -64,6 +64,9 @@ struct LiteralValue { std::vector bytes; LiteralValue() : tag(None), intVal(0), blockInfo{0, 0, 0, 0, false, false} {} + LiteralValue(int64_t v) : tag(Integer), intVal(v), blockInfo{0, 0, 0, 0, false, false} {} + LiteralValue(int v) : tag(Integer), intVal(v), blockInfo{0, 0, 0, 0, false, false} {} + LiteralValue(double v) : tag(Float), floatVal(v), blockInfo{0, 0, 0, 0, false, false} {} // ----- factory helpers ----- diff --git a/runtime/cpp/Compiler/SemanticVisitor.cpp b/runtime/cpp/Compiler/SemanticVisitor.cpp index fa062f1e..98932395 100644 --- a/runtime/cpp/Compiler/SemanticVisitor.cpp +++ b/runtime/cpp/Compiler/SemanticVisitor.cpp @@ -101,10 +101,7 @@ void SemanticVisitor::analyzeScript_while_(SScriptNode* aScriptNode, std::functi void SemanticVisitor::visitAssignment_(SAssignmentNode* node) { analyzeAssignment_(node); - SParseNode* expression = node->expression(); - if (expression) { - expression->acceptVisitor_(this); - } + node->expression()->acceptVisitor_(this); } void SemanticVisitor::visitBlock_(SBlockNode* node) { @@ -118,18 +115,12 @@ void SemanticVisitor::visitBlock_(SBlockNode* node) { void SemanticVisitor::visitBrace_(SBraceNode* node) { if (!node->isLiteral()) { - SParseNode* asMessage = node->asSMessageNode(); - if (asMessage && asMessage->isMessage()) { - asMessage->acceptVisitor_(this); - } + node->asSMessageNode()->acceptVisitor_(this); } } void SemanticVisitor::visitCascade_(SCascadeNode* node) { - SParseNode* receiver = node->receiver(); - if (receiver) { - receiver->acceptVisitor_(this); - } + node->receiver()->acceptVisitor_(this); auto& messages = node->messages(); for (auto msg : messages) { @@ -144,10 +135,7 @@ void SemanticVisitor::visitIdentifier_(SIdentifierNode* node) { void SemanticVisitor::visitMessage_(SMessageNode* node) { analyzeMessage_(node); - SParseNode* receiver = node->receiver(); - if (receiver) { - receiver->acceptVisitor_(this); - } + node->receiver()->acceptVisitor_(this); auto& arguments = node->arguments(); for (auto arg : arguments) { @@ -169,10 +157,7 @@ void SemanticVisitor::visitMethod_(SMethodNode* node) { } void SemanticVisitor::visitReturn_(SReturnNode* node) { - SParseNode* expression = node->expression(); - if (expression) { - expression->acceptVisitor_(this); - } + node->expression()->acceptVisitor_(this); analyzeReturn_(node); } From 12d197b13a28d9b86421b4d48e245477dc7ac14c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Wed, 18 Mar 2026 00:08:45 -0300 Subject: [PATCH 07/15] Extract Loader and SymbolProvider from Bootstrapper; refactor GC root scanning Separate module-loading responsibilities from bootstrapping: - Extract Loader class (Loader.h/cpp) to handle module resolution and loading from both .ems files and source directories - Move Bootstrapper to Bootstrap/ subdirectory, now focused solely on kernel bootstrapping from source - Delete old monolithic Bootstrapper.h from runtime/cpp root Introduce SymbolProvider abstraction (SymbolProvider.h/cpp): - BootstrapSymbolProvider: used during kernel bootstrap - DynamicSymbolProvider: uses the live symbol table at runtime - Runtime::switchToDynamicSymbolProvider_() for the transition - Remove inline symbolTableAt_() and knownSymbols map from Runtime Refactor GC root scanning in G1GC: - Extract resolveObject_() helper to deduplicate evacuation/marking logic shared between scanBehavior() and scanSlot() - Add scanRoot_(Object**) virtual method for properly scanning root pointers (stack frames, GCedRefs) instead of casting them to fake HeapObjects and using scan_from_to_ Other changes: - Add _error: undermessage and DictionaryNew primitive - Guard primitiveNewBytes/primitiveNewSized against non-SmallInteger args - Add Runtime::send_to_, send_to_with_ convenience methods - Refactor Launcher to bootstrap from source via Loader and use message sends to load/run modules - Remove dead code and debug leftovers from Runtime lookup cache - Fix unused variable in SSemanticVisitor>>visitAssignment: --- modules/Compiler/SSemanticVisitor.st | 5 +- runtime/cpp/Allocator/G1GC.cpp | 136 ++- runtime/cpp/Allocator/G1GC.h | 2 + runtime/cpp/Allocator/GarbageCollector.cpp | 7 +- runtime/cpp/Allocator/GarbageCollector.h | 1 + runtime/cpp/Bootstrap/BootstrappedKernel.cpp | 2 +- runtime/cpp/Bootstrap/BootstrappedKernel.h | 2 +- runtime/cpp/Bootstrap/Bootstrapper.cpp | 855 +++++++++++++++++++ runtime/cpp/Bootstrap/Bootstrapper.h | 142 +++ runtime/cpp/Bootstrapper.h | 179 ---- runtime/cpp/Evaluator/EvaluationContext.cpp | 13 +- runtime/cpp/Evaluator/EvaluationContext.h | 12 +- runtime/cpp/Evaluator/Evaluator.cpp | 42 +- runtime/cpp/Evaluator/Evaluator.h | 2 + runtime/cpp/Evaluator/Runtime.cpp | 101 ++- runtime/cpp/Launcher.cpp | 49 +- runtime/cpp/Loader.cpp | 230 +++++ runtime/cpp/Loader.h | 61 ++ runtime/cpp/SymbolProvider.cpp | 67 ++ runtime/cpp/SymbolProvider.h | 51 ++ 20 files changed, 1621 insertions(+), 338 deletions(-) create mode 100644 runtime/cpp/Bootstrap/Bootstrapper.cpp create mode 100644 runtime/cpp/Bootstrap/Bootstrapper.h delete mode 100644 runtime/cpp/Bootstrapper.h create mode 100644 runtime/cpp/Loader.cpp create mode 100644 runtime/cpp/Loader.h create mode 100644 runtime/cpp/SymbolProvider.cpp create mode 100644 runtime/cpp/SymbolProvider.h diff --git a/modules/Compiler/SSemanticVisitor.st b/modules/Compiler/SSemanticVisitor.st index 595c41de..1d62fbb3 100644 --- a/modules/Compiler/SSemanticVisitor.st +++ b/modules/Compiler/SSemanticVisitor.st @@ -66,11 +66,8 @@ SSemanticVisitor >> initialize [ { #category : #visiting } SSemanticVisitor >> visitAssignment: anAssignmentNode [ - | c | self analyzeAssignment: anAssignmentNode. - anAssignmentNode expression acceptVisitor: self. - c := anAssignmentNode compiler. - + anAssignmentNode expression acceptVisitor: self ] { #category : #visiting } diff --git a/runtime/cpp/Allocator/G1GC.cpp b/runtime/cpp/Allocator/G1GC.cpp index 73977128..65738ca4 100644 --- a/runtime/cpp/Allocator/G1GC.cpp +++ b/runtime/cpp/Allocator/G1GC.cpp @@ -192,7 +192,7 @@ void G1GC::followClosure() void G1GC::followGCedRefs() { _runtime->gcedRefsDo_([this](GCedRef *ref) { - this->queue_from_to_((HeapObject*)ref->getRaw(), 1, 1); + this->scanRoot_(ref->getRaw()); }); } @@ -268,101 +268,93 @@ void G1GC::scan_from_to_(HeapObject *anObject, uintptr_t start, uintptr_t end) _stack.push_back(end); } +HeapObject* G1GC::resolveObject_(HeapObject *obj) +{ + auto evacuate = this->hasToEvacuate_(obj); + if (obj->hasBeenSeen()) + return evacuate ? this->copyOf_(obj) : obj; + + obj->beSeen(); + + if (evacuate) + obj = this->evacuate_(obj); + else + this->updateRegionOccupancy_(obj); + + if (obj->isSpecial()) + this->rememberSpecial_(obj); + + return obj; +} + void G1GC::scanBehavior() { - auto slot = _scanned->behavior(); -// printf("scanning behavior of %#" PRIxPTR " (%s)", (uintptr_t)_scanned, _scanned->printString().c_str()); -// fflush(stdout); -// printf(", which is %#" PRIxPTR "( %s)\n", (uintptr_t)slot, slot->printString().c_str()); - if (((Object*)slot)->isSmallInteger()) - { - _index = _index + 1; - return; - } - auto evacuate = this->hasToEvacuate_(slot); - if (slot->hasBeenSeen()) - { - if (evacuate) - { - slot = this->copyOf_(slot); - _scanned->behavior(slot); - } + auto slot = _scanned->behavior(); + if (((Object*)slot)->isSmallInteger()) + { _index = _index + 1; - return; - } - slot->beSeen(); - if (evacuate) + return; + } + + bool wasSeen = slot->hasBeenSeen(); + slot = this->resolveObject_(slot); + _scanned->behavior(slot); + + if (wasSeen) { - slot = this->evacuate_(slot); - _scanned->behavior(slot); - } - else - { - this->updateRegionOccupancy_(slot); - } + _index = _index + 1; + return; + } - if (slot->isSpecial()) - this->rememberSpecial_(slot); - if (_index < _limit) - this->queueCurrent(); + this->queueCurrent(); _index = 0; _limit = slot->strongPointersSize(); _scanned = slot; } -void G1GC::scanSlot() +void G1GC::scanRoot_(Object** root) { - // scanned can be a heap object or a chunk of stack frame, we cannot use slotAt_ - auto slot = ((Object**)_scanned)[_index-1]; - auto stack = debugRuntime->_evaluator->context()->stack(); -// if ((uintptr_t)_scanned < (uintptr_t)&stack[0] || (uintptr_t)_scanned > (uintptr_t)&stack[0xFFFF]) -// printf("scanning slot %d of %#" PRIxPTR " (%s)", _index, (uintptr_t)_scanned, _scanned->printString().c_str()); -// else -// printf("scanning stack slot of frame %#" PRIxPTR " at %d" , (uintptr_t)_scanned, _index); - -// fflush(stdout); -// printf(" which is %#" PRIxPTR " (%s)\n", (uintptr_t)slot, slot->printString().c_str()); + auto slot = *root; if (slot->isSmallInteger()) - { - _index = _index + 1; - return; - } + return; - auto hslot = slot->asHeapObject(); + auto hslot = slot->asHeapObject(); + bool wasSeen = hslot->hasBeenSeen(); + hslot = this->resolveObject_(hslot); + *root = (Object*)hslot; - auto evacuate = this->hasToEvacuate_(hslot); - if (hslot->hasBeenSeen()) + if (!wasSeen) + this->scan_from_to_(hslot, 0, hslot->strongPointersSize()); +} + +void G1GC::scanSlot() +{ + auto &slotRef = _scanned->slotAt_(_index); + auto slot = slotRef; + + if (slot == nullptr || slot->isSmallInteger()) { - if (evacuate) - { - hslot = this->copyOf_(hslot); - ((Object**)_scanned)[_index-1] = (Object*)hslot; - } _index = _index + 1; - return; - } + return; + } - hslot->beSeen(); + auto hslot = slot->asHeapObject(); + bool wasSeen = hslot->hasBeenSeen(); + hslot = this->resolveObject_(hslot); + slotRef = (Object*)hslot; - if (evacuate) + if (wasSeen) { - hslot = this->evacuate_(hslot); - ((Object**)_scanned)[_index-1] = (Object*)hslot; - } - else - { - this->updateRegionOccupancy_(hslot); - } + _index = _index + 1; + return; + } - if (hslot->isSpecial()) - this->rememberSpecial_(hslot); - if (_index < _limit) - this->queueCurrent(); - + this->queueCurrent(); + _index = 0; _limit = hslot->strongPointersSize(); _scanned = hslot; diff --git a/runtime/cpp/Allocator/G1GC.h b/runtime/cpp/Allocator/G1GC.h index b23d0342..8a18299e 100644 --- a/runtime/cpp/Allocator/G1GC.h +++ b/runtime/cpp/Allocator/G1GC.h @@ -65,7 +65,9 @@ class G1GC : public GarbageCollector { //void purgeRememberedSet(); // RE-ENABLE AFTER PLUGGING BACK GENGC void queue_from_to_(HeapObject *anObject, uintptr_t start, uintptr_t end); void queueCurrent(); + HeapObject* resolveObject_(HeapObject *obj); void scan_from_to_(HeapObject *anObject, uintptr_t start, uintptr_t end); + void scanRoot_(Object** root); void scanBehavior(); void scanSlot(); void scanThreadLocalStorage_(HeapObject *thread); diff --git a/runtime/cpp/Allocator/GarbageCollector.cpp b/runtime/cpp/Allocator/GarbageCollector.cpp index 54f16e9f..e1f018b0 100644 --- a/runtime/cpp/Allocator/GarbageCollector.cpp +++ b/runtime/cpp/Allocator/GarbageCollector.cpp @@ -172,9 +172,8 @@ void GarbageCollector::scanNativeStackFrame_sized_(uintptr_t *framePointer, uint } void GarbageCollector::scanStackFrameObjects_sized_(uintptr_t *framePointer, uintptr_t size) { - //for (uintptr_t i = 0; i < size; i++) - // printf("adding %s to queue\n", ((Object*)framePointer[i])->printString().c_str()); - this->scan_from_to_((HeapObject*)framePointer, 1, size); + for (uintptr_t i = 0; i < size; i++) + this->scanRoot_((Object**)&framePointer[i]); } void GarbageCollector::scanSpecialSlots_(HeapObject *special) @@ -219,7 +218,7 @@ void GarbageCollector::scanFirstStackChunk_(HeapObject *aProcessVMStack) { void GarbageCollector::scanPointer_(Object** pointer) { - this->scan_from_to_((HeapObject*)pointer, 1, 1); + this->scanRoot_(pointer); } /* only for use until we have context switches */ diff --git a/runtime/cpp/Allocator/GarbageCollector.h b/runtime/cpp/Allocator/GarbageCollector.h index d911feb4..2400c6dc 100644 --- a/runtime/cpp/Allocator/GarbageCollector.h +++ b/runtime/cpp/Allocator/GarbageCollector.h @@ -51,6 +51,7 @@ class GarbageCollector { void rescueEphemeron_(HeapObject *ephemeron); bool rescueUnreachableEphemerons(); virtual void scan_from_to_(HeapObject *current, uintptr_t start, uintptr_t limit) = 0; + virtual void scanRoot_(Object** root) = 0; void scanNativeStackFrame_sized_(uintptr_t *framePointer, uintptr_t size); void scanStackFrameObjects_sized_(uintptr_t *framePointer, uintptr_t size); void scanSpecialSlots_(HeapObject *special); diff --git a/runtime/cpp/Bootstrap/BootstrappedKernel.cpp b/runtime/cpp/Bootstrap/BootstrappedKernel.cpp index 2c9b9ee6..61a111ed 100644 --- a/runtime/cpp/Bootstrap/BootstrappedKernel.cpp +++ b/runtime/cpp/Bootstrap/BootstrappedKernel.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Bootstrap/BootstrappedKernel.h b/runtime/cpp/Bootstrap/BootstrappedKernel.h index 424960f1..1893f942 100644 --- a/runtime/cpp/Bootstrap/BootstrappedKernel.h +++ b/runtime/cpp/Bootstrap/BootstrappedKernel.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Bootstrap/Bootstrapper.cpp b/runtime/cpp/Bootstrap/Bootstrapper.cpp new file mode 100644 index 00000000..428b7569 --- /dev/null +++ b/runtime/cpp/Bootstrap/Bootstrapper.cpp @@ -0,0 +1,855 @@ +/* + Copyright (c) 2025-2026, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "Bootstrapper.h" +#include "../Loader.h" +#include "BootstrappedKernel.h" +#include "TonelReader.h" +#include "../Allocator/Memory.h" +#include "../Allocator/GCHeap.h" +#include "../Compiler/SSmalltalkCompiler.h" +#include "../Compiler/LiteralValue.h" +#include "../Compiler/Backend/SCompiledMethod.h" +#include "../Compiler/CompilationResult.h" +#include "../KnownConstants.h" +#include "../GCedRef.h" +#include +#include +#include +#include +#include + +namespace Egg { + +Bootstrapper::Bootstrapper(const std::string& kernelPath, Loader* loader) + : _loader(loader), _symbolProvider(new BootstrapSymbolProvider(this)), + _kernelPath(kernelPath), + _space(nullptr), + _nilObj(nullptr), _trueObj(nullptr), _falseObj(nullptr), + _kernelModule(nullptr), _compiledMethodClass(nullptr), + _undefinedObjectClass(nullptr), _trueClass(nullptr), _falseClass(nullptr), + _classClass(nullptr), _metaclassClass(nullptr), + _symbolClass(nullptr), _wideSymbolClass(nullptr), + _stringClass(nullptr), _wideStringClass(nullptr), + _arrayClass(nullptr), _methodDictionaryClass(nullptr) { + _compiler = std::make_unique(); +} + +Bootstrapper::~Bootstrapper() { +} + +// ========================================================================= +// Main bootstrap +// ========================================================================= + +Runtime* Bootstrapper::bootstrap() { + // Allocate GC space for kernel objects (128MB) + uintptr_t segmentSize = 128 * 1024 * 1024; + _space = new GCSpace(segmentSize); + _space->commitMemory_(segmentSize); + _space->increaseSoftLimit_(segmentSize); + _loader->_runtime = nullptr; + + loadKernelSpecs(); + createInitialObjects(); + instantiateMetaobjects(); + initializeMetaobjects(); + createKernelNamespace(); + loadKernelMethods(); + createRuntimeWithBootstrappedKernel(); + + return _loader->_runtime; +} + +// ========================================================================= +// Phase 1: Parse class definitions +// ========================================================================= + +void Bootstrapper::createInitialObjects() { + _nilObj = allocateSlots_(0); + _trueObj = allocateSlots_(0); + _falseObj = allocateSlots_(0); +} + +void Bootstrapper::loadKernelSpecs() { + namespace fs = std::filesystem; + TonelReader reader; + + for (const auto& entry : fs::directory_iterator(_kernelPath)) { + if (entry.path().extension() == ".st") { + std::string filename = entry.path().filename().string(); + std::string className = filename.substr(0, filename.length() - 3); + + if (className == "package") continue; + + std::string source = readSourceFile_(className); + ClassSpec* spec = reader.parseFile(source); + _moduleSpec.addClass(spec); + } + } + + // Load VM extension methods from Kernel/VM/ directory + auto vmPath = _kernelPath + "/VM"; + if (fs::exists(vmPath) && fs::is_directory(vmPath)) { + for (const auto& entry : fs::directory_iterator(vmPath)) { + if (entry.path().extension() == ".st") { + std::string filename = entry.path().filename().string(); + std::string className = filename.substr(0, filename.length() - 3); + + if (className == "package") continue; + + std::ifstream file(entry.path()); + if (!file.is_open()) continue; + std::string source((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + ClassSpec* extensionSpec = reader.parseFile(source); + ClassSpec* existingSpec = _moduleSpec.resolveClass(className); + if (!existingSpec) { + std::cerr << " Warning: VM extension for unknown class " << className << std::endl; + delete extensionSpec->metaclass(); + delete extensionSpec; + continue; + } + + for (const auto& m : extensionSpec->methods()) + existingSpec->addMethod(m); + for (const auto& m : extensionSpec->metaclass()->methods()) + existingSpec->metaclass()->addMethod(m); + + delete extensionSpec->metaclass(); + delete extensionSpec; + } + } + } +} + +std::string Bootstrapper::readSourceFile_(const std::string& className) { + std::string filename = _kernelPath + "/" + className + ".st"; + std::ifstream file(filename); + + if (!file.is_open()) { + throw std::runtime_error("Cannot open file: " + filename); + } + + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); +} + +// ========================================================================= +// Phase 3 & 4: Metaobject creation and initialization +// ========================================================================= + +void Bootstrapper::instantiateMetaobjects() { + for (const auto& [name, spec] : _moduleSpec.classes()) { + instantiateMetaobjectsOf_(name); + } +} + +void Bootstrapper::initializeMetaobjects() { + // Cache pointers to frequently-used classes + _metaclassClass = _classes["Metaclass"]; + _classClass = _classes["Class"]; + _undefinedObjectClass = _classes["UndefinedObject"]; + _trueClass = _classes["True"]; + _falseClass = _classes["False"]; + _symbolClass = _classes["Symbol"]; + _wideSymbolClass = _classes["WideSymbol"]; + _stringClass = _classes["String"]; + _wideStringClass = _classes["WideString"]; + _arrayClass = _classes["Array"]; + _methodDictionaryClass = _classes["MethodDictionary"]; + _compiledMethodClass = _classes["CompiledMethod"]; + + for (const auto& [name, spec] : _moduleSpec.classes()) { + initializeMetaobjectsOf_(name); + } + + // Set behaviors on pre-existing objects + _nilObj->behavior(_behaviors["UndefinedObject"]); + _trueObj->behavior(_behaviors["True"]); + _falseObj->behavior(_behaviors["False"]); +} + +uint32_t Bootstrapper::instSizeOf_(const Egg::string& className) { + ClassSpec* spec = _moduleSpec.resolveClass(className); + if (spec) { + if (spec->superclass()) + return spec->instSize(); + uint32_t count = spec->instVarNames().size(); + auto supername = spec->supername(); + if (!supername.empty() && supername != U"nil") { + auto it = _classes.find(supername); + if (it != _classes.end()) { + auto format = it->second->slot(Offsets::SpeciesFormat)->asSmallInteger()->asNative(); + count += format & 0x7F; + } + } + return count; + } + auto it = _classes.find(className); + if (it != _classes.end()) { + auto formatObj = it->second->slot(Offsets::SpeciesFormat); + return formatObj->asSmallInteger()->asNative() & 0x7F; + } + error(("instSizeOf_: class not found: " + std::string(className.begin(), className.end())).c_str()); + return 0; +} + +void Bootstrapper::instantiateMetaobjectsOf_(const Egg::string& className) { + uint32_t classSlots = instSizeOf_("Class"); + uint32_t metaclassSlots = instSizeOf_("Metaclass"); + uint32_t behaviorSlots = instSizeOf_("Behavior"); + + ClassSpec* spec = _moduleSpec.resolveClass(className); + uint32_t classInstVarCount = spec->metaclass()->instVarNames().size(); + + HeapObject* metaclass = allocateSlots_(metaclassSlots); + metaclass->beNamed(); + for (uint32_t i = 0; i < metaclassSlots; i++) + metaclass->untypedSlot(i) = (Object*)_nilObj; + + HeapObject* cls = allocateSlots_(classSlots + classInstVarCount); + cls->beNamed(); + for (uint32_t i = 0; i < classSlots + classInstVarCount; i++) + cls->untypedSlot(i) = (Object*)_nilObj; + + HeapObject* behavior = allocateSlots_(behaviorSlots); + for (uint32_t i = 0; i < behaviorSlots; i++) + behavior->untypedSlot(i) = (Object*)_nilObj; + + HeapObject* metaBehavior = allocateSlots_(behaviorSlots); + for (uint32_t i = 0; i < behaviorSlots; i++) + metaBehavior->untypedSlot(i) = (Object*)_nilObj; + + _classes[className] = cls; + _metaclasses[className] = metaclass; + _behaviors[className] = behavior; + _metaBehaviors[className] = metaBehavior; +} + +void Bootstrapper::initializeMetaobjectsOf_(const Egg::string& className) { + ClassSpec* spec = _moduleSpec.resolveClass(className); + + HeapObject* cls = _classes[className]; + HeapObject* metaclass = _metaclasses[className]; + HeapObject* behavior = _behaviors[className]; + HeapObject* metaBehavior = _metaBehaviors[className]; + HeapObject* behaviorClassBehavior = _behaviors["Behavior"]; + + // --- Link class header -> metaclass's instance behavior --- + cls->behavior(metaBehavior); + + // --- Link metaclass header -> Metaclass's instance behavior --- + metaclass->behavior(_behaviors["Metaclass"]); + + // --- Metaclass slots --- + metaclass->slot(Offsets::MetaclassClass) = (Object*)cls; + metaclass->slot(Offsets::SpeciesInstanceBehavior) = (Object*)metaBehavior; + + uint32_t classInstVarCount = spec->metaclass()->instVarNames().size(); + uint32_t metaclassInstSize = instSizeOf_("Class") + classInstVarCount; + uint32_t metaFormat = (metaclassInstSize & 0x7F) | 0x4000; + metaclass->slot(Offsets::SpeciesFormat) = newSmallInteger_(metaFormat); + + // --- Class slots --- + uint32_t format = spec->instSize() & 0x7F; + if (spec->isVariable()) + format |= 0x2000; + if (spec->isPointers()) + format |= 0x4000; + cls->slot(Offsets::SpeciesFormat) = newSmallInteger_(format); + cls->slot(Offsets::SpeciesInstanceBehavior) = (Object*)behavior; + cls->slot(Offsets::ClassName) = internSymbol_(spec->name()); + + // Create instance variables array + const auto& ivarNames = spec->instVarNames(); + if (!ivarNames.empty()) { + HeapObject* ivars = newArray_(ivarNames.size()); + for (size_t i = 0; i < ivarNames.size(); i++) { + ivars->untypedSlot(i) = internSymbol_(ivarNames[i]); + } + cls->slot(Offsets::SpeciesInstanceVariables) = (Object*)ivars; + } + + // Set metaclass instance variables + const auto& classInstVarNames = spec->metaclass()->instVarNames(); + if (!classInstVarNames.empty()) { + HeapObject* metaIvars = newArray_(classInstVarNames.size()); + for (size_t i = 0; i < classInstVarNames.size(); i++) { + metaIvars->untypedSlot(i) = internSymbol_(classInstVarNames[i]); + } + metaclass->slot(Offsets::SpeciesInstanceVariables) = (Object*)metaIvars; + } + + // --- Instance behavior slots --- + behavior->behavior(behaviorClassBehavior); + behavior->slot(Offsets::BehaviorClass) = (Object*)cls; + behavior->slot(Offsets::BehaviorMethodDictionary) = (Object*)newMethodArray(); + + // --- Metaclass instance behavior slots --- + metaBehavior->behavior(behaviorClassBehavior); + metaBehavior->slot(Offsets::BehaviorClass) = (Object*)metaclass; + metaBehavior->slot(Offsets::BehaviorMethodDictionary) = (Object*)newMethodArray(); + + // --- Superclass and behavior chain linking --- + const auto& supername = spec->supername(); + if (!supername.empty() && supername != "nil") { + auto superIt = _classes.find(supername); + if (superIt != _classes.end()) { + cls->slot(Offsets::SpeciesSuperclass) = (Object*)superIt->second; + _metaclasses[className]->slot(Offsets::SpeciesSuperclass) = (Object*)_metaclasses[supername]; + behavior->slot(Offsets::BehaviorNext) = (Object*)_behaviors[supername]; + metaBehavior->slot(Offsets::BehaviorNext) = (Object*)_metaBehaviors[supername]; + } else { + std::cerr << " Warning: Superclass " << supername + << " not found for " << className << std::endl; + } + } else { + auto classIt = _behaviors.find("Class"); + if (classIt != _behaviors.end()) { + _metaclasses[className]->slot(Offsets::SpeciesSuperclass) = (Object*)_classes["Class"]; + metaBehavior->slot(Offsets::BehaviorNext) = (Object*)classIt->second; + } + } +} + +// ========================================================================= +// Phase 5: Create kernel namespace +// ========================================================================= + +void Bootstrapper::createKernelNamespace() { + std::vector> entries; + for (const auto& entry : _classes) { + const auto& className = entry.first; + HeapObject* cls = entry.second; + + Object* key = internSymbol_(className); + HeapObject* assoc = newAssociation_value_(key, (Object*)cls); + entries.push_back({key, assoc}); + } + // Add WordSize constant + { + Object* key = internSymbol_("WordSize"); + HeapObject* assoc = newAssociation_value_(key, (Object*)newSmallInteger_(sizeof(void*))); + entries.push_back({key, assoc}); + } + + HeapObject* namespace_ = newDictionary_("Namespace", entries); + + // Create kernel module and set its slots + _kernelModule = newSlots_("KernelModule"); + _kernelModule->slot(Offsets::ModuleName) = internSymbol_("Kernel"); + _kernelModule->slot(Offsets::ModuleNamespace) = (Object*)namespace_; + + // Add Kernel module reference to namespace + { + Object* key = internSymbol_("Kernel"); + HeapObject* assoc = newAssociation_value_(key, (Object*)_kernelModule); + insertInOpenHashTable_( + namespace_->slot(Offsets::DictionaryTable)->asHeapObject(), + namespace_->slot(Offsets::DictionaryTable)->asHeapObject()->size() - 1, + key, assoc); + namespace_->untypedSlot(Offsets::DictionaryTally) = newSmallInteger_( + ((intptr_t)namespace_->untypedSlot(Offsets::DictionaryTally) >> 1) + 1); + } + + // Create an empty namespaces array for classes without class variables + HeapObject* emptyNamespacesArray = allocateSlots_(0); + emptyNamespacesArray->behavior(_behaviors["Array"]); + emptyNamespacesArray->beArrayed(); + + // Set up each class: ClassNamespaces and ClassModule + for (const auto& entry : _classes) { + const auto& className = entry.first; + HeapObject* cls = entry.second; + + cls->slot(Offsets::ClassModule) = (Object*)_kernelModule; + + ClassSpec* spec = _moduleSpec.resolveClass(className); + if (spec && !spec->classVarNames().empty()) { + auto& cvars = spec->classVarNames(); + + std::vector> cvarEntries; + for (size_t j = 0; j < cvars.size(); j++) { + Object* key = internSymbol_(cvars[j]); + HeapObject* assoc = newAssociation_value_(key, (Object*)_nilObj); + cvarEntries.push_back({key, assoc}); + } + HeapObject* cvarNamespace = newDictionary_("Namespace", cvarEntries); + + HeapObject* namespacesArray = allocateSlots_(1); + namespacesArray->behavior(_behaviors["Array"]); + namespacesArray->beArrayed(); + namespacesArray->slot(0) = (Object*)cvarNamespace; + cls->slot(Offsets::ClassNamespaces) = (Object*)namespacesArray; + } else { + cls->slot(Offsets::ClassNamespaces) = (Object*)emptyNamespacesArray; + } + } +} + +// ========================================================================= +// Phase 6: Compile and install methods +// ========================================================================= + +void Bootstrapper::loadKernelMethods() { + for (const auto& [className, spec] : _moduleSpec.classes()) { + auto it = _classes.find(className); + if (it == _classes.end()) { + std::cerr << " Warning: Class " << className << " not found in registry" << std::endl; + continue; + } + + HeapObject* cls = it->second; + + for (const auto& method : spec->methods()) { + try { + compileAndInstallMethod_(method.source(), cls); + } catch (const std::exception& e) { + std::cerr << " Warning: Failed to compile instance method" << std::endl; + std::cerr << " " << e.what() << std::endl; + } + } + + HeapObject* metaclass = _metaclasses[className]; + for (const auto& method : spec->metaclass()->methods()) { + try { + compileAndInstallMethod_(method.source(), metaclass); + } catch (const std::exception& e) { + std::cerr << " Warning: Failed to compile class method" << std::endl; + std::cerr << " " << e.what() << std::endl; + } + } + } +} + +// ========================================================================= +// Phase 7: Create Runtime +// ========================================================================= + +void Bootstrapper::createRuntimeWithBootstrappedKernel() { + uintptr_t spaceBase = _space->base(); + uintptr_t spaceSize = _space->reservedSize(); + uintptr_t objectsEnd = _space->next(); + + BootstrappedKernel* kernel = new BootstrappedKernel(spaceBase, spaceSize, objectsEnd); + + kernel->addExport("nil", _nilObj); + kernel->addExport("true", _trueObj); + kernel->addExport("false", _falseObj); + kernel->addExport("Kernel", _kernelModule); + kernel->addExport("__module__", _kernelModule); + + kernel->addExport("Array", _classes.at("Array")); + kernel->addExport("Metaclass", _metaclassClass); + kernel->addExport("CompiledMethod", _classes.at("CompiledMethod")); + kernel->addExport("SmallInteger", _classes.at("SmallInteger")); + kernel->addExport("LargePositiveInteger", _classes.at("LargePositiveInteger")); + kernel->addExport("LargeNegativeInteger", _classes.at("LargeNegativeInteger")); + kernel->addExport("Float", _classes.at("Float")); + kernel->addExport("CompiledBlock", _classes.at("CompiledBlock")); + kernel->addExport("ByteArray", _classes.at("ByteArray")); + kernel->addExport("String", _stringClass); + kernel->addExport("Closure", _classes.at("Closure")); + kernel->addExport("Behavior", _classes.at("Behavior")); + kernel->addExport("Ephemeron", _classes.at("Ephemeron")); + kernel->addExport("ProcessVMStack", _classes.at("ProcessVMStack")); + kernel->addExport("OpenHashTable", _classes.at("OpenHashTable")); + + _loader->_runtime = new Runtime(_loader, kernel, _symbolProvider); + _loader->_runtime->addSegmentSpace_(kernel); + _loader->_runtime->initializeEvaluator(); + _loader->_runtime->initializeClosureReturnMethod(); + + // Send #bootstrap to the kernel module + auto bootstrapSymbol = _symbolProvider->symbols().at("bootstrap"); + _loader->_runtime->send_to_(bootstrapSymbol, (Object*)_kernelModule); + + // Fill symbol table + fillSymbolTable(); +} + +// ========================================================================= +// Phase 8: Fill symbol table +// ========================================================================= + +void Bootstrapper::fillSymbolTable() { + auto symbolClass = (Object*)_classes.at("Symbol"); + + auto table = _loader->_runtime->sendLocal_to_("symbolTable", symbolClass); + GCedRef tableRef(table); + + for (const auto& [name, symbol] : _symbolProvider->symbols()) { + _loader->_runtime->sendLocal_to_with_("add:", tableRef.get(), symbol); + } + + _loader->_runtime->switchToDynamicSymbolProvider_(tableRef.get()->asHeapObject()); +} + +// ========================================================================= +// Kernel-specific hash table helpers +// ========================================================================= + +HeapObject* Bootstrapper::newOpenHashTable_(uint32_t indexedSize, HeapObject* owner) { + uint32_t totalSlots = 1 + indexedSize; + HeapObject* table = newSlots_sized_("OpenHashTable", totalSlots); + table->slot(0) = (Object*)owner; + return table; +} + +void Bootstrapper::insertInOpenHashTable_(HeapObject* table, uint32_t indexedSize, Object* key, HeapObject* assoc) { + uint32_t hash = key->asHeapObject()->hash(); + uint32_t index = (hash % indexedSize); + uint32_t originalIndex = index; + do { + Object* existing = table->slot(1 + index); + if (existing == (Object*)_nilObj) { + table->slot(1 + index) = (Object*)assoc; + return; + } + index = (index + 1) % indexedSize; + } while (index != originalIndex); + std::cerr << "ERROR: OpenHashTable full during bootstrap!" << std::endl; +} + +HeapObject* Bootstrapper::newDictionary_(const Egg::string& behaviorName, + std::vector>& entries) { + uint32_t count = entries.size(); + uint32_t targetSize = std::max(7u, count * 3 / 2); + static const uint32_t primes[] = {7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, + 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, + 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, + 233, 239, 241, 251, 269, 359, 479, 641, 857}; + uint32_t primeSize = targetSize; + for (uint32_t p : primes) { + if (p >= targetSize) { primeSize = p; break; } + } + + HeapObject* dict = newSlots_(behaviorName); + dict->untypedSlot(Offsets::DictionaryTally) = newSmallInteger_(count); + + HeapObject* table = newOpenHashTable_(primeSize, dict); + dict->slot(Offsets::DictionaryTable) = (Object*)table; + + for (auto& [key, assoc] : entries) { + insertInOpenHashTable_(table, primeSize, key, assoc); + } + return dict; +} + +// ========================================================================= +// Method compilation +// ========================================================================= + +void Bootstrapper::compileAndInstallMethod_(const Egg::string& source, HeapObject* cls) { + CompilationResult* result = _compiler->compileMethod_(source); + SCompiledMethod* smethod = static_cast(result->method()); + if (!smethod) { + std::cerr << "ERROR: Failed to compile method from source: '" + << source.substr(0, std::min(source.length(), size_t(60))) << "...'" << std::endl; + std::exit(1); + } + + Egg::string selector = smethod->selector(); + + const auto& treecodes = smethod->treecodes(); + const auto& literals = smethod->literals(); + uint32_t literalCount = literals.size(); + + // Transfer the compiled method to a heap object + HeapObject* method = allocateSlots_(Offsets::MethodInstSize + literalCount); + + // Set method's behavior to CompiledMethod's instance behavior + Object* cmBehavior = _compiledMethodClass->slot(Offsets::SpeciesInstanceBehavior); + if (cmBehavior && cmBehavior != (Object*)_nilObj) { + method->behavior(cmBehavior->asHeapObject()); + } + + method->slot(Offsets::MethodFormat) = (Object*)newSmallInteger_(smethod->format()); + method->slot(Offsets::MethodExecutableCode) = (Object*)_nilObj; + method->slot(Offsets::MethodTreecodes) = (Object*)newByteArray_(treecodes); + method->slot(Offsets::MethodClassBinding) = (Object*)cls; + method->slot(Offsets::MethodSelector) = internSymbol_(selector); + method->slot(Offsets::MethodSourceCode) = (Object*)newString_(source); + + // Transfer literals + for (uint32_t i = 0; i < literalCount; i++) { + method->slot(Offsets::MethodInstSize + i) = transferLiteral_(literals[i], method); + } + + // Install in behavior's method dictionary + HeapObject* behavior = cls->slot(Offsets::SpeciesInstanceBehavior)->asHeapObject(); + Object* methodDictObj = behavior->slot(Offsets::BehaviorMethodDictionary); + HeapObject* methodArray; + if (methodDictObj == nullptr || methodDictObj == (Object*)_nilObj) { + methodArray = newMethodArray(); + behavior->slot(Offsets::BehaviorMethodDictionary) = (Object*)methodArray; + } else { + methodArray = methodDictObj->asHeapObject(); + } + addMethodToArray_(methodArray, internSymbol_(selector), (Object*)method); +} + +Object* Bootstrapper::transferLiteral_(const LiteralValue& lit, HeapObject* method) { + switch (lit.tag) { + case LiteralValue::Symbol: + return internSymbol_(lit.asString()); + case LiteralValue::String: + return (Object*)newString_(lit.asString()); + case LiteralValue::Integer: + return (Object*)newSmallInteger_(lit.asInteger()); + case LiteralValue::Float: + return (Object*)_nilObj; + case LiteralValue::Character: + return (Object*)newSmallInteger_((intptr_t)lit.asCharacter()); + case LiteralValue::Boolean: + return (Object*)(lit.asBoolean() ? _trueObj : _falseObj); + case LiteralValue::Nil: + return (Object*)_nilObj; + case LiteralValue::Array: + return (Object*)transferArray_(lit.asArray()); + case LiteralValue::ByteArray: { + HeapObject* ba = newByteArray_(lit.asByteArray()); + return (Object*)ba; + } + case LiteralValue::Block: + return (Object*)transferBlock_(lit.asBlock(), method); + default: + error("transferLiteral_: unimplemented literal tag"); + return (Object*)_nilObj; + } +} + +HeapObject* Bootstrapper::transferArray_(const std::vector& elements) { + HeapObject* arr = newArray_(elements.size()); + arr->behavior(_behaviors["Array"]); + for (size_t i = 0; i < elements.size(); i++) { + arr->slot(i) = transferLiteral_(elements[i], nullptr); + } + return arr; +} + +HeapObject* Bootstrapper::transferBlock_(const LiteralValue::BlockInfo& info, HeapObject* method) { + HeapObject* block = allocateSlots_(3); + + auto it = _classes.find("CompiledBlock"); + if (it != _classes.end()) { + Object* cbBehavior = it->second->slot(Offsets::SpeciesInstanceBehavior); + if (cbBehavior && cbBehavior != (Object*)_nilObj) { + block->behavior(cbBehavior->asHeapObject()); + } + } + + uint32_t format = (info.argCount & 0x3F) + | ((info.tempCount & 0xFF) << 6) + | ((info.id & 0xFF) << 14) + | (info.capturesSelf ? 0x400000 : 0) + | (info.capturesHome ? 0x800000 : 0) + | ((info.envCount & 0x7F) << 24); + + block->slot(Offsets::BlockFormat) = (Object*)newSmallInteger_(format); + block->slot(Offsets::BlockExecutableCode) = (Object*)_nilObj; + block->slot(Offsets::BlockMethod) = (Object*)method; + + return block; +} + +// ========================================================================= +// Object creation helpers +// ========================================================================= + +HeapObject* Bootstrapper::newBytes_(const Egg::string& className, const void* data, uint32_t byteCount) { + HeapObject* obj = allocateBytesRaw_(byteCount); + obj->behavior(_behaviors[className]); + if (data) + std::memcpy((void*)obj, data, byteCount); + return obj; +} + +HeapObject* Bootstrapper::newSlots_(const Egg::string& className) { + return newSlots_sized_(className, instSizeOf_(className)); +} + +HeapObject* Bootstrapper::newSlots_sized_(const Egg::string& className, uint32_t slotCount) { + HeapObject* obj = allocateSlots_(slotCount); + obj->behavior(_behaviors[className]); + ClassSpec* spec = _moduleSpec.resolveClass(className); + if (spec) { + if (spec->instSize() > 0) + obj->beNamed(); + if (spec->isVariable()) + obj->beArrayed(); + } else { + auto it = _classes.find(className); + if (it != _classes.end()) { + auto format = it->second->slot(Offsets::SpeciesFormat)->asSmallInteger()->asNative(); + if ((format & 0x7F) > 0) + obj->beNamed(); + if (format & 0x2000) + obj->beArrayed(); + } + } + for (uint32_t i = 0; i < slotCount; i++) + obj->untypedSlot(i) = (Object*)_nilObj; + return obj; +} + +HeapObject* Bootstrapper::newAssociation_value_(const Egg::string& key, Object* value) { + return newAssociation_value_(internSymbol_(key), value); +} + +HeapObject* Bootstrapper::newAssociation_value_(Object* key, Object* value) { + HeapObject* assoc = newSlots_("Association"); + assoc->slot(Offsets::AssociationKey) = key; + assoc->slot(Offsets::AssociationValue) = value; + return assoc; +} + +Object* Bootstrapper::internSymbol_(const Egg::string& str) { + return _symbolProvider->symbolFor_(str); +} + +Object* Bootstrapper::newSymbol_(const Egg::string& str) { + bool isWide = std::ranges::any_of(str, [](char32_t cp) { return cp > 0xFF; }); + HeapObject* symbol; + if (!isWide) { + uint32_t len = str.size(); + symbol = newBytes_("Symbol", nullptr, len + 1); + for (uint32_t i = 0; i < len; i++) + ((uint8_t*)symbol)[i] = (uint8_t)str[i]; + ((uint8_t*)symbol)[len] = 0; + } else { + uint32_t len = str.size(); + symbol = newBytes_("WideSymbol", nullptr, len * 4); + for (uint32_t i = 0; i < len; i++) + ((uint32_t*)symbol)[i] = (uint32_t)str[i]; + } + return (Object*)symbol; +} + +HeapObject* Bootstrapper::newString_(const Egg::string& str) { + bool isWide = std::ranges::any_of(str, [](char32_t cp) { return cp > 0xFF; }); + + if (!isWide) { + uint32_t len = str.size(); + HeapObject* string = newBytes_("String", nullptr, len + 1); + for (uint32_t i = 0; i < len; i++) + ((uint8_t*)string)[i] = (uint8_t)str[i]; + ((uint8_t*)string)[len] = 0; + return string; + } else { + uint32_t len = str.size(); + HeapObject* string = newBytes_("WideString", nullptr, len * 4); + for (uint32_t i = 0; i < len; i++) + ((uint32_t*)string)[i] = (uint32_t)str[i]; + return string; + } +} + +HeapObject* Bootstrapper::newArray_(uint32_t size) { + HeapObject* array = allocateSlots_(size); + array->behavior(_behaviors["Array"]); + array->beArrayed(); + return array; +} + +HeapObject* Bootstrapper::newByteArray_(const std::vector& bytes) { + return newBytes_("ByteArray", bytes.data(), bytes.size()); +} + +HeapObject* Bootstrapper::newMethodArray() { + uint32_t capacity = 1024; + HeapObject* array = allocateSlots_(capacity); + array->behavior(_behaviors["Array"]); + for (uint32_t i = 0; i < capacity; i++) { + array->slot(i) = (Object*)_nilObj; + } + return array; +} + +void Bootstrapper::addMethodToArray_(HeapObject* array, Object* selector, Object* method) { + uint32_t size = array->size(); + for (uint32_t i = 0; i < size; i += 2) { + Object* existing = array->slot(i); + if (existing == (Object*)_nilObj) { + array->slot(i) = selector; + array->slot(i + 1) = method; + return; + } + if (existing == selector) { + array->slot(i + 1) = method; + return; + } + } + throw std::runtime_error("Method array is full"); +} + +// ========================================================================= +// Low-level memory allocation +// ========================================================================= + +uintptr_t Bootstrapper::align_(uintptr_t addr) { + return (addr + 7) & ~7; +} + +HeapObject* Bootstrapper::initializeHeader_(void* allocation, uint32_t size, uint8_t flags) { + bool isSmall = (size <= HeapObject::MAX_SMALL_SIZE); + if (isSmall) { + HeapObject::SmallHeader* header = HeapObject::SmallHeader::at(allocation); + header->size = (uint8_t)size; + header->hash = 0; + header->behavior = 0; + header->flags = HeapObject::SmallHeader::IsSmall | flags; + return header->object(); + } else { + HeapObject::LargeHeader* header = HeapObject::LargeHeader::at(allocation); + header->size = size; + header->padding = 0; + header->smallHeader.size = 0; + header->smallHeader.hash = 0; + header->smallHeader.behavior = 0; + header->smallHeader.flags = flags; + return header->object(); + } +} + +HeapObject* Bootstrapper::allocateSlots_(uint32_t slotCount) { + if (_loader->_runtime) { + return _loader->_runtime->_heap->allocateSlots_(slotCount); + } + + bool isSmall = (slotCount <= HeapObject::MAX_SMALL_SIZE); + uintptr_t headerSize = isSmall ? sizeof(HeapObject::SmallHeader) : sizeof(HeapObject::LargeHeader); + uintptr_t totalSize = align_(headerSize + slotCount * sizeof(Object*)); + + uintptr_t allocation = _space->allocateCommittingIfNeeded_(totalSize); + if (!allocation) { + error("Bootstrapper: Out of memory"); + return nullptr; + } + + return initializeHeader_((void*)allocation, slotCount, 0); +} + +HeapObject* Bootstrapper::allocateBytesRaw_(uint32_t byteCount) { + if (_loader->_runtime) { + return _loader->_runtime->_heap->allocateBytes_(byteCount); + } + + bool isSmall = (byteCount <= HeapObject::MAX_SMALL_SIZE); + uintptr_t headerSize = isSmall ? sizeof(HeapObject::SmallHeader) : sizeof(HeapObject::LargeHeader); + uintptr_t totalSize = align_(headerSize + byteCount); + + uintptr_t allocation = _space->allocateCommittingIfNeeded_(totalSize); + if (!allocation) { + error("Bootstrapper: Out of memory"); + return nullptr; + } + + return initializeHeader_((void*)allocation, byteCount, HeapObject::SmallHeader::IsBytes); +} + +} // namespace Egg diff --git a/runtime/cpp/Bootstrap/Bootstrapper.h b/runtime/cpp/Bootstrap/Bootstrapper.h new file mode 100644 index 00000000..152564f8 --- /dev/null +++ b/runtime/cpp/Bootstrap/Bootstrapper.h @@ -0,0 +1,142 @@ +/* + Copyright (c) 2025-2026, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _BOOTSTRAPPER_H_ +#define _BOOTSTRAPPER_H_ + +#include +#include +#include +#include +#include +#include "../HeapObject.h" +#include "Utils/egg_string.h" +#include "../Compiler/LiteralValue.h" +#include "../Allocator/GCSpace.h" +#include "../SymbolProvider.h" +#include "CodeSpecs.h" + +namespace Egg +{ + + class Loader; + class Runtime; + class SSmalltalkCompiler; + + class Bootstrapper + { + friend class BootstrapperTestFixture; + + public: + Loader *_loader; + BootstrapSymbolProvider *_symbolProvider; + std::string _kernelPath; + ModuleSpec _moduleSpec; + + // Bootstrap state + GCSpace *_space; + std::unique_ptr _compiler; + HeapObject *_nilObj; + HeapObject *_trueObj; + HeapObject *_falseObj; + HeapObject *_kernelModule; + HeapObject *_compiledMethodClass; + std::map _classes; + std::map _metaclasses; + std::map _behaviors; + std::map _metaBehaviors; + + // Cached class pointers (kernel-specific) + HeapObject *_undefinedObjectClass; + HeapObject *_trueClass; + HeapObject *_falseClass; + HeapObject *_classClass; + HeapObject *_metaclassClass; + HeapObject *_symbolClass; + HeapObject *_wideSymbolClass; + HeapObject *_stringClass; + HeapObject *_wideStringClass; + HeapObject *_arrayClass; + HeapObject *_methodDictionaryClass; + + public: + Bootstrapper(const std::string &kernelPath, Loader *loader); + ~Bootstrapper(); + + // Main bootstrapping method + Runtime *bootstrap(); + + // Phase 1: Parse class definitions from source files + std::string readSourceFile_(const std::string &className); + void loadKernelSpecs(); + + // Phase 2: Create initial objects + void createInitialObjects(); + + // Phase 3: Allocate all species and behaviors + void instantiateMetaobjects(); + + // Phase 4: Initialize metaobjects (link them together) + void initializeMetaobjects(); + + // Phase 5: Create kernel namespace + void createKernelNamespace(); + + // Phase 6: Compile and install methods + void loadKernelMethods(); + + // Phase 7: Create Runtime with bootstrapped kernel + void createRuntimeWithBootstrappedKernel(); + + // Phase 8: Fill Smalltalk symbol table with bootstrap symbols + void fillSymbolTable(); + + // Kernel-specific hash table helpers + HeapObject *newOpenHashTable_(uint32_t indexedSize, HeapObject *owner); + void insertInOpenHashTable_(HeapObject *table, uint32_t indexedSize, Object *key, HeapObject *assoc); + HeapObject *newDictionary_(const Egg::string &behaviorName, std::vector> &entries); + + // Metaobject creation + void instantiateMetaobjectsOf_(const Egg::string &className); + void initializeMetaobjectsOf_(const Egg::string &className); + uint32_t instSizeOf_(const Egg::string &className); + + // Method compilation + void compileAndInstallMethod_(const Egg::string &source, HeapObject *cls); + Object *transferLiteral_(const LiteralValue &lit, HeapObject *method); + HeapObject *transferBlock_(const LiteralValue::BlockInfo &blockInfo, HeapObject *method); + HeapObject *transferArray_(const std::vector &elements); + + // Object creation helpers + HeapObject *newBytes_(const Egg::string &className, const void *data, uint32_t byteCount); + HeapObject *newSlots_(const Egg::string &className); + HeapObject *newSlots_sized_(const Egg::string &className, uint32_t slotCount); + HeapObject *newAssociation_value_(const Egg::string &key, Object *value); + HeapObject *newAssociation_value_(Object *key, Object *value); + Object *internSymbol_(const Egg::string &str); + Object *newSymbol_(const Egg::string &str); + HeapObject *newString_(const Egg::string &str); + HeapObject *newArray_(uint32_t size); + HeapObject *newByteArray_(const std::vector &bytes); + HeapObject *newMethodArray(); + void addMethodToArray_(HeapObject *array, Object *selector, Object *method); + + // SmallInteger helper + Object *newSmallInteger_(intptr_t value) + { + return (Object *)((value << 1) | 1); + } + + private: + // Low-level memory allocation + HeapObject *allocateSlots_(uint32_t slotCount); + HeapObject *allocateBytesRaw_(uint32_t byteCount); + HeapObject *initializeHeader_(void *allocation, uint32_t size, uint8_t flags); + uintptr_t align_(uintptr_t addr); + }; + +} // namespace Egg + +#endif // _BOOTSTRAPPER_H_ diff --git a/runtime/cpp/Bootstrapper.h b/runtime/cpp/Bootstrapper.h deleted file mode 100644 index 44e96bfc..00000000 --- a/runtime/cpp/Bootstrapper.h +++ /dev/null @@ -1,179 +0,0 @@ - - -#include -#include -#include -#include -#include - -#include "Allocator/GCHeap.h" -#include "Allocator/GCSpace.h" -#include "Evaluator/Evaluator.h" -#include "Evaluator/SAssociationBinding.h" - -namespace Egg { - -class Bootstrapper { - public: - std::map _segments; - Runtime *_runtime; - ImageSegment *_kernel; - - Bootstrapper(ImageSegment *kernel) { - this->_kernel = kernel; - this->_runtime = new Runtime(this, this->_kernel); - - this->_runtime->addSegmentSpace_(kernel); -// this->_runtime->bootstrapper_(this); - this->_runtime->initializeEvaluator(); - } - - HeapObject* loadModule_(std::string name) - { - auto imageSegment = _segments.contains(name) ? _segments[name] : this->loadModuleFromFile(name + ".ems"); - return imageSegment->_exports["__module__"]; - } - - void - bindModuleImports(ImageSegment *imageSegment, std::vector &imports) - { - for (int i = 0; i < imageSegment->_importDescriptors.size(); i++) - { - imports.push_back(this->bindModuleImport(imageSegment, imageSegment->_importDescriptors[i])); - } - } - - Object* bindModuleImport(ImageSegment* imageSegment, std::vector &descriptor) - { - - auto linker = this->importStringAt_(imageSegment, descriptor[0]); - HeapObject *token; - if (descriptor.size() == 1) - token = this->_kernel->_exports["nil"]; - else if (descriptor.size() == 2) - token = this->importStringAt_(imageSegment, descriptor[1]); - else - { - std::vector array; - for (int i = 1; i < descriptor.size(); i++) - array.push_back(this->importStringAt_(imageSegment, descriptor[i])); - token = this->transferArray(array); - } - - auto ref = this->_runtime->sendLocal_to_with_with_("linker:token:", (Object*)this->_kernel->_exports["SymbolicReference"], (Object*)linker, (Object*)token); - return this->_runtime->sendLocal_to_("link", ref); - } - - HeapObject* importStringAt_(ImageSegment* imageSegment, uint32_t index) - { - return this->transferSymbol(imageSegment->importStringAt_(index)); - } - - HeapObject* transferSymbol(std::string &str) - { - return this->_runtime->addSymbol_(str); - } - - HeapObject* transferArray(std::vector &array) - { - return this->_runtime->newArray_(array); - } - - HeapObject* transferArray(std::vector &array) - { - return this->_runtime->newArray_(array); - } - - - ImageSegment* loadModuleFromFile(const std::string &filename) - { - auto filepath = this->findInPath(filename); - auto stream = std::ifstream(filepath, std::ios::binary); - auto imageSegment = new ImageSegment(&stream); - std::vector imports; - this->bindModuleImports(imageSegment, imports); - imageSegment->fixPointerSlots(imports); - this->_runtime->addSegmentSpace_(imageSegment); - return imageSegment; - } - - std::filesystem::path findInPath(const std::string &filename) - { - std::vector dirs({"./", "../"}); - auto searched = "image-segments/" + filename; - for (auto dir : dirs) { - auto filePath = std::filesystem::path(dir) / searched; - - if (std::filesystem::exists(filePath)) - return filePath; - } - - auto str = std::string("could not find module snapshot file ") + filename; - error(str.c_str()); - std::terminate(); - } - - // only used for testing that the most basic things work (i.e. sending messages) - ImageSegment* bareLoadModuleFromFile(const std::string &filename) - { - auto filepath = this->findInPath(filename); - auto stream = std::ifstream(filepath); - auto imageSegment = new ImageSegment(&stream); - std::vector imports; - for (int i = 0; i < imageSegment->_importDescriptors.size(); i++) - { - std::vector &descriptor = imageSegment->_importDescriptors[i]; - auto import = this->bareBindModuleImport(imageSegment, descriptor); - std::cout << "import " << i << " is: " << import->printString() << std::endl; - imports.push_back(import); - } - imageSegment->fixPointerSlots(imports); - return imageSegment; - } - - Object* bareBindModuleImport(ImageSegment* imageSegment, std::vector &descriptor) - { - - auto linker = imageSegment->importStringAt_(descriptor[0]); - std::vector tokens; - for (int i = 1; i < descriptor.size(); i++) - tokens.push_back(imageSegment->importStringAt_(descriptor[i])); - - if (linker == "asSymbol") { - auto symbol = _runtime->symbolTableAt_(tokens[0]); - if (symbol == nullptr) { - symbol = (Object*)_runtime->newString_(tokens[0]); - _runtime->addKnownSymbol_(tokens[0], symbol); - } - return (Object*)symbol; - } - if (linker == "nil") - return (Object*)_runtime->_nilObj; - if (linker == "true") - return (Object*)_runtime->_trueObj; - if (linker == "false") - return (Object*)_runtime->_falseObj; - if (linker == "asClass") - return (Object*)_kernel->_exports[tokens[1]]; - if (linker == "asMetaclass") - return (Object*)_runtime->speciesOf_((Object*)_kernel->_exports[tokens[1]]); - if (linker == "asBehavior") - return (Object*)_runtime->speciesInstanceBehavior_(_kernel->_exports[tokens[1]]); - if (linker == "asMetaclassBehavior") - return (Object*)_runtime->speciesInstanceBehavior_(_runtime->speciesOf_((Object*)_kernel->_exports[tokens[1]])); - if (linker == "asModule") - return (Object*)_kernel->_exports["__module__"]; - if (linker == "symbolTable") - return (Object*)_kernel->_exports["SymbolTable"]; - if (linker == "nilToken") { - auto hashTable = _kernel->_exports["HashTable"]; - auto symbol = _runtime->existingSymbolFrom_("NilToken"); - auto binding = (SAssociationBinding*)_runtime->_evaluator->context()->staticBindingForCvar_in_(symbol, hashTable); - return binding->valueWithin_(_runtime->_evaluator->context()); - } - ASSERT(false); - std::terminate(); - } -}; - -} diff --git a/runtime/cpp/Evaluator/EvaluationContext.cpp b/runtime/cpp/Evaluator/EvaluationContext.cpp index 9cf61025..34d89ac6 100644 --- a/runtime/cpp/Evaluator/EvaluationContext.cpp +++ b/runtime/cpp/Evaluator/EvaluationContext.cpp @@ -16,6 +16,16 @@ using namespace Egg; +EvaluationContext::EvaluationContext(Runtime *runtime) : _runtime(runtime) +{ + _regM = nullptr; + _regE = runtime->_nilObj; + _regSP = STACK_SIZE + 1; + _regBP = _regPC = 0; + _regS = nullptr; + _stack = new Object*[STACK_SIZE]; +} + HeapObject* EvaluationContext::classBinding() { return _runtime->methodClassBinding_(this->method()); @@ -242,7 +252,8 @@ SBinding* EvaluationContext::staticBindingForCvar_in_(Object *aSymbol, HeapObjec } SBinding* EvaluationContext::staticBindingForCvar_(Object *aSymbol) { - auto species = this->_runtime->methodClassBinding_(this->method()); + auto method = this->method(); + auto species = this->_runtime->methodClassBinding_(method); return staticBindingForCvar_in_(aSymbol, species); } diff --git a/runtime/cpp/Evaluator/EvaluationContext.h b/runtime/cpp/Evaluator/EvaluationContext.h index dc44ca3b..765b750b 100644 --- a/runtime/cpp/Evaluator/EvaluationContext.h +++ b/runtime/cpp/Evaluator/EvaluationContext.h @@ -36,17 +36,7 @@ class EvaluationContext { public: const int STACK_SIZE = 64 * 1024; - EvaluationContext(Runtime *runtime) : - _runtime(runtime) - { - _regM = _regE = nullptr; - _regSP = STACK_SIZE + 1; - _regBP = _regPC = 0; - _regS = 0; - - _stack = new Object*[STACK_SIZE]; - - } + EvaluationContext(Runtime *runtime); Object* receiver() { return _regS; } Object* self() { return _regS; } diff --git a/runtime/cpp/Evaluator/Evaluator.cpp b/runtime/cpp/Evaluator/Evaluator.cpp index 8900f6db..bd3e6836 100644 --- a/runtime/cpp/Evaluator/Evaluator.cpp +++ b/runtime/cpp/Evaluator/Evaluator.cpp @@ -106,6 +106,7 @@ void Evaluator::initializeUndermessages() { this->addUndermessage("_smiBitAnd:", &Evaluator::underprimitiveSMIBitAnd); this->addUndermessage("_smiBitOr:", &Evaluator::underprimitiveSMIBitOr); this->addUndermessage("_halt", &Evaluator::underprimitiveHalt); + this->addUndermessage("_error:", &Evaluator::underprimitiveError); } @@ -172,6 +173,7 @@ void Evaluator::initializePrimitives() //this->addPrimitive("HostSuspendedBecause", &Evaluator::primitiveHostSuspendedBecause); this->addPrimitive("HostLoadModule", &Evaluator::primitiveHostLoadModule); //this->addPrimitive("HostFixOverrides", &Evaluator::primitiveHostFixOverrides); + this->addPrimitive("DictionaryNew", &Evaluator::primitiveDictionaryNew); this->addPrimitive("PrimeFor", &Evaluator::primitivePrimeFor); this->addPrimitive("FlushFromCaches", &Evaluator::primitiveFlushFromCaches); this->addPrimitive("FFICall", &Evaluator::primitiveFFICall); @@ -456,7 +458,8 @@ void Evaluator::visitOpPushR(SOpPushR *anSOpPushR) void Evaluator::popFrameAndPrepare() { _context->popFrame(); - auto code = _runtime->methodExecutableCode_(_context->compiledCode()); + auto method = _context->compiledCode(); + auto code = _runtime->methodExecutableCode_(method); _work = _runtime->executableCodeWork_(code); } @@ -601,6 +604,23 @@ Object* Evaluator::primitiveClass() { return (Object*)this->_runtime->speciesOf_(this->_context->self()); } +Object* Evaluator::primitiveDictionaryNew() { + auto guard = this->_runtime->_heap->atGCSafepoint(); + // self is Namespace class (or any HashedCollection subclass metaclass). + // Equivalent to: self basicNew initialize: (self sizeFor: 5) + // sizeFor: 5 => 7 max: (5*3//2) = 7. primeFor: 7 => 7. + auto species = this->_context->self()->asHeapObject(); + auto table = this->_runtime->newSlots_size_(this->_runtime->_openHashTableClass, 7); + auto instance = this->_runtime->newSlotsOf_(species); + // tally := 0 + instance->slot(0) = (Object*)this->_runtime->newInteger_(0); + // table := aHashTable + instance->slot(1) = (Object*)table; + // hashTable policy := instance (OpenHashTable named slot 0 = policy) + table->slot(0) = (Object*)instance; + return (Object*)instance; +} + Object* Evaluator::primitiveClosureArgumentCount() { auto block = _runtime->closureBlock_(this->_context->self()->asHeapObject()); auto count = _runtime->blockArgumentCount_(block); @@ -883,14 +903,20 @@ Object* Evaluator::primitiveNew() { } Object* Evaluator::primitiveNewBytes() { + auto arg = this->_context->firstArgument(); + if (!arg->isSmallInteger()) + return this->failPrimitive(); auto guard = this->_runtime->_heap->atGCSafepoint(); - auto size = this->_context->firstArgument()->asSmallInteger()->asNative(); + auto size = arg->asSmallInteger()->asNative(); return (Object*)this->_runtime->newBytes_size_(this->_context->self()->asHeapObject(), size); } Object* Evaluator::primitiveNewSized() { + auto arg = this->_context->firstArgument(); + if (!arg->isSmallInteger()) + return this->failPrimitive(); auto guard = this->_runtime->_heap->atGCSafepoint(); - auto size = this->_context->firstArgument()->asSmallInteger()->asNative(); + auto size = arg->asSmallInteger()->asNative(); return (Object*)this->_runtime->newOf_sized_(this->_context->self()->asHeapObject(), size); } @@ -1022,7 +1048,9 @@ Object* Evaluator::primitiveSetBehavior() { } Object* Evaluator::primitiveSize() { - return newIntObject(this->_runtime->arrayedSizeOf_(this->_context->self())); + auto self = this->_context->self(); + auto result = this->_runtime->arrayedSizeOf_(self); + return newIntObject(result); } Object* Evaluator::primitiveStringReplaceFromToWithStartingAt() { @@ -1272,6 +1300,12 @@ Object* Evaluator::underprimitiveHalt(Object *receiver, std::vector &ar return receiver; } +Object* Evaluator::underprimitiveError(Object *receiver, std::vector &args) { + std::string msg = args[0]->asHeapObject()->printString(); + error_(msg); + return receiver; +} + Object* Evaluator::underprimitiveIdentityEquals(Object *receiver, std::vector &args) { return boolObject(receiver == args[0]); } diff --git a/runtime/cpp/Evaluator/Evaluator.h b/runtime/cpp/Evaluator/Evaluator.h index b61b06ef..bf28b90d 100644 --- a/runtime/cpp/Evaluator/Evaluator.h +++ b/runtime/cpp/Evaluator/Evaluator.h @@ -192,6 +192,7 @@ class Evaluator : public SExpressionVisitor { Object* primitiveBootstrapDictKeys(); Object* primitiveBootstrapDictNew(); Object* primitiveClass(); + Object* primitiveDictionaryNew(); Object* primitiveClosureArgumentCount(); Object* primitiveClosureAsCallback(); Object* primitiveClosureValue(); @@ -270,6 +271,7 @@ class Evaluator : public SExpressionVisitor { Object* underprimitiveByteAt(Object *receiver, std::vector &args); Object* underprimitiveByteAtPut(Object *receiver, std::vector &args); Object* underprimitiveHalt(Object *receiver, std::vector &args); + Object* underprimitiveError(Object *receiver, std::vector &args); Object* underprimitiveIdentityEquals(Object *receiver, std::vector &args); Object* underprimitiveIsLarge(Object *receiver, std::vector &args); Object* underprimitiveIsSmallInteger(Object *receiver, std::vector &args); diff --git a/runtime/cpp/Evaluator/Runtime.cpp b/runtime/cpp/Evaluator/Runtime.cpp index 3f1930e4..a4be2095 100644 --- a/runtime/cpp/Evaluator/Runtime.cpp +++ b/runtime/cpp/Evaluator/Runtime.cpp @@ -2,7 +2,7 @@ #include #include "Runtime.h" -#include "Bootstrapper.h" +#include "Loader.h" #include "Evaluator.h" #include "Allocator/GCHeap.h" #include "SAbstractMessage.h" @@ -16,9 +16,10 @@ using namespace Egg; Runtime *Egg::debugRuntime = nullptr; -Runtime::Runtime(Bootstrapper* bootstrapper, ImageSegment* kernel): - _bootstrapper(bootstrapper), +Runtime::Runtime(Loader* loader, ImageSegment* kernel, SymbolProvider* symbolProvider): + _loader(loader), _kernel(kernel), + _symbolProvider(symbolProvider), _lastHash(0) { this->initializeKernelObjects(); @@ -33,6 +34,21 @@ void Runtime::initializeEvaluator() { } +void Runtime::initializeClosureReturnMethod() { + auto symbol = this->existingSymbolFrom_("return:"); + if (!symbol) { + std::cerr << "Warning: 'return:' symbol not found" << std::endl; + return; + } + auto behavior = this->speciesInstanceBehavior_(_closureClass); + auto method = this->lookup_startingAt_(symbol, behavior); + if (!method) { + std::cerr << "Warning: Could not find 'return:' method in Closure behavior" << std::endl; + return; + } + this->_closureReturnMethod = method->asHeapObject(); +} + uintptr_t Runtime::arrayedSizeOf_(Object *anObject) { if (anObject->isSmallInteger()) return 0; @@ -136,11 +152,29 @@ HeapObject *Runtime::newString_(const std::string &str) } HeapObject *Runtime::addSymbol_(const std::string &str){ - return this->sendLocal_to_("asSymbol", (Object*)this->newString_(str))->asHeapObject(); + auto result = _symbolProvider->symbolFor_(str); + return result->asHeapObject(); +} + +Object* Runtime::send_to_(Object *symbol, Object *receiver) { + std::vector args; + return this->_evaluator->send_to_with_(symbol, receiver, args); +} + +Object* Runtime::send_to_with_(Object *symbol, Object *receiver, Object* arg1) { + std::vector args; + args.push_back(arg1); + return this->_evaluator->send_to_with_(symbol, receiver, args); +} + +void Runtime::switchToDynamicSymbolProvider_(HeapObject* symbolTable) { + // Don't delete old provider — Bootstrapper may still reference it + _symbolProvider = new DynamicSymbolProvider(this, symbolTable); +; } HeapObject *Runtime::loadModule_(HeapObject *name) { - return _bootstrapper->loadModule_(name->asLocalString()); + return _loader->loadModule_(name->asLocalString()); } void Runtime::addSegmentSpace_(ImageSegment* segment) @@ -166,7 +200,6 @@ uintptr_t Runtime::hashFor_(Object *anObject) Object* Runtime::sendLocal_to_withArgs_(const std::string &selector, Object *receiver, std::vector &arguments) { auto symbol = this->existingSymbolFrom_(selector); - return this->_evaluator->send_to_with_(symbol, receiver, arguments); } @@ -218,14 +251,10 @@ Object* Runtime::lookup_startingAt_(Object *symbol, HeapObject *behavior) { checkCache(); - //if (symbol->printString() == "#sizeInBytes") { - // int a = 0; - //} auto iter = _globalCache.find(global_cache_key(symbol,(Object*)behavior)); if (iter != _globalCache.end()) { - if (iter->second->get()->asHeapObject()->slotAt_(5)->printString() != symbol->printString()) - int b = 1; - return iter->second->get(); + auto result = iter->second->get(); + return result; } auto method = this->doLookup_startingAt_(symbol, behavior); @@ -254,6 +283,24 @@ Object* Runtime::doLookup_startingAt_(Object *symbol, HeapObject *startBehavior) Object* Runtime::methodFor_in_(Object *symbol, HeapObject *behavior) { auto md = this->behaviorMethodDictionary_(behavior); + HeapObject* symbolObj = symbol->asHeapObject(); + + // Array-based method dict: linear scan of [selector, method, selector, method, ...] + if (this->speciesOf_((Object*)md) == this->_arrayClass) { + uint32_t size = md->size(); + for (uint32_t i = 0; i + 1 < size; i += 2) { + Object* key = md->slot(i); + if (key == (Object*)this->_nilObj) + return nullptr; + if (key == symbol) + return md->slot(i + 1); + } + return nullptr; + } + + // Hashed MethodDictionary lookup (InlinedHashTable: key-value pairs in indexed slots) + // The table is an InlinedHashTable, a subclass of HashTable which extends Array + // with a named ivar `policy` at slot(0). Indexed key-value pairs start at slot(1). auto table = this->dictionaryTable_(md); for (int index = 2; index < table->size(); index += 2) { if (table->slotAt_(index) == symbol) @@ -263,35 +310,9 @@ Object* Runtime::methodFor_in_(Object *symbol, HeapObject *behavior) } Object* Runtime::existingSymbolFrom_(const std::string &selector) { - auto result = this->symbolTableAt_(selector); - if (result == nullptr) { - std::string str = std::string("symbol #") + selector + " not found in image"; - error(str.c_str()); - } - return result; -} -Object* Runtime::symbolTableAt_(const std::string &selector) -{ - auto it = this->_knownSymbols.find(selector); - if (it != this->_knownSymbols.end()) - return it->second->get(); - - if (selector == "linker:token:") { - int a = 0; - } - HeapObject *table = this->_symbolTable->slotAt_(2)->asHeapObject(); - for (int i = 2; i < table->size(); i++){ - auto symbol = table->slotAt_(i); - if (symbol != (Object*)this->_nilObj){ - //std::cout << "symbol" << symbol->printString() << " at: 0x" << i << std::endl; - if (symbol->asHeapObject()->sameBytesThan(selector)) - return symbol; - } - } - - return nullptr; + return _symbolProvider->existingSymbolFor_(selector); } - + HeapObject* Runtime::lookupAssociationFor_in_(Object *symbol, HeapObject *dictionary) { auto table = this->dictionaryTable_(dictionary); for (int index = 2; index <= table->size(); index++) { diff --git a/runtime/cpp/Launcher.cpp b/runtime/cpp/Launcher.cpp index bd89336e..88620fd1 100644 --- a/runtime/cpp/Launcher.cpp +++ b/runtime/cpp/Launcher.cpp @@ -3,23 +3,18 @@ See (MIT) license in root directory. */ -#include #include -#include -#include +#include #include "Launcher.h" -#include "ImageSegment.h" #include "Util.h" -#include "Bootstrapper.h" +#include "Loader.h" +#include "GCedRef.h" -#include "Evaluator/Evaluator.h" #include "Evaluator/Runtime.h" using namespace Egg; -void start(Runtime *runtime, HeapObject *kernel, std::vector &args); - int Launcher::main(const int argc, const char** argv) { @@ -27,26 +22,38 @@ Launcher::main(const int argc, const char** argv) printf("Usage: %s \n", argv[0]); return 1; } - std::ifstream kernelFile("Kernel.ems", std::ifstream::binary); - if (!kernelFile) { - printf("No Kernel.ems file\n"); - return 1; - } Egg::Initialize(); - auto kernelSegment = new ImageSegment(&kernelFile); - kernelSegment->fixPointerSlots({}); - auto bootstrapper = new Bootstrapper(kernelSegment); - auto runtime = bootstrapper->_runtime; - HeapObject *kernel = bootstrapper->_kernel->_exports["Kernel"]; + // Determine modules directory from the module path argument + std::filesystem::path modulePath(argv[1]); + std::string modulesDir; + if (modulePath.has_parent_path()) { + modulesDir = modulePath.parent_path().string(); + } else { + modulesDir = "modules"; + } + auto loader = new Loader(modulesDir); + Runtime* runtime = loader->loadKernel(); + if (!runtime) { + return 1; + } + + // Extract module name from path + auto moduleName = modulePath.filename().string(); - std::vector args; + auto kernelModule = (Object*)runtime->_kernel->_exports["__module__"]; + runtime->sendLocal_to_("useHostModuleLoader", kernelModule); + auto moduleNameSym = (Object*)runtime->addSymbol_(moduleName); + auto module = runtime->sendLocal_to_with_("load:", kernelModule, moduleNameSym); + GCedRef moduleRef(module); + auto args = std::vector(); for (int i = 0; i < argc; i++) args.push_back((Object*)runtime->newString_(argv[i])); - - start(runtime, kernel, args); + auto array = runtime->newArray_(args); + runtime->sendLocal_to_with_("main:", moduleRef.get(), (Object*)array); + return 0; } diff --git a/runtime/cpp/Loader.cpp b/runtime/cpp/Loader.cpp new file mode 100644 index 00000000..90958af5 --- /dev/null +++ b/runtime/cpp/Loader.cpp @@ -0,0 +1,230 @@ +/* + Copyright (c) 2019-2025 Javier Pimás, Jan Vrany, Labware. + See (MIT) license in root directory. + */ + +#include "Loader.h" +#include "Bootstrap/Bootstrapper.h" +#include "Bootstrap/SourceModuleLoader.h" +#include "ImageSegment.h" +#include + +namespace Egg { + +Loader::Loader(const std::string& modulesDir) + : _modulesDir(modulesDir), _runtime(nullptr), _kernel(nullptr) { +} + +Loader::~Loader() { +} + +std::string Loader::findModulesDir_() { + namespace fs = std::filesystem; + if (fs::exists(_modulesDir)) + return _modulesDir; + + std::vector prefixes = { + "../../../../", "../../../", "../../", "../", "./" + }; + for (const auto& prefix : prefixes) { + auto candidate = prefix + _modulesDir; + if (fs::exists(candidate)) + return candidate; + } + return ""; +} + +bool Loader::hasEmsFile_(const std::string& name) { + auto searched = "image-segments/" + name + ".ems"; + for (const auto& dir : {"./", "../"}) { + if (std::filesystem::exists(std::filesystem::path(dir) / searched)) + return true; + } + return false; +} + +bool Loader::hasSourceDir_(const std::string& name) { + namespace fs = std::filesystem; + auto modulesRoot = findModulesDir_(); + if (modulesRoot.empty()) return false; + auto moduleDir = fs::path(modulesRoot) / name; + return fs::exists(moduleDir) && fs::is_directory(moduleDir); +} + +Runtime* Loader::loadKernel() { + namespace fs = std::filesystem; + auto modulesRoot = findModulesDir_(); + + auto kernelPath = (fs::path(modulesRoot) / "Kernel").string(); + if (!fs::exists(kernelPath)) { + std::cerr << "Cannot bootstrap: Kernel module source directory not found at " + << kernelPath << std::endl; + return nullptr; + } + + Bootstrapper bootstrapper(kernelPath, this); + _runtime = bootstrapper.bootstrap(); + _kernel = _runtime->_kernel; + + // Register the kernel module as loaded so Kernel load: #Kernel works + _loadedModules["Kernel"] = _kernel->_exports["__module__"]; + + return _runtime; +} + +HeapObject* Loader::loadModule_(const std::string& name) { + // 1. Already loaded? + auto it = _loadedModules.find(name); + if (it != _loadedModules.end()) + return it->second; + + HeapObject* module = nullptr; + + // 2. .ems file available? + if (hasEmsFile_(name)) { + auto imageSegment = _segments.count(name) ? _segments[name] : loadModuleFromFile(name + ".ems"); + module = imageSegment->_exports["__module__"]; + } + // 3. Source directory available? + else if (hasSourceDir_(name)) { + namespace fs = std::filesystem; + auto modulesRoot = findModulesDir_(); + auto modulePath = (fs::path(modulesRoot) / name).string(); + SourceModuleLoader sourceLoader(_runtime); + module = sourceLoader.loadModuleFromSource(modulePath); + } + else { + error(("Module not found: " + name).c_str()); + return nullptr; + } + + _loadedModules[name] = module; + return module; +} + +// .ems loading support methods + +ImageSegment* Loader::loadModuleFromFile(const std::string &filename) { + auto filepath = this->findInPath(filename); + auto stream = std::ifstream(filepath, std::ios::binary); + auto imageSegment = new ImageSegment(&stream); + std::vector imports; + this->bindModuleImports(imageSegment, imports); + imageSegment->fixPointerSlots(imports); + this->_runtime->addSegmentSpace_(imageSegment); + return imageSegment; +} + +void Loader::bindModuleImports(ImageSegment *imageSegment, std::vector &imports) { + for (size_t i = 0; i < imageSegment->_importDescriptors.size(); i++) { + imports.push_back(this->bindModuleImport(imageSegment, imageSegment->_importDescriptors[i])); + } +} + +Object* Loader::bindModuleImport(ImageSegment* imageSegment, std::vector &descriptor) { + auto linker = this->importStringAt_(imageSegment, descriptor[0]); + HeapObject *token; + if (descriptor.size() == 1) + token = this->_kernel->_exports["nil"]; + else if (descriptor.size() == 2) + token = this->importStringAt_(imageSegment, descriptor[1]); + else { + std::vector array; + for (size_t i = 1; i < descriptor.size(); i++) + array.push_back(this->importStringAt_(imageSegment, descriptor[i])); + token = this->transferArray(array); + } + + auto ref = this->_runtime->sendLocal_to_with_with_("linker:token:", (Object*)this->_kernel->_exports["SymbolicReference"], (Object*)linker, (Object*)token); + return this->_runtime->sendLocal_to_("link", ref); +} + +HeapObject* Loader::importStringAt_(ImageSegment* imageSegment, uint32_t index) { + return this->transferSymbol(imageSegment->importStringAt_(index)); +} + +HeapObject* Loader::transferSymbol(std::string &str) { + return this->_runtime->addSymbol_(str); +} + +HeapObject* Loader::transferArray(std::vector &array) { + return this->_runtime->newArray_(array); +} + +HeapObject* Loader::transferArray(std::vector &array) { + return this->_runtime->newArray_(array); +} + +std::filesystem::path Loader::findInPath(const std::string &filename) { + std::vector dirs({"./", "../"}); + auto searched = "image-segments/" + filename; + for (auto& dir : dirs) { + auto filePath = std::filesystem::path(dir) / searched; + if (std::filesystem::exists(filePath)) + return filePath; + } + + auto str = std::string("could not find module snapshot file ") + filename; + error(str.c_str()); + std::terminate(); +} + +// Bare testing support + +ImageSegment* Loader::bareLoadModuleFromFile(const std::string &filename) { + auto filepath = this->findInPath(filename); + auto stream = std::ifstream(filepath); + auto imageSegment = new ImageSegment(&stream); + std::vector imports; + for (size_t i = 0; i < imageSegment->_importDescriptors.size(); i++) { + std::vector &descriptor = imageSegment->_importDescriptors[i]; + auto import = this->bareBindModuleImport(imageSegment, descriptor); + std::cout << "import " << i << " is: " << import->printString() << std::endl; + imports.push_back(import); + } + imageSegment->fixPointerSlots(imports); + return imageSegment; +} + +Object* Loader::bareBindModuleImport(ImageSegment* imageSegment, std::vector &descriptor) { + auto linker = imageSegment->importStringAt_(descriptor[0]); + std::vector tokens; + for (size_t i = 1; i < descriptor.size(); i++) + tokens.push_back(imageSegment->importStringAt_(descriptor[i])); + + if (linker == "asSymbol") { + auto symbol = _runtime->existingSymbolFrom_(tokens[0]); + if (symbol == nullptr) { + symbol = (Object*)_runtime->newString_(tokens[0]); + } + return (Object*)symbol; + } + if (linker == "nil") + return (Object*)_runtime->_nilObj; + if (linker == "true") + return (Object*)_runtime->_trueObj; + if (linker == "false") + return (Object*)_runtime->_falseObj; + if (linker == "asClass") + return (Object*)_kernel->_exports[tokens[1]]; + if (linker == "asMetaclass") + return (Object*)_runtime->speciesOf_((Object*)_kernel->_exports[tokens[1]]); + if (linker == "asBehavior") + return (Object*)_runtime->speciesInstanceBehavior_(_kernel->_exports[tokens[1]]); + if (linker == "asMetaclassBehavior") + return (Object*)_runtime->speciesInstanceBehavior_(_runtime->speciesOf_((Object*)_kernel->_exports[tokens[1]])); + if (linker == "asModule") + return (Object*)_kernel->_exports["__module__"]; + if (linker == "symbolTable") + return (Object*)_kernel->_exports["SymbolTable"]; + if (linker == "nilToken") { + auto hashTable = _kernel->_exports["HashTable"]; + auto symbol = _runtime->existingSymbolFrom_("NilToken"); + auto binding = (SAssociationBinding*)_runtime->_evaluator->context()->staticBindingForCvar_in_(symbol, hashTable); + return binding->valueWithin_(_runtime->_evaluator->context()); + } + ASSERT(false); + std::terminate(); +} + +} // namespace Egg diff --git a/runtime/cpp/Loader.h b/runtime/cpp/Loader.h new file mode 100644 index 00000000..efef08a1 --- /dev/null +++ b/runtime/cpp/Loader.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2019-2025 Javier Pimás, Jan Vrany, Labware. + See (MIT) license in root directory. + */ + +#ifndef _LOADER_H_ +#define _LOADER_H_ + +#include +#include +#include +#include +#include +#include + +#include "Allocator/GCHeap.h" +#include "Allocator/GCSpace.h" +#include "Evaluator/Evaluator.h" +#include "Evaluator/SAssociationBinding.h" + +namespace Egg { + +class Loader { +public: + Runtime *_runtime; + ImageSegment *_kernel; + + Loader(const std::string& modulesDir); + ~Loader(); + + Runtime* loadKernel(); + HeapObject* loadModule_(const std::string& name); + + // .ems loading support + ImageSegment* loadModuleFromFile(const std::string &filename); + void bindModuleImports(ImageSegment *imageSegment, std::vector &imports); + Object* bindModuleImport(ImageSegment* imageSegment, std::vector &descriptor); + HeapObject* importStringAt_(ImageSegment* imageSegment, uint32_t index); + HeapObject* transferSymbol(std::string &str); + HeapObject* transferArray(std::vector &array); + HeapObject* transferArray(std::vector &array); + std::filesystem::path findInPath(const std::string &filename); + + // bare testing support + ImageSegment* bareLoadModuleFromFile(const std::string &filename); + Object* bareBindModuleImport(ImageSegment* imageSegment, std::vector &descriptor); + + std::string findModulesDir_(); + +private: + std::string _modulesDir; + std::map _loadedModules; + std::map _segments; + + bool hasEmsFile_(const std::string& name); + bool hasSourceDir_(const std::string& name); +}; + +} + +#endif // _LOADER_H_ diff --git a/runtime/cpp/SymbolProvider.cpp b/runtime/cpp/SymbolProvider.cpp new file mode 100644 index 00000000..33a99b38 --- /dev/null +++ b/runtime/cpp/SymbolProvider.cpp @@ -0,0 +1,67 @@ +#include "SymbolProvider.h" +#include "Evaluator/Runtime.h" +#include "Bootstrap/Bootstrapper.h" + +namespace Egg { + +Object* BootstrapSymbolProvider::symbolFor_(const Egg::string& name) { + auto it = _symbols.find(name); + if (it != _symbols.end()) + return it->second; + auto symbol = _bootstrapper->newSymbol_(name); + _symbols[name] = symbol; + return symbol; +} + +Object* BootstrapSymbolProvider::existingSymbolFor_(const Egg::string& name) { + auto it = _symbols.find(name); + return it != _symbols.end() ? it->second : nullptr; +} + +DynamicSymbolProvider::DynamicSymbolProvider(Runtime* runtime, HeapObject* symbolTable) + : _runtime(runtime), _symbolTable(symbolTable) {} + +Object* DynamicSymbolProvider::existingSymbolFor_(const Egg::string& name) { + auto it = _cache.find(name); + if (it != _cache.end()) + return it->second->get(); + + bool isLatin1 = name.isLatin1(); + const char* bytes; + if (isLatin1) { + bytes = name.toBytes(); + } else { + bytes = reinterpret_cast(name.c_str()); + } + + std::string bytesStr(bytes, isLatin1 ? name.size() : name.size() * 4); + + // Linear scan of symbol table (HashTable with 'policy' ivar at slot 1, elements from slot 2) + HeapObject* table = _symbolTable->slotAt_(2)->asHeapObject(); + for (int i = 2; i <= table->size(); i++) { + auto symbol = table->slotAt_(i); + if (symbol != (Object*)_runtime->_nilObj) { + if (symbol->asHeapObject()->sameBytesThan(bytesStr)) { + if (isLatin1) delete[] bytes; + _cache[name] = new GCedRef(symbol); + return symbol; + } + } + } + + if (isLatin1) delete[] bytes; + return nullptr; +} + +Object* DynamicSymbolProvider::symbolFor_(const Egg::string& name) { + auto existing = existingSymbolFor_(name); + if (existing) + return existing; + + auto stringObj = _runtime->newString_(name.toUtf8()); + auto result = _runtime->sendLocal_to_("asSymbol", (Object*)stringObj); + _cache[name] = new GCedRef(result); + return result; +} + +} diff --git a/runtime/cpp/SymbolProvider.h b/runtime/cpp/SymbolProvider.h new file mode 100644 index 00000000..fb21e168 --- /dev/null +++ b/runtime/cpp/SymbolProvider.h @@ -0,0 +1,51 @@ +#ifndef _SYMBOLPROVIDER_H_ +#define _SYMBOLPROVIDER_H_ + +#include +#include +#include "Egg.h" +#include "Utils/egg_string.h" +#include "GCedRef.h" + +namespace Egg { + +class Runtime; + +class SymbolProvider { +public: + virtual ~SymbolProvider() = default; + virtual Object* symbolFor_(const Egg::string& name) = 0; + virtual Object* existingSymbolFor_(const Egg::string& name) = 0; +}; + +class Bootstrapper; + +class BootstrapSymbolProvider : public SymbolProvider { + Bootstrapper* _bootstrapper; + std::map _symbols; +public: + BootstrapSymbolProvider(Bootstrapper* bootstrapper) : _bootstrapper(bootstrapper) {} + + Object* symbolFor_(const Egg::string& name) override; + Object* existingSymbolFor_(const Egg::string& name) override; + + std::map& symbols() { return _symbols; } +}; + +class DynamicSymbolProvider : public SymbolProvider { + Runtime* _runtime; + HeapObject* _symbolTable; + std::map _cache; +public: + DynamicSymbolProvider(Runtime* runtime, HeapObject* symbolTable); + + Object* symbolFor_(const Egg::string& name) override; + Object* existingSymbolFor_(const Egg::string& name) override; + + void symbolTable_(HeapObject* table) { _symbolTable = table; } + std::map& cache() { return _cache; } +}; + +} + +#endif // _SYMBOLPROVIDER_H_ From 4ad5a1f119b04351b177064232fc27c59ce766a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Wed, 18 Mar 2026 00:16:06 -0300 Subject: [PATCH 08/15] Rename egg::string to Egg::string and move egg_string.h to Utils/ Consolidate the string utility class under the Egg namespace to match the project convention. Move egg_string.h from Compiler/ to Utils/ and update all includes accordingly. Also update copyright years to 2025-2026 across affected files. --- runtime/cpp/Bootstrap/CodeSpecs.cpp | 12 ++-- runtime/cpp/Bootstrap/CodeSpecs.h | 48 +++++++------- runtime/cpp/Bootstrap/TonelReader.cpp | 64 +++++++++---------- runtime/cpp/Bootstrap/TonelReader.h | 20 +++--- runtime/cpp/Compiler/AST/SAssignmentNode.cpp | 2 +- runtime/cpp/Compiler/AST/SAssignmentNode.h | 2 +- runtime/cpp/Compiler/AST/SBlockNode.cpp | 2 +- runtime/cpp/Compiler/AST/SBlockNode.h | 2 +- runtime/cpp/Compiler/AST/SBraceNode.cpp | 2 +- runtime/cpp/Compiler/AST/SBraceNode.h | 2 +- .../cpp/Compiler/AST/SCascadeMessageNode.cpp | 2 +- .../cpp/Compiler/AST/SCascadeMessageNode.h | 2 +- runtime/cpp/Compiler/AST/SCascadeNode.cpp | 2 +- runtime/cpp/Compiler/AST/SCascadeNode.h | 2 +- runtime/cpp/Compiler/AST/SCommentNode.cpp | 2 +- runtime/cpp/Compiler/AST/SCommentNode.h | 8 +-- runtime/cpp/Compiler/AST/SIdentifierNode.cpp | 4 +- runtime/cpp/Compiler/AST/SIdentifierNode.h | 8 +-- runtime/cpp/Compiler/AST/SLiteralNode.cpp | 2 +- runtime/cpp/Compiler/AST/SLiteralNode.h | 4 +- runtime/cpp/Compiler/AST/SMessageNode.cpp | 2 +- runtime/cpp/Compiler/AST/SMessageNode.h | 2 +- runtime/cpp/Compiler/AST/SMethodNode.cpp | 6 +- runtime/cpp/Compiler/AST/SMethodNode.h | 4 +- runtime/cpp/Compiler/AST/SNumberNode.cpp | 2 +- runtime/cpp/Compiler/AST/SNumberNode.h | 2 +- runtime/cpp/Compiler/AST/SParseNode.cpp | 10 +-- runtime/cpp/Compiler/AST/SParseNode.h | 14 ++-- runtime/cpp/Compiler/AST/SParseNodeVisitor.h | 2 +- runtime/cpp/Compiler/AST/SPragmaNode.cpp | 2 +- runtime/cpp/Compiler/AST/SPragmaNode.h | 14 ++-- runtime/cpp/Compiler/AST/SReturnNode.cpp | 2 +- runtime/cpp/Compiler/AST/SReturnNode.h | 2 +- runtime/cpp/Compiler/AST/SScriptNode.cpp | 2 +- runtime/cpp/Compiler/AST/SScriptNode.h | 2 +- runtime/cpp/Compiler/AST/SSelectorNode.cpp | 6 +- runtime/cpp/Compiler/AST/SSelectorNode.h | 10 +-- runtime/cpp/Compiler/AST/SStringNode.cpp | 2 +- runtime/cpp/Compiler/AST/SStringNode.h | 2 +- runtime/cpp/Compiler/Backend/SCompiledBlock.h | 2 +- .../cpp/Compiler/Backend/SCompiledMethod.cpp | 2 +- .../cpp/Compiler/Backend/SCompiledMethod.h | 14 ++-- .../cpp/Compiler/Binding/ArgumentBinding.h | 4 +- .../Compiler/Binding/ArgumentEnvironment.h | 2 +- .../cpp/Compiler/Binding/ArrayEnvironment.h | 2 +- runtime/cpp/Compiler/Binding/Binding.h | 8 +-- runtime/cpp/Compiler/Binding/BlockScope.cpp | 14 ++-- runtime/cpp/Compiler/Binding/BlockScope.h | 12 ++-- runtime/cpp/Compiler/Binding/ClassBinding.h | 4 +- .../cpp/Compiler/Binding/ConstantBinding.h | 4 +- runtime/cpp/Compiler/Binding/FieldBinding.h | 4 +- runtime/cpp/Compiler/Binding/GlobalBinding.h | 4 +- runtime/cpp/Compiler/Binding/LocalBinding.cpp | 2 +- runtime/cpp/Compiler/Binding/LocalBinding.h | 4 +- .../cpp/Compiler/Binding/LocalEnvironment.h | 2 +- runtime/cpp/Compiler/Binding/MethodBinding.h | 4 +- runtime/cpp/Compiler/Binding/MethodScope.cpp | 10 +-- runtime/cpp/Compiler/Binding/MethodScope.h | 12 ++-- .../Binding/PseudoVariableBindings.cpp | 12 ++-- .../Compiler/Binding/PseudoVariableBindings.h | 16 ++--- runtime/cpp/Compiler/Binding/Scope.h | 6 +- runtime/cpp/Compiler/Binding/ScriptScope.cpp | 16 ++--- runtime/cpp/Compiler/Binding/ScriptScope.h | 26 ++++---- .../cpp/Compiler/Binding/StackEnvironment.h | 2 +- .../cpp/Compiler/Binding/TemporaryBinding.h | 4 +- runtime/cpp/Compiler/CompilationError.cpp | 8 +-- runtime/cpp/Compiler/CompilationError.h | 14 ++-- runtime/cpp/Compiler/CompilationResult.h | 2 +- runtime/cpp/Compiler/CompilerTypes.h | 2 +- runtime/cpp/Compiler/LiteralValue.h | 22 +++---- runtime/cpp/Compiler/MessageInliner.cpp | 18 +++--- runtime/cpp/Compiler/MessageInliner.h | 2 +- .../cpp/Compiler/Parser/SSmalltalkParser.cpp | 20 +++--- .../cpp/Compiler/Parser/SSmalltalkParser.h | 2 +- .../cpp/Compiler/Parser/SSmalltalkScanner.cpp | 34 +++++----- .../cpp/Compiler/Parser/SSmalltalkScanner.h | 14 ++-- runtime/cpp/Compiler/Parser/SToken.h | 34 +++++----- runtime/cpp/Compiler/Parser/Stream.h | 16 ++--- runtime/cpp/Compiler/SCompiler.cpp | 8 +-- runtime/cpp/Compiler/SCompiler.h | 2 +- runtime/cpp/Compiler/SSmalltalkCompiler.cpp | 20 +++--- runtime/cpp/Compiler/SSmalltalkCompiler.h | 24 +++---- runtime/cpp/Compiler/SemanticVisitor.cpp | 2 +- runtime/cpp/Compiler/SemanticVisitor.h | 2 +- runtime/cpp/Compiler/Stretch.h | 2 +- runtime/cpp/Compiler/TreecodeEncoder.cpp | 14 ++-- runtime/cpp/Compiler/TreecodeEncoder.h | 8 +-- runtime/cpp/{Compiler => Utils}/egg_string.h | 30 +++++++-- 88 files changed, 394 insertions(+), 378 deletions(-) rename runtime/cpp/{Compiler => Utils}/egg_string.h (93%) diff --git a/runtime/cpp/Bootstrap/CodeSpecs.cpp b/runtime/cpp/Bootstrap/CodeSpecs.cpp index 6614434f..bd51985d 100644 --- a/runtime/cpp/Bootstrap/CodeSpecs.cpp +++ b/runtime/cpp/Bootstrap/CodeSpecs.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -22,7 +22,7 @@ void ModuleSpec::addClass(ClassSpec* cls) { cls->module(this); } -ClassSpec* ModuleSpec::resolveClass(const egg::string& name) const { +ClassSpec* ModuleSpec::resolveClass(const Egg::string& name) const { auto it = _classes.find(name); return (it != _classes.end()) ? it->second : nullptr; } @@ -37,8 +37,8 @@ ClassSpec* ClassSpec::superclass() const { // ---- MetaclassSpec ---- -const egg::string& MetaclassSpec::name() const { - static const egg::string empty; +const Egg::string& MetaclassSpec::name() const { + static const Egg::string empty; if (!_instanceClass) return empty; return _instanceClass->name(); } @@ -60,13 +60,13 @@ uint32_t SpeciesSpec::instSize() const { return count; } -std::vector SpeciesSpec::allInstVarNames() const { +std::vector SpeciesSpec::allInstVarNames() const { std::vector chain; for (auto s = this; s != nullptr; s = static_cast(s->superclass())) chain.push_back(s); std::reverse(chain.begin(), chain.end()); - std::vector result; + std::vector result; for (auto s : chain) for (auto& iv : s->instVarNames()) result.push_back(iv); diff --git a/runtime/cpp/Bootstrap/CodeSpecs.h b/runtime/cpp/Bootstrap/CodeSpecs.h index dd03dd71..bb77daa3 100644 --- a/runtime/cpp/Bootstrap/CodeSpecs.h +++ b/runtime/cpp/Bootstrap/CodeSpecs.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. C++ port of modules/CodeSpecs/*.st — only the subset needed @@ -12,7 +12,7 @@ #include #include #include -#include "../Compiler/egg_string.h" +#include "Utils/egg_string.h" namespace Egg { @@ -26,13 +26,13 @@ class ModuleSpec; class MethodSpec { public: MethodSpec() = default; - MethodSpec(const egg::string& source) : _source(source) {} + MethodSpec(const Egg::string& source) : _source(source) {} - const egg::string& source() const { return _source; } - void source(const egg::string& s) { _source = s; } + const Egg::string& source() const { return _source; } + void source(const Egg::string& s) { _source = s; } private: - egg::string _source; + Egg::string _source; }; // ---- SpeciesSpec (base for ClassSpec and MetaclassSpec) ---- @@ -48,8 +48,8 @@ class SpeciesSpec { SpeciesSpec() : _format(0), _module(nullptr) {} virtual ~SpeciesSpec() = default; - const std::vector& instVarNames() const { return _instanceVariables; } - void instVarNames(const std::vector& ivars) { _instanceVariables = ivars; } + const std::vector& instVarNames() const { return _instanceVariables; } + void instVarNames(const std::vector& ivars) { _instanceVariables = ivars; } int format() const { return _format; } void beArrayed() { _format |= IsArrayed; } void beBytes() { _format |= IsBytes; } @@ -62,14 +62,14 @@ class SpeciesSpec { ModuleSpec* module() const { return _module; } void module(ModuleSpec* m) { _module = m; } - virtual const egg::string& name() const = 0; + virtual const Egg::string& name() const = 0; virtual ClassSpec* superclass() const = 0; uint32_t instSize() const; - std::vector allInstVarNames() const; + std::vector allInstVarNames() const; protected: - std::vector _instanceVariables; + std::vector _instanceVariables; std::vector _methods; int _format; ModuleSpec* _module; @@ -85,7 +85,7 @@ class MetaclassSpec : public SpeciesSpec { ClassSpec* instanceClass() const { return _instanceClass; } void instanceClass(ClassSpec* cls) { _instanceClass = cls; } - const egg::string& name() const override; + const Egg::string& name() const override; ClassSpec* superclass() const override; private: @@ -99,11 +99,11 @@ class ClassSpec : public SpeciesSpec { public: ClassSpec() : _metaclass(nullptr), _variable(false), _pointers(true) {} - const egg::string& name() const override { return _name; } - void name(const egg::string& n) { _name = n; } + const Egg::string& name() const override { return _name; } + void name(const Egg::string& n) { _name = n; } - const egg::string& supername() const { return _supername; } - void supername(const egg::string& s) { _supername = s; } + const Egg::string& supername() const { return _supername; } + void supername(const Egg::string& s) { _supername = s; } ClassSpec* superclass() const override; @@ -115,16 +115,16 @@ class ClassSpec : public SpeciesSpec { bool isPointers() const { return _pointers; } void isPointers(bool p) { _pointers = p; } - const std::vector& classVarNames() const { return _classVarNames; } - void classVarNames(const std::vector& cvars) { _classVarNames = cvars; } + const std::vector& classVarNames() const { return _classVarNames; } + void classVarNames(const std::vector& cvars) { _classVarNames = cvars; } private: - egg::string _name; - egg::string _supername; + Egg::string _name; + Egg::string _supername; MetaclassSpec* _metaclass; bool _variable; bool _pointers; - std::vector _classVarNames; + std::vector _classVarNames; }; // ---- ModuleSpec (minimal) ---- @@ -136,11 +136,11 @@ class ModuleSpec { ~ModuleSpec(); void addClass(ClassSpec* cls); - ClassSpec* resolveClass(const egg::string& name) const; - const std::map& classes() const { return _classes; } + ClassSpec* resolveClass(const Egg::string& name) const; + const std::map& classes() const { return _classes; } private: - std::map _classes; + std::map _classes; }; } // namespace Egg diff --git a/runtime/cpp/Bootstrap/TonelReader.cpp b/runtime/cpp/Bootstrap/TonelReader.cpp index f1c39aa3..bd3d287f 100644 --- a/runtime/cpp/Bootstrap/TonelReader.cpp +++ b/runtime/cpp/Bootstrap/TonelReader.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. Stream-based Tonel reader modelled after modules/Tonel/TonelReader.st. @@ -38,11 +38,11 @@ void TonelReader::skipLine() { // ── Tonel structure ────────────────────────────────────────────────── ClassSpec* TonelReader::parseFile(const std::string& utf8Source) { - _source = egg::string(utf8Source); + _source = Egg::string(utf8Source); _pos = 0; readComments(); - egg::string type = readType(); + Egg::string type = readType(); auto fields = readDefinition(); auto* spec = new ClassSpec(); @@ -50,14 +50,14 @@ ClassSpec* TonelReader::parseFile(const std::string& utf8Source) { meta->instanceClass(spec); spec->metaclass(meta); - spec->name(fields.count("name") ? fields["name"] : egg::string("")); + spec->name(fields.count("name") ? fields["name"] : Egg::string("")); if (type == "Class") { - spec->supername(fields.count("superclass") ? fields["superclass"] : egg::string("")); + spec->supername(fields.count("superclass") ? fields["superclass"] : Egg::string("")); } // #type field: #variable (pointer-indexed), #bytes (byte-indexed) if (fields.count("type")) { - egg::string t = fields["type"]; + Egg::string t = fields["type"]; if (t == "variable") { spec->isVariable(true); spec->isPointers(true); @@ -69,13 +69,13 @@ ClassSpec* TonelReader::parseFile(const std::string& utf8Source) { // instVars / classVars / classInstVars are parsed from the STON array // already stored as comma-separated names during parseSTONArray() - auto splitNames = [](const egg::string& csv) -> std::vector { - std::vector out; + auto splitNames = [](const Egg::string& csv) -> std::vector { + std::vector out; if (csv.empty()) return out; size_t start = 0; while (start < csv.length()) { size_t comma = csv.find(U',', start); - if (comma == egg::string::npos) comma = csv.length(); + if (comma == Egg::string::npos) comma = csv.length(); if (comma > start) out.push_back(csv.substr(start, comma - start)); start = comma + 1; @@ -104,7 +104,7 @@ void TonelReader::readComments() { } // Mirrors TonelReader >> readType (via ReadStream >> nextWordOrNumber) -egg::string TonelReader::readType() { +Egg::string TonelReader::readType() { skipSeparators(); size_t start = _pos; while (!atEnd() && ((_source[_pos] >= U'A' && _source[_pos] <= U'Z') || @@ -115,7 +115,7 @@ egg::string TonelReader::readType() { } // Mirrors TonelReader >> readDefinition (parses STON map) -std::map TonelReader::readDefinition() { +std::map TonelReader::readDefinition() { skipSeparators(); return parseSTONMap(); } @@ -138,10 +138,10 @@ void TonelReader::readMethod(ClassSpec* spec, MetaclassSpec* meta) { // 2. ClassName [class] >> selector...signature [ skipSeparators(); // Read up to " >> " - egg::string className; + Egg::string className; { - size_t sepPos = _source.find(egg::string(" >> "), _pos); - if (sepPos == egg::string::npos) return; + size_t sepPos = _source.find(Egg::string(" >> "), _pos); + if (sepPos == Egg::string::npos) return; className = _source.substr(_pos, sepPos - _pos); _pos = sepPos + 4; // skip " >> " } @@ -152,17 +152,17 @@ void TonelReader::readMethod(ClassSpec* spec, MetaclassSpec* meta) { bool isClassSide = false; if (className.length() > 6) { - egg::string tail = className.substr(className.length() - 5, 5); + Egg::string tail = className.substr(className.length() - 5, 5); if (tail == "class" && (className.length() == 5 || className[className.length() - 6] == U' ')) isClassSide = true; } // 3. Read signature up to [ - egg::string signature; + Egg::string signature; { size_t bracketPos = _source.find(U'[', _pos); - if (bracketPos == egg::string::npos) return; + if (bracketPos == Egg::string::npos) return; signature = _source.substr(_pos, bracketPos - _pos); _pos = bracketPos + 1; // skip '[' } @@ -175,10 +175,10 @@ void TonelReader::readMethod(ClassSpec* spec, MetaclassSpec* meta) { signature.erase(signature.begin()); // 4. Read body via nextBlock (we already consumed the '[') - egg::string body = nextBlock(); + Egg::string body = nextBlock(); // 5. Build method source = signature \n\t body - egg::string methodSource = signature + "\n\t" + body; + Egg::string methodSource = signature + "\n\t" + body; if (isClassSide) meta->addMethod(MethodSpec(methodSource)); @@ -191,7 +191,7 @@ void TonelReader::readMethod(ClassSpec* spec, MetaclassSpec* meta) { // Called after the opening '[' has already been consumed. // Reads until the matching ']', handling nesting and literals. -egg::string TonelReader::nextBlock() { +Egg::string TonelReader::nextBlock() { // Skip the rest of the line that contained '[' size_t bodyStart = _pos; while (bodyStart < _source.length() && @@ -224,7 +224,7 @@ egg::string TonelReader::nextBlock() { _source[bodyEnd - 1] == U'\t')) bodyEnd--; - if (bodyEnd <= bodyStart) return egg::string(""); + if (bodyEnd <= bodyStart) return Egg::string(""); return _source.substr(bodyStart, bodyEnd - bodyStart); } @@ -258,8 +258,8 @@ void TonelReader::skipToMatch(char32_t ch) { // ── Minimal STON parser ────────────────────────────────────────────── // Just enough to parse Tonel definition headers and method metadata. -std::map TonelReader::parseSTONMap() { - std::map map; +std::map TonelReader::parseSTONMap() { + std::map map; skipSeparators(); if (atEnd() || peek() != U'{') return map; next(); // skip '{' @@ -268,11 +268,11 @@ std::map TonelReader::parseSTONMap() { skipSeparators(); if (atEnd() || peek() == U'}') { if (!atEnd()) next(); break; } - egg::string key = parseSTONValue(); + Egg::string key = parseSTONValue(); skipSeparators(); if (!atEnd() && peek() == U':') next(); // skip ':' skipSeparators(); - egg::string value = parseSTONValue(); + Egg::string value = parseSTONValue(); map[key] = value; @@ -282,7 +282,7 @@ std::map TonelReader::parseSTONMap() { return map; } -egg::string TonelReader::parseSTONValue() { +Egg::string TonelReader::parseSTONValue() { skipSeparators(); if (atEnd()) return ""; char32_t ch = peek(); @@ -291,7 +291,7 @@ egg::string TonelReader::parseSTONValue() { if (ch == U'[') { auto arr = parseSTONArray(); // Encode as comma-separated string for ClassSpec consumption - egg::string result; + Egg::string result; for (size_t i = 0; i < arr.size(); i++) { if (i > 0) result += ","; result += arr[i]; @@ -306,7 +306,7 @@ egg::string TonelReader::parseSTONValue() { return _source.substr(start, _pos - start); } -egg::string TonelReader::parseSTONSymbol() { +Egg::string TonelReader::parseSTONSymbol() { next(); // skip '#' if (!atEnd() && peek() == U'\'') return parseSTONString(); // #'foo bar' size_t start = _pos; @@ -317,9 +317,9 @@ egg::string TonelReader::parseSTONSymbol() { return _source.substr(start, _pos - start); } -egg::string TonelReader::parseSTONString() { +Egg::string TonelReader::parseSTONString() { next(); // skip opening ' - egg::string result; + Egg::string result; while (!atEnd()) { char32_t c = next(); if (c == U'\'') { @@ -336,8 +336,8 @@ egg::string TonelReader::parseSTONString() { return result; } -std::vector TonelReader::parseSTONArray() { - std::vector arr; +std::vector TonelReader::parseSTONArray() { + std::vector arr; next(); // skip '[' while (true) { skipSeparators(); diff --git a/runtime/cpp/Bootstrap/TonelReader.h b/runtime/cpp/Bootstrap/TonelReader.h index 43a6a93b..ddee7b7a 100644 --- a/runtime/cpp/Bootstrap/TonelReader.h +++ b/runtime/cpp/Bootstrap/TonelReader.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. Stream-based Tonel reader modelled after modules/Tonel/TonelReader.st. @@ -33,26 +33,26 @@ class TonelReader { // Tonel structure (mirrors TonelReader.st >> read) void readComments(); - egg::string readType(); - std::map readDefinition(); + Egg::string readType(); + std::map readDefinition(); void readMethods(ClassSpec* spec, MetaclassSpec* meta); void readMethod(ClassSpec* spec, MetaclassSpec* meta); // Block / literal skipping (mirrors TonelReader.st) - egg::string nextBlock(); + Egg::string nextBlock(); void skipString(); void skipComment(); void skipToMatch(char32_t ch); // Minimal STON helpers - std::map parseSTONMap(); - egg::string parseSTONValue(); - egg::string parseSTONSymbol(); - egg::string parseSTONString(); - std::vector parseSTONArray(); + std::map parseSTONMap(); + Egg::string parseSTONValue(); + Egg::string parseSTONSymbol(); + Egg::string parseSTONString(); + std::vector parseSTONArray(); // State - egg::string _source; + Egg::string _source; size_t _pos = 0; }; diff --git a/runtime/cpp/Compiler/AST/SAssignmentNode.cpp b/runtime/cpp/Compiler/AST/SAssignmentNode.cpp index 0733c87a..35d4580d 100644 --- a/runtime/cpp/Compiler/AST/SAssignmentNode.cpp +++ b/runtime/cpp/Compiler/AST/SAssignmentNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SAssignmentNode.h b/runtime/cpp/Compiler/AST/SAssignmentNode.h index 91eb5b3f..0b48c304 100644 --- a/runtime/cpp/Compiler/AST/SAssignmentNode.h +++ b/runtime/cpp/Compiler/AST/SAssignmentNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SBlockNode.cpp b/runtime/cpp/Compiler/AST/SBlockNode.cpp index 7ec9849a..80e82477 100644 --- a/runtime/cpp/Compiler/AST/SBlockNode.cpp +++ b/runtime/cpp/Compiler/AST/SBlockNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SBlockNode.h b/runtime/cpp/Compiler/AST/SBlockNode.h index 95b434ba..1bfb9a3b 100644 --- a/runtime/cpp/Compiler/AST/SBlockNode.h +++ b/runtime/cpp/Compiler/AST/SBlockNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SBraceNode.cpp b/runtime/cpp/Compiler/AST/SBraceNode.cpp index 765dd75c..83b0de7d 100644 --- a/runtime/cpp/Compiler/AST/SBraceNode.cpp +++ b/runtime/cpp/Compiler/AST/SBraceNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SBraceNode.h b/runtime/cpp/Compiler/AST/SBraceNode.h index 85232783..069405b1 100644 --- a/runtime/cpp/Compiler/AST/SBraceNode.h +++ b/runtime/cpp/Compiler/AST/SBraceNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SCascadeMessageNode.cpp b/runtime/cpp/Compiler/AST/SCascadeMessageNode.cpp index 83a5574a..b0b81089 100644 --- a/runtime/cpp/Compiler/AST/SCascadeMessageNode.cpp +++ b/runtime/cpp/Compiler/AST/SCascadeMessageNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SCascadeMessageNode.h b/runtime/cpp/Compiler/AST/SCascadeMessageNode.h index 8539da83..d6c71cba 100644 --- a/runtime/cpp/Compiler/AST/SCascadeMessageNode.h +++ b/runtime/cpp/Compiler/AST/SCascadeMessageNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SCascadeNode.cpp b/runtime/cpp/Compiler/AST/SCascadeNode.cpp index 4600a19a..51b2df93 100644 --- a/runtime/cpp/Compiler/AST/SCascadeNode.cpp +++ b/runtime/cpp/Compiler/AST/SCascadeNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SCascadeNode.h b/runtime/cpp/Compiler/AST/SCascadeNode.h index 12b78e8a..ef7030bb 100644 --- a/runtime/cpp/Compiler/AST/SCascadeNode.h +++ b/runtime/cpp/Compiler/AST/SCascadeNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SCommentNode.cpp b/runtime/cpp/Compiler/AST/SCommentNode.cpp index 9949328f..04a0dd72 100644 --- a/runtime/cpp/Compiler/AST/SCommentNode.cpp +++ b/runtime/cpp/Compiler/AST/SCommentNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SCommentNode.h b/runtime/cpp/Compiler/AST/SCommentNode.h index ca860c2f..d7764010 100644 --- a/runtime/cpp/Compiler/AST/SCommentNode.h +++ b/runtime/cpp/Compiler/AST/SCommentNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -17,7 +17,7 @@ namespace Egg { */ class SCommentNode : public SParseNode { private: - egg::string _value; + Egg::string _value; public: SCommentNode(SSmalltalkCompiler* compiler); @@ -25,8 +25,8 @@ class SCommentNode : public SParseNode { void acceptVisitor_(SParseNodeVisitor* visitor) override; - egg::string value() const { return _value; } - void value_(const egg::string& aString) { _value = aString; } + Egg::string value() const { return _value; } + void value_(const Egg::string& aString) { _value = aString; } bool isComment() const override { return true; } }; diff --git a/runtime/cpp/Compiler/AST/SIdentifierNode.cpp b/runtime/cpp/Compiler/AST/SIdentifierNode.cpp index bfd6e5d1..c48c806d 100644 --- a/runtime/cpp/Compiler/AST/SIdentifierNode.cpp +++ b/runtime/cpp/Compiler/AST/SIdentifierNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -65,7 +65,7 @@ void SIdentifierNode::beAssigned() { void SIdentifierNode::checkLowercase() { if (!_name.empty()) { char32_t first = _name[0]; - if (egg::isLetter(first) && egg::isUppercase(first)) { + if (Egg::isLetter(first) && Egg::isUppercase(first)) { std::cerr << "Warning: variable '" << _name << "' should start with a lowercase letter." << std::endl; } } diff --git a/runtime/cpp/Compiler/AST/SIdentifierNode.h b/runtime/cpp/Compiler/AST/SIdentifierNode.h index f72f3754..7f54997f 100644 --- a/runtime/cpp/Compiler/AST/SIdentifierNode.h +++ b/runtime/cpp/Compiler/AST/SIdentifierNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -19,7 +19,7 @@ class Binding; */ class SIdentifierNode : public SParseNode { private: - egg::string _name; + Egg::string _name; Binding* _binding; bool _assigned = false; @@ -29,8 +29,8 @@ class SIdentifierNode : public SParseNode { void acceptVisitor_(SParseNodeVisitor* visitor) override; - egg::string name() const { return _name; } - void name_(const egg::string& n) { _name = n; } + Egg::string name() const { return _name; } + void name_(const Egg::string& n) { _name = n; } Binding* binding() const { return _binding; } void binding_(Binding* b) { _binding = b; } diff --git a/runtime/cpp/Compiler/AST/SLiteralNode.cpp b/runtime/cpp/Compiler/AST/SLiteralNode.cpp index f0db7f9f..64106626 100644 --- a/runtime/cpp/Compiler/AST/SLiteralNode.cpp +++ b/runtime/cpp/Compiler/AST/SLiteralNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SLiteralNode.h b/runtime/cpp/Compiler/AST/SLiteralNode.h index 95d3bda0..580a8de4 100644 --- a/runtime/cpp/Compiler/AST/SLiteralNode.h +++ b/runtime/cpp/Compiler/AST/SLiteralNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -32,7 +32,7 @@ class SLiteralNode : public SParseNode { void literalValue_(LiteralValue&& v) { _litValue = std::move(v); } // Backwards‑compatible string accessors - egg::string value() const { return _litValue.printString(); } + Egg::string value() const { return _litValue.printString(); } void value_(const LiteralValue& v) { _litValue = v; } void beSymbol_() { _litValue.tag = LiteralValue::Symbol; } diff --git a/runtime/cpp/Compiler/AST/SMessageNode.cpp b/runtime/cpp/Compiler/AST/SMessageNode.cpp index 586e5c6e..cd059ce1 100644 --- a/runtime/cpp/Compiler/AST/SMessageNode.cpp +++ b/runtime/cpp/Compiler/AST/SMessageNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SMessageNode.h b/runtime/cpp/Compiler/AST/SMessageNode.h index 7811bb43..1202484f 100644 --- a/runtime/cpp/Compiler/AST/SMessageNode.h +++ b/runtime/cpp/Compiler/AST/SMessageNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SMethodNode.cpp b/runtime/cpp/Compiler/AST/SMethodNode.cpp index 4d6643fd..72f8e52d 100644 --- a/runtime/cpp/Compiler/AST/SMethodNode.cpp +++ b/runtime/cpp/Compiler/AST/SMethodNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -88,7 +88,7 @@ std::vector SMethodNode::literals() { // Add pragma name if used if (_pragma && _pragma->isUsed()) { - const egg::string& pragmaName = _pragma->name(); + const Egg::string& pragmaName = _pragma->name(); if (!pragmaName.empty()) { addUnique(LiteralValue::fromSymbol(pragmaName)); } @@ -191,7 +191,7 @@ bool SMethodNode::needsFrame() const { return false; } -egg::string SMethodNode::selectorString() const { +Egg::string SMethodNode::selectorString() const { return _selector ? _selector->symbol() : ""; } diff --git a/runtime/cpp/Compiler/AST/SMethodNode.h b/runtime/cpp/Compiler/AST/SMethodNode.h index 3ba21822..a52a5c07 100644 --- a/runtime/cpp/Compiler/AST/SMethodNode.h +++ b/runtime/cpp/Compiler/AST/SMethodNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -48,7 +48,7 @@ class SMethodNode : public SScriptNode { SCompiledMethod* methodClass(); bool needsEnvironment() const; bool needsFrame() const; - egg::string selectorString() const; + Egg::string selectorString() const; void nodesDo_(std::function block, bool includeDeclarations = false) override; }; diff --git a/runtime/cpp/Compiler/AST/SNumberNode.cpp b/runtime/cpp/Compiler/AST/SNumberNode.cpp index b4e04fab..2218f6b8 100644 --- a/runtime/cpp/Compiler/AST/SNumberNode.cpp +++ b/runtime/cpp/Compiler/AST/SNumberNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SNumberNode.h b/runtime/cpp/Compiler/AST/SNumberNode.h index e902b563..9310c8e4 100644 --- a/runtime/cpp/Compiler/AST/SNumberNode.h +++ b/runtime/cpp/Compiler/AST/SNumberNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SParseNode.cpp b/runtime/cpp/Compiler/AST/SParseNode.cpp index c13eec4a..6f6f291a 100644 --- a/runtime/cpp/Compiler/AST/SParseNode.cpp +++ b/runtime/cpp/Compiler/AST/SParseNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -25,7 +25,7 @@ SParseNode* SParseNode::nodesDetect_(std::function predicate, return found ? found : ifAbsent(); } -SParseNode* SParseNode::nodeWithLiteral_(const egg::string& value) { +SParseNode* SParseNode::nodeWithLiteral_(const Egg::string& value) { return nodesDetect_([ &value ](SParseNode* n) { @@ -33,7 +33,7 @@ SParseNode* SParseNode::nodeWithLiteral_(const egg::string& value) { }, []() { return nullptr; }); } -SParseNode* SParseNode::variableNamed_(const egg::string& name) { +SParseNode* SParseNode::variableNamed_(const Egg::string& name) { SParseNode* result = nullptr; allNodesDo_includingDeclarations_([&](SParseNode* node) { if (node->isIdentifier() && node->nameEquals_(name)) result = node; @@ -41,8 +41,8 @@ SParseNode* SParseNode::variableNamed_(const egg::string& name) { return result; } -bool SParseNode::valueEquals_(const egg::string&) const { return false; } -bool SParseNode::nameEquals_(const egg::string&) const { return false; } +bool SParseNode::valueEquals_(const Egg::string&) const { return false; } +bool SParseNode::nameEquals_(const Egg::string&) const { return false; } bool SParseNode::isMethodArgument() const { return false; } bool SParseNode::isMethodTemporary() const { return false; } diff --git a/runtime/cpp/Compiler/AST/SParseNode.h b/runtime/cpp/Compiler/AST/SParseNode.h index 7098d94f..29b59ccd 100644 --- a/runtime/cpp/Compiler/AST/SParseNode.h +++ b/runtime/cpp/Compiler/AST/SParseNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -28,7 +28,7 @@ class SParseNode { protected: Stretch _position; SSmalltalkCompiler* _compiler; - std::vector _comments; + std::vector _comments; public: SParseNode(SSmalltalkCompiler* compiler) : _compiler(compiler) {} @@ -36,12 +36,12 @@ class SParseNode { virtual void allNodesDo_includingDeclarations_(std::function block); virtual SParseNode* nodesDetect_(std::function predicate, std::function ifAbsent); - virtual SParseNode* nodeWithLiteral_(const egg::string& value); - virtual SParseNode* variableNamed_(const egg::string& name); + virtual SParseNode* nodeWithLiteral_(const Egg::string& value); + virtual SParseNode* variableNamed_(const Egg::string& name); virtual bool isMethodArgument() const; virtual bool isMethodTemporary() const; - virtual bool valueEquals_(const egg::string&) const; - virtual bool nameEquals_(const egg::string&) const; + virtual bool valueEquals_(const Egg::string&) const; + virtual bool nameEquals_(const Egg::string&) const; virtual SParseNode* ast(); @@ -52,7 +52,7 @@ class SParseNode { SSmalltalkCompiler* compiler() const { return _compiler; } - void addComment_(const egg::string& comment) { + void addComment_(const Egg::string& comment) { _comments.push_back(comment); } diff --git a/runtime/cpp/Compiler/AST/SParseNodeVisitor.h b/runtime/cpp/Compiler/AST/SParseNodeVisitor.h index 404a7385..08ba631e 100644 --- a/runtime/cpp/Compiler/AST/SParseNodeVisitor.h +++ b/runtime/cpp/Compiler/AST/SParseNodeVisitor.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SPragmaNode.cpp b/runtime/cpp/Compiler/AST/SPragmaNode.cpp index fb82ca47..f505e187 100644 --- a/runtime/cpp/Compiler/AST/SPragmaNode.cpp +++ b/runtime/cpp/Compiler/AST/SPragmaNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SPragmaNode.h b/runtime/cpp/Compiler/AST/SPragmaNode.h index 4927f3d3..bb379bc5 100644 --- a/runtime/cpp/Compiler/AST/SPragmaNode.h +++ b/runtime/cpp/Compiler/AST/SPragmaNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -26,7 +26,7 @@ class SPragmaNode : public SParseNode { private: Type _type; - egg::string _name; + Egg::string _name; int _primitiveNumber; // For primitive pragmas void* _info; // For FFI descriptors (placeholder for now) @@ -40,8 +40,8 @@ class SPragmaNode : public SParseNode { Type type() const { return _type; } void type_(Type t) { _type = t; } - egg::string name() const { return _name; } - void name_(const egg::string& n) { _name = n; } + Egg::string name() const { return _name; } + void name_(const Egg::string& n) { _name = n; } int primitiveNumber() const { return _primitiveNumber; } void primitiveNumber_(int n) { _primitiveNumber = n; } @@ -55,19 +55,19 @@ class SPragmaNode : public SParseNode { bool isSymbolic() const { return _type == Type::Symbolic; } bool isUsed() const { return _type != Type::None; } - void bePrimitive_(int number, const egg::string& name) { + void bePrimitive_(int number, const Egg::string& name) { _type = Type::Primitive; _primitiveNumber = number; _name = name; } - void beFFI_(const egg::string& name, void* descriptor) { + void beFFI_(const Egg::string& name, void* descriptor) { _type = Type::FFI; _name = name; _info = descriptor; } - void beSymbolic_(const egg::string& symbol) { + void beSymbolic_(const Egg::string& symbol) { _type = Type::Symbolic; _name = symbol; } diff --git a/runtime/cpp/Compiler/AST/SReturnNode.cpp b/runtime/cpp/Compiler/AST/SReturnNode.cpp index eaa3f111..08a040fe 100644 --- a/runtime/cpp/Compiler/AST/SReturnNode.cpp +++ b/runtime/cpp/Compiler/AST/SReturnNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SReturnNode.h b/runtime/cpp/Compiler/AST/SReturnNode.h index f5079212..b3a5fae6 100644 --- a/runtime/cpp/Compiler/AST/SReturnNode.h +++ b/runtime/cpp/Compiler/AST/SReturnNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SScriptNode.cpp b/runtime/cpp/Compiler/AST/SScriptNode.cpp index 5fd6b929..a3b42186 100644 --- a/runtime/cpp/Compiler/AST/SScriptNode.cpp +++ b/runtime/cpp/Compiler/AST/SScriptNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SScriptNode.h b/runtime/cpp/Compiler/AST/SScriptNode.h index 2f69b5c9..c34b2199 100644 --- a/runtime/cpp/Compiler/AST/SScriptNode.h +++ b/runtime/cpp/Compiler/AST/SScriptNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SSelectorNode.cpp b/runtime/cpp/Compiler/AST/SSelectorNode.cpp index 38c84fc0..b480a9b8 100644 --- a/runtime/cpp/Compiler/AST/SSelectorNode.cpp +++ b/runtime/cpp/Compiler/AST/SSelectorNode.cpp @@ -1,11 +1,11 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #include "SSelectorNode.h" #include "SParseNodeVisitor.h" -#include "../egg_string.h" +#include "Utils/egg_string.h" namespace Egg { @@ -17,7 +17,7 @@ bool SSelectorNode::isBinary() const { if (_symbol.empty()) return false; char32_t first = _symbol[0]; - return !egg::isLetter(first) && !egg::isDigit(first) && first != '_' && first != ':'; + return !Egg::isLetter(first) && !Egg::isDigit(first) && first != '_' && first != ':'; } } // namespace Egg diff --git a/runtime/cpp/Compiler/AST/SSelectorNode.h b/runtime/cpp/Compiler/AST/SSelectorNode.h index 7ced9de8..1d27152e 100644 --- a/runtime/cpp/Compiler/AST/SSelectorNode.h +++ b/runtime/cpp/Compiler/AST/SSelectorNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -18,7 +18,7 @@ namespace Egg { */ class SSelectorNode : public SParseNode { private: - egg::string _symbol; + Egg::string _symbol; std::vector _keywords; // For keyword selectors with multiple parts public: @@ -27,8 +27,8 @@ class SSelectorNode : public SParseNode { void acceptVisitor_(SParseNodeVisitor* visitor) override; - egg::string symbol() const { return _symbol; } - void symbol_(const egg::string& s) { _symbol = s; } + Egg::string symbol() const { return _symbol; } + void symbol_(const Egg::string& s) { _symbol = s; } const std::vector& keywords() const { return _keywords; } void addKeyword_(SSelectorNode* kw) { _keywords.push_back(kw); } @@ -37,7 +37,7 @@ class SSelectorNode : public SParseNode { bool isBinary() const; bool hasSymbol() const { return !_symbol.empty(); } - egg::string value() const { return _symbol; } + Egg::string value() const { return _symbol; } void nodesDo_(std::function block, bool includeDeclarations = false) override { SParseNode::nodesDo_(block, includeDeclarations); diff --git a/runtime/cpp/Compiler/AST/SStringNode.cpp b/runtime/cpp/Compiler/AST/SStringNode.cpp index f47872a7..cfd77773 100644 --- a/runtime/cpp/Compiler/AST/SStringNode.cpp +++ b/runtime/cpp/Compiler/AST/SStringNode.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/AST/SStringNode.h b/runtime/cpp/Compiler/AST/SStringNode.h index 1fb93fff..4e2f79be 100644 --- a/runtime/cpp/Compiler/AST/SStringNode.h +++ b/runtime/cpp/Compiler/AST/SStringNode.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/Backend/SCompiledBlock.h b/runtime/cpp/Compiler/Backend/SCompiledBlock.h index 37a2da69..f084adb1 100644 --- a/runtime/cpp/Compiler/Backend/SCompiledBlock.h +++ b/runtime/cpp/Compiler/Backend/SCompiledBlock.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _SCOMPILEDBLOCK_H_ diff --git a/runtime/cpp/Compiler/Backend/SCompiledMethod.cpp b/runtime/cpp/Compiler/Backend/SCompiledMethod.cpp index 0f9f06c6..e0c62254 100644 --- a/runtime/cpp/Compiler/Backend/SCompiledMethod.cpp +++ b/runtime/cpp/Compiler/Backend/SCompiledMethod.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #include "SCompiledMethod.h" diff --git a/runtime/cpp/Compiler/Backend/SCompiledMethod.h b/runtime/cpp/Compiler/Backend/SCompiledMethod.h index 23b157d0..b75e31fc 100644 --- a/runtime/cpp/Compiler/Backend/SCompiledMethod.h +++ b/runtime/cpp/Compiler/Backend/SCompiledMethod.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _SCOMPILEDMETHOD_H_ @@ -28,8 +28,8 @@ class SCompiledMethod { void* _optimizedCode; std::vector _treecodes; void* _classBinding; - egg::string _selector; - egg::string _source; + Egg::string _selector; + Egg::string _source; // Format bit layout matching Smalltalk CompiledMethod>>initializeFormatFlags: // ArgCount: bits 0-5 (6 bits) @@ -86,11 +86,11 @@ class SCompiledMethod { bool isDebuggable() const; void beDebuggable_(); - egg::string selector() const { return _selector; } - void selector_(const egg::string& sel) { _selector = sel; } + Egg::string selector() const { return _selector; } + void selector_(const Egg::string& sel) { _selector = sel; } - egg::string source() const { return _source; } - void source_(const egg::string& src) { _source = src; } + Egg::string source() const { return _source; } + void source_(const Egg::string& src) { _source = src; } void* classBinding() const { return _classBinding; } void classBinding_(void* binding) { _classBinding = binding; } diff --git a/runtime/cpp/Compiler/Binding/ArgumentBinding.h b/runtime/cpp/Compiler/Binding/ArgumentBinding.h index 8f87a137..a195e765 100644 --- a/runtime/cpp/Compiler/Binding/ArgumentBinding.h +++ b/runtime/cpp/Compiler/Binding/ArgumentBinding.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _ARGUMENTBINDING_H_ @@ -11,7 +11,7 @@ namespace Egg { class ArgumentBinding : public LocalBinding { public: - ArgumentBinding(const egg::string& name, uint32_t position) + ArgumentBinding(const Egg::string& name, uint32_t position) : LocalBinding(Kind::Argument, name, position) { _environment = new ArgumentEnvironment(); } diff --git a/runtime/cpp/Compiler/Binding/ArgumentEnvironment.h b/runtime/cpp/Compiler/Binding/ArgumentEnvironment.h index 0cc8a5fe..7d8f41c8 100644 --- a/runtime/cpp/Compiler/Binding/ArgumentEnvironment.h +++ b/runtime/cpp/Compiler/Binding/ArgumentEnvironment.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _ARGUMENTENVIRONMENT_H_ diff --git a/runtime/cpp/Compiler/Binding/ArrayEnvironment.h b/runtime/cpp/Compiler/Binding/ArrayEnvironment.h index ebcf036d..8a4ee221 100644 --- a/runtime/cpp/Compiler/Binding/ArrayEnvironment.h +++ b/runtime/cpp/Compiler/Binding/ArrayEnvironment.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _ARRAYENVIRONMENT_H_ diff --git a/runtime/cpp/Compiler/Binding/Binding.h b/runtime/cpp/Compiler/Binding/Binding.h index 975ec4b8..66e167f0 100644 --- a/runtime/cpp/Compiler/Binding/Binding.h +++ b/runtime/cpp/Compiler/Binding/Binding.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _BINDING_H_ @@ -28,11 +28,11 @@ class Binding { Constant }; - Binding(Kind kind, const egg::string& name, uint32_t position) + Binding(Kind kind, const Egg::string& name, uint32_t position) : _kind(kind), _name(name), _position(position) {} Kind kind() const { return _kind; } - const egg::string& name() const { return _name; } + const Egg::string& name() const { return _name; } uint32_t position() const { return _position; } virtual bool isDynamic() const { return false; } @@ -52,7 +52,7 @@ class Binding { private: Kind _kind; - egg::string _name; + Egg::string _name; uint32_t _position; }; diff --git a/runtime/cpp/Compiler/Binding/BlockScope.cpp b/runtime/cpp/Compiler/Binding/BlockScope.cpp index 21fe907b..210ebb99 100644 --- a/runtime/cpp/Compiler/Binding/BlockScope.cpp +++ b/runtime/cpp/Compiler/Binding/BlockScope.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #include "BlockScope.h" @@ -15,7 +15,7 @@ BlockScope::BlockScope() : ScriptScope() { } Binding* BlockScope::captureArgument_(Binding* anArgumentBinding) { - egg::string name = anArgumentBinding->name(); + Egg::string name = anArgumentBinding->name(); auto it = _captured.find(name); if (it != _captured.end()) { return it->second; @@ -61,7 +61,7 @@ void BlockScope::captureSelf_() { } Binding* BlockScope::captureTemporary_(Binding* aTemporaryBinding) { - egg::string name = aTemporaryBinding->name(); + Egg::string name = aTemporaryBinding->name(); if (defines_(name)) { return aTemporaryBinding; } @@ -265,13 +265,13 @@ ScriptScope* BlockScope::realParent_() { return realParent ? realParent->scope() : nullptr; } -Binding* BlockScope::resolve_(const egg::string& aString) { +Binding* BlockScope::resolve_(const Egg::string& aString) { auto local = resolveLocal_(aString); if (local) return local; return parent_()->resolve_(aString); } -Binding* BlockScope::resolveLocal_(const egg::string& aString) { +Binding* BlockScope::resolveLocal_(const Egg::string& aString) { auto local = ScriptScope::resolveLocal_(aString); if (local) return local; @@ -279,14 +279,14 @@ Binding* BlockScope::resolveLocal_(const egg::string& aString) { return (it != _captured.end()) ? it->second : nullptr; } -SScriptNode* BlockScope::scriptDefining_(const egg::string& aString) { +SScriptNode* BlockScope::scriptDefining_(const Egg::string& aString) { if (defines_(aString)) { return _script; } return parent_()->scriptDefining_(aString); } -Binding* BlockScope::transferLocal_(const egg::string& name) { +Binding* BlockScope::transferLocal_(const Egg::string& name) { auto binding = resolveLocal_(name); if (binding) return binding; diff --git a/runtime/cpp/Compiler/Binding/BlockScope.h b/runtime/cpp/Compiler/Binding/BlockScope.h index 5fdce70d..5a2d6fd2 100644 --- a/runtime/cpp/Compiler/Binding/BlockScope.h +++ b/runtime/cpp/Compiler/Binding/BlockScope.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _BLOCKSCOPE_H_ @@ -17,7 +17,7 @@ namespace Egg { class BlockScope : public ScriptScope { private: std::vector _environments; - std::map _captured; + std::map _captured; Binding* captureArgument_(Binding* anArgumentBinding); Binding* captureTemporary_(Binding* aTemporaryBinding); @@ -46,10 +46,10 @@ class BlockScope : public ScriptScope { Binding* captureLocal_(Binding* aLocalBinding) override; void captureSelf_() override; int* environmentIndexOf_(SScriptNode* aScriptNode) override; - Binding* resolve_(const egg::string& aString) override; - Binding* resolveLocal_(const egg::string& aString); - SScriptNode* scriptDefining_(const egg::string& aString) override; - Binding* transferLocal_(const egg::string& name) override; + Binding* resolve_(const Egg::string& aString) override; + Binding* resolveLocal_(const Egg::string& aString); + SScriptNode* scriptDefining_(const Egg::string& aString) override; + Binding* transferLocal_(const Egg::string& name) override; std::vector localBindings_() override; void positionDefinedLocals_() override; void positionLocals_() override; diff --git a/runtime/cpp/Compiler/Binding/ClassBinding.h b/runtime/cpp/Compiler/Binding/ClassBinding.h index 57794a87..d7bc5596 100644 --- a/runtime/cpp/Compiler/Binding/ClassBinding.h +++ b/runtime/cpp/Compiler/Binding/ClassBinding.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _CLASSBINDING_H_ @@ -10,7 +10,7 @@ namespace Egg { class ClassBinding : public Binding { public: - ClassBinding(const egg::string& name, uint32_t position) + ClassBinding(const Egg::string& name, uint32_t position) : Binding(Kind::Class, name, position) {} Binding* copy_() override { diff --git a/runtime/cpp/Compiler/Binding/ConstantBinding.h b/runtime/cpp/Compiler/Binding/ConstantBinding.h index 631703bf..83012ba4 100644 --- a/runtime/cpp/Compiler/Binding/ConstantBinding.h +++ b/runtime/cpp/Compiler/Binding/ConstantBinding.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _CONSTANTBINDING_H_ @@ -10,7 +10,7 @@ namespace Egg { class ConstantBinding : public Binding { public: - ConstantBinding(const egg::string& name, uint32_t position) + ConstantBinding(const Egg::string& name, uint32_t position) : Binding(Kind::Constant, name, position) {} bool isLiteral() const { return true; } diff --git a/runtime/cpp/Compiler/Binding/FieldBinding.h b/runtime/cpp/Compiler/Binding/FieldBinding.h index 268bcced..61720281 100644 --- a/runtime/cpp/Compiler/Binding/FieldBinding.h +++ b/runtime/cpp/Compiler/Binding/FieldBinding.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _FIELDBINDING_H_ @@ -10,7 +10,7 @@ namespace Egg { class FieldBinding : public Binding { public: - FieldBinding(const egg::string& name, uint32_t position) + FieldBinding(const Egg::string& name, uint32_t position) : Binding(Kind::Field, name, position) {} Binding* copy_() override { diff --git a/runtime/cpp/Compiler/Binding/GlobalBinding.h b/runtime/cpp/Compiler/Binding/GlobalBinding.h index 73cc180c..52540317 100644 --- a/runtime/cpp/Compiler/Binding/GlobalBinding.h +++ b/runtime/cpp/Compiler/Binding/GlobalBinding.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _GLOBALBINDING_H_ @@ -10,7 +10,7 @@ namespace Egg { class GlobalBinding : public Binding { public: - GlobalBinding(const egg::string& name, uint32_t position) + GlobalBinding(const Egg::string& name, uint32_t position) : Binding(Kind::Global, name, position) {} Binding* copy_() override { diff --git a/runtime/cpp/Compiler/Binding/LocalBinding.cpp b/runtime/cpp/Compiler/Binding/LocalBinding.cpp index abe805ea..9899570e 100644 --- a/runtime/cpp/Compiler/Binding/LocalBinding.cpp +++ b/runtime/cpp/Compiler/Binding/LocalBinding.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #include "LocalBinding.h" diff --git a/runtime/cpp/Compiler/Binding/LocalBinding.h b/runtime/cpp/Compiler/Binding/LocalBinding.h index b342df7f..f2f4d5a0 100644 --- a/runtime/cpp/Compiler/Binding/LocalBinding.h +++ b/runtime/cpp/Compiler/Binding/LocalBinding.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _LOCALBINDING_H_ @@ -22,7 +22,7 @@ class LocalBinding : public Binding { SIdentifierNode* _declaration; public: - LocalBinding(Kind kind, const egg::string& name, uint32_t position) + LocalBinding(Kind kind, const Egg::string& name, uint32_t position) : Binding(kind, name, position), _index(-1), _environment(nullptr), _declaration(nullptr) {} virtual ~LocalBinding() { diff --git a/runtime/cpp/Compiler/Binding/LocalEnvironment.h b/runtime/cpp/Compiler/Binding/LocalEnvironment.h index 5e4e4635..15e3e7a8 100644 --- a/runtime/cpp/Compiler/Binding/LocalEnvironment.h +++ b/runtime/cpp/Compiler/Binding/LocalEnvironment.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _LOCALENVIRONMENT_H_ diff --git a/runtime/cpp/Compiler/Binding/MethodBinding.h b/runtime/cpp/Compiler/Binding/MethodBinding.h index 6d4471c9..cd89d7c9 100644 --- a/runtime/cpp/Compiler/Binding/MethodBinding.h +++ b/runtime/cpp/Compiler/Binding/MethodBinding.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _METHODBINDING_H_ @@ -10,7 +10,7 @@ namespace Egg { class MethodBinding : public Binding { public: - MethodBinding(const egg::string& name, uint32_t position) + MethodBinding(const Egg::string& name, uint32_t position) : Binding(Kind::Method, name, position) {} Binding* copy_() override { diff --git a/runtime/cpp/Compiler/Binding/MethodScope.cpp b/runtime/cpp/Compiler/Binding/MethodScope.cpp index dd96745b..b3b96c0b 100644 --- a/runtime/cpp/Compiler/Binding/MethodScope.cpp +++ b/runtime/cpp/Compiler/Binding/MethodScope.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #include "MethodScope.h" @@ -39,7 +39,7 @@ int* MethodScope::environmentIndexOf_(SScriptNode* aScriptNode) { return nullptr; } -Binding* MethodScope::resolve_(const egg::string& aString) { +Binding* MethodScope::resolve_(const Egg::string& aString) { auto local = resolveLocal_(aString); if (local) return local; @@ -49,12 +49,12 @@ Binding* MethodScope::resolve_(const egg::string& aString) { return DynamicBinding::named_(aString); } -Binding* MethodScope::resolvePseudo_(const egg::string& aString) { +Binding* MethodScope::resolvePseudo_(const Egg::string& aString) { auto it = _pseudo.find(aString); return (it != _pseudo.end()) ? it->second : nullptr; } -SScriptNode* MethodScope::scriptDefining_(const egg::string& aString) { +SScriptNode* MethodScope::scriptDefining_(const Egg::string& aString) { if (resolveLocal_(aString)) { return _script; } @@ -62,7 +62,7 @@ SScriptNode* MethodScope::scriptDefining_(const egg::string& aString) { return nullptr; } -Binding* MethodScope::transferLocal_(const egg::string& name) { +Binding* MethodScope::transferLocal_(const Egg::string& name) { return resolveLocal_(name); } diff --git a/runtime/cpp/Compiler/Binding/MethodScope.h b/runtime/cpp/Compiler/Binding/MethodScope.h index da5eb0da..671bcb7c 100644 --- a/runtime/cpp/Compiler/Binding/MethodScope.h +++ b/runtime/cpp/Compiler/Binding/MethodScope.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _METHODSCOPE_H_ @@ -23,10 +23,10 @@ class DynamicBinding; */ class MethodScope : public ScriptScope { private: - std::map _pseudo; + std::map _pseudo; void initializePseudoVars_(); - Binding* resolvePseudo_(const egg::string& aString); + Binding* resolvePseudo_(const Egg::string& aString); public: MethodScope(); @@ -36,9 +36,9 @@ class MethodScope : public ScriptScope { Binding* captureLocal_(Binding* aLocalBinding) override; void captureSelf_() override; int* environmentIndexOf_(SScriptNode* aScriptNode) override; - Binding* resolve_(const egg::string& aString) override; - SScriptNode* scriptDefining_(const egg::string& aString) override; - Binding* transferLocal_(const egg::string& name) override; + Binding* resolve_(const Egg::string& aString) override; + SScriptNode* scriptDefining_(const Egg::string& aString) override; + Binding* transferLocal_(const Egg::string& name) override; }; } // namespace Egg diff --git a/runtime/cpp/Compiler/Binding/PseudoVariableBindings.cpp b/runtime/cpp/Compiler/Binding/PseudoVariableBindings.cpp index cdfac9f2..38a204a0 100644 --- a/runtime/cpp/Compiler/Binding/PseudoVariableBindings.cpp +++ b/runtime/cpp/Compiler/Binding/PseudoVariableBindings.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #include "PseudoVariableBindings.h" @@ -48,15 +48,15 @@ void NestedDynamicBinding::encodeUsing_(TreecodeEncoder* encoder) { encoder->encodeNestedDynamicVar_(name()); } -DynamicBinding* DynamicBinding::named_(const egg::string& name) { +DynamicBinding* DynamicBinding::named_(const Egg::string& name) { size_t dotPos = name.find('.'); - if (dotPos == egg::string::npos) { + if (dotPos == Egg::string::npos) { return new DynamicBinding(name); } - egg::string first = name.substr(0, dotPos); - egg::string second = name.substr(dotPos + 1); - std::vector names = {first, second}; + Egg::string first = name.substr(0, dotPos); + Egg::string second = name.substr(dotPos + 1); + std::vector names = {first, second}; return new NestedDynamicBinding(names); } diff --git a/runtime/cpp/Compiler/Binding/PseudoVariableBindings.h b/runtime/cpp/Compiler/Binding/PseudoVariableBindings.h index c3f65bd5..c87bd495 100644 --- a/runtime/cpp/Compiler/Binding/PseudoVariableBindings.h +++ b/runtime/cpp/Compiler/Binding/PseudoVariableBindings.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _PSEUDOVARIABLEBINDINGS_H_ @@ -78,10 +78,10 @@ class SuperBinding : public Binding { class DynamicBinding : public Binding { public: - DynamicBinding(const egg::string& name) + DynamicBinding(const Egg::string& name) : Binding(Kind::Global, name, 0) {} - static DynamicBinding* named_(const egg::string& name); + static DynamicBinding* named_(const Egg::string& name); bool isDynamic() const override { return true; } @@ -98,13 +98,13 @@ class DynamicBinding : public Binding { class NestedDynamicBinding : public DynamicBinding { private: - std::vector _names; + std::vector _names; public: - NestedDynamicBinding(const std::vector& names) + NestedDynamicBinding(const std::vector& names) : DynamicBinding(buildCompositeName(names)), _names(names) {} - const std::vector& names() const { return _names; } + const std::vector& names() const { return _names; } void encodeUsing_(TreecodeEncoder* encoder) override; @@ -113,8 +113,8 @@ class NestedDynamicBinding : public DynamicBinding { } private: - static egg::string buildCompositeName(const std::vector& names) { - egg::string result; + static Egg::string buildCompositeName(const std::vector& names) { + Egg::string result; for (size_t i = 0; i < names.size(); ++i) { if (i > 0) result += "."; result += names[i]; diff --git a/runtime/cpp/Compiler/Binding/Scope.h b/runtime/cpp/Compiler/Binding/Scope.h index 9cbeedca..0287dfa6 100644 --- a/runtime/cpp/Compiler/Binding/Scope.h +++ b/runtime/cpp/Compiler/Binding/Scope.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _SCOPE_H_ @@ -19,8 +19,8 @@ class Scope { virtual ~Scope() {} void addBinding_(Binding* binding) { _bindings.push_back(binding); } const std::vector& bindings() const { return _bindings; } - virtual Binding* defineArgument_(const egg::string& name) { auto b = new ArgumentBinding(name, 0); addBinding_(b); return b; } - virtual Binding* defineTemporary_(const egg::string& name) { auto b = new TemporaryBinding(name, 0); addBinding_(b); return b; } + virtual Binding* defineArgument_(const Egg::string& name) { auto b = new ArgumentBinding(name, 0); addBinding_(b); return b; } + virtual Binding* defineTemporary_(const Egg::string& name) { auto b = new TemporaryBinding(name, 0); addBinding_(b); return b; } virtual void captureEnvironment_(SParseNode* aScriptNode) { /* Default: do nothing */ } private: std::vector _bindings; diff --git a/runtime/cpp/Compiler/Binding/ScriptScope.cpp b/runtime/cpp/Compiler/Binding/ScriptScope.cpp index c6503360..05ff8919 100644 --- a/runtime/cpp/Compiler/Binding/ScriptScope.cpp +++ b/runtime/cpp/Compiler/Binding/ScriptScope.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #include "ScriptScope.h" @@ -16,7 +16,7 @@ ScriptScope::ScriptScope() : Scope(), _script(nullptr), _stackSize(0), _envSize(0), _captureSelf(false) { } -Binding* ScriptScope::defineArgument_(const egg::string& identifier) { +Binding* ScriptScope::defineArgument_(const Egg::string& identifier) { if (resolves_(identifier)) { redefinitionError_(identifier); } @@ -27,7 +27,7 @@ Binding* ScriptScope::defineArgument_(const egg::string& identifier) { return binding; } -Binding* ScriptScope::defineTemporary_(const egg::string& identifier) { +Binding* ScriptScope::defineTemporary_(const Egg::string& identifier) { if (_temporaries.find(identifier) != _temporaries.end()) { redefinitionError_(identifier); } @@ -37,12 +37,12 @@ Binding* ScriptScope::defineTemporary_(const egg::string& identifier) { return binding; } -bool ScriptScope::defines_(const egg::string& aString) { +bool ScriptScope::defines_(const Egg::string& aString) { return _temporaries.find(aString) != _temporaries.end() || _arguments.find(aString) != _arguments.end(); } -Binding* ScriptScope::resolveLocal_(const egg::string& aString) { +Binding* ScriptScope::resolveLocal_(const Egg::string& aString) { auto it = _temporaries.find(aString); if (it != _temporaries.end()) { return it->second; @@ -54,7 +54,7 @@ Binding* ScriptScope::resolveLocal_(const egg::string& aString) { return nullptr; } -bool ScriptScope::resolves_(const egg::string& aString) { +bool ScriptScope::resolves_(const Egg::string& aString) { auto binding = resolve_(aString); return binding && !binding->isDynamic(); } @@ -119,7 +119,7 @@ int* ScriptScope::environmentIndexOf_(SScriptNode* aScriptNode) { return nullptr; } -Binding* ScriptScope::transferLocal_(const egg::string& name) { +Binding* ScriptScope::transferLocal_(const Egg::string& name) { return resolveLocal_(name); } @@ -129,7 +129,7 @@ ScriptScope* ScriptScope::realScope_() { return realScript ? realScript->scope() : nullptr; } -void ScriptScope::redefinitionError_(const egg::string& name) { +void ScriptScope::redefinitionError_(const Egg::string& name) { if (_script && _script->compiler()) { _script->compiler()->warning_at_( name.toUtf8() + " already declared", diff --git a/runtime/cpp/Compiler/Binding/ScriptScope.h b/runtime/cpp/Compiler/Binding/ScriptScope.h index ec52622d..c1f6eabb 100644 --- a/runtime/cpp/Compiler/Binding/ScriptScope.h +++ b/runtime/cpp/Compiler/Binding/ScriptScope.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _SCRIPTSCOPE_H_ @@ -23,14 +23,14 @@ class TemporaryBinding; class ScriptScope : public Scope { protected: SScriptNode* _script; - std::map _arguments; - std::vector _argumentOrder; - std::map _temporaries; + std::map _arguments; + std::vector _argumentOrder; + std::map _temporaries; int _stackSize; int _envSize; bool _captureSelf; - void redefinitionError_(const egg::string& name); + void redefinitionError_(const Egg::string& name); public: ScriptScope(); @@ -44,11 +44,11 @@ class ScriptScope : public Scope { int growEnvironment_() { return ++_envSize; } int growStack_() { return ++_stackSize; } - Binding* defineArgument_(const egg::string& identifier) override; - Binding* defineTemporary_(const egg::string& identifier) override; - bool defines_(const egg::string& aString); - Binding* resolveLocal_(const egg::string& aString); - bool resolves_(const egg::string& aString); + Binding* defineArgument_(const Egg::string& identifier) override; + Binding* defineTemporary_(const Egg::string& identifier) override; + bool defines_(const Egg::string& aString); + Binding* resolveLocal_(const Egg::string& aString); + bool resolves_(const Egg::string& aString); virtual std::vector localBindings_(); virtual void positionLocals_(); @@ -60,9 +60,9 @@ class ScriptScope : public Scope { virtual Binding* captureLocal_(Binding* aLocalBinding); virtual void captureSelf_(); virtual int* environmentIndexOf_(SScriptNode* aScriptNode); - virtual Binding* resolve_(const egg::string& aString) = 0; - virtual SScriptNode* scriptDefining_(const egg::string& aString) = 0; - virtual Binding* transferLocal_(const egg::string& name); + virtual Binding* resolve_(const Egg::string& aString) = 0; + virtual SScriptNode* scriptDefining_(const Egg::string& aString) = 0; + virtual Binding* transferLocal_(const Egg::string& name); virtual ScriptScope* realScope_(); }; diff --git a/runtime/cpp/Compiler/Binding/StackEnvironment.h b/runtime/cpp/Compiler/Binding/StackEnvironment.h index 27eb7397..93b03d15 100644 --- a/runtime/cpp/Compiler/Binding/StackEnvironment.h +++ b/runtime/cpp/Compiler/Binding/StackEnvironment.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _STACKENVIRONMENT_H_ diff --git a/runtime/cpp/Compiler/Binding/TemporaryBinding.h b/runtime/cpp/Compiler/Binding/TemporaryBinding.h index 170767a6..9300028a 100644 --- a/runtime/cpp/Compiler/Binding/TemporaryBinding.h +++ b/runtime/cpp/Compiler/Binding/TemporaryBinding.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _TEMPORARYBINDING_H_ @@ -11,7 +11,7 @@ namespace Egg { class TemporaryBinding : public LocalBinding { public: - TemporaryBinding(const egg::string& name, uint32_t position) + TemporaryBinding(const Egg::string& name, uint32_t position) : LocalBinding(Kind::Temporary, name, position) { _environment = new StackEnvironment(); } diff --git a/runtime/cpp/Compiler/CompilationError.cpp b/runtime/cpp/Compiler/CompilationError.cpp index 3f2142bd..5605094e 100644 --- a/runtime/cpp/Compiler/CompilationError.cpp +++ b/runtime/cpp/Compiler/CompilationError.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -9,7 +9,7 @@ namespace Egg { -CompilationError::CompilationError(const egg::string& desc) +CompilationError::CompilationError(const Egg::string& desc) : std::runtime_error(desc.toUtf8()), _compiler(nullptr), _resumable(false), _retryable(false), _stretch(nullptr), _description(desc) { } @@ -40,9 +40,9 @@ void CompilationError::proceed() { } } -egg::string CompilationError::source() { +Egg::string CompilationError::source() { if (!_compiler || !_stretch) return ""; - egg::string sourceCode = _compiler->sourceCode(); + Egg::string sourceCode = _compiler->sourceCode(); int start = _stretch->start(); int end = _stretch->end(); if (start < 0 || end > static_cast(sourceCode.length()) || start > end) { diff --git a/runtime/cpp/Compiler/CompilationError.h b/runtime/cpp/Compiler/CompilationError.h index 344c9579..63c173e3 100644 --- a/runtime/cpp/Compiler/CompilationError.h +++ b/runtime/cpp/Compiler/CompilationError.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -9,7 +9,7 @@ #include #include #include "Stretch.h" -#include "egg_string.h" +#include "Utils/egg_string.h" namespace Egg { @@ -25,10 +25,10 @@ class CompilationError : public std::runtime_error { bool _resumable; bool _retryable; Stretch* _stretch; - egg::string _description; + Egg::string _description; public: - CompilationError(const egg::string& desc = ""); + CompilationError(const Egg::string& desc = ""); virtual ~CompilationError() {} void beFatal(); @@ -36,13 +36,13 @@ class CompilationError : public std::runtime_error { void beWarning(); SSmalltalkCompiler* compiler() { return _compiler; } void compiler_(SSmalltalkCompiler* aSCompiler); - void description_(const egg::string& aString) { _description = aString; } - egg::string description() const { return _description; } + void description_(const Egg::string& aString) { _description = aString; } + Egg::string description() const { return _description; } bool isResumable() const { return _resumable; } bool isUndeclaredAccess() const { return false; } bool isUndeclaredAssignment() const { return false; } void proceed(); - egg::string source(); + Egg::string source(); Stretch* stretch() { return _stretch; } void stretch_(Stretch* aStretch) { _stretch = aStretch; } }; diff --git a/runtime/cpp/Compiler/CompilationResult.h b/runtime/cpp/Compiler/CompilationResult.h index 63368561..3f12f951 100644 --- a/runtime/cpp/Compiler/CompilationResult.h +++ b/runtime/cpp/Compiler/CompilationResult.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/CompilerTypes.h b/runtime/cpp/Compiler/CompilerTypes.h index 2dc35200..d33c2b83 100644 --- a/runtime/cpp/Compiler/CompilerTypes.h +++ b/runtime/cpp/Compiler/CompilerTypes.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/LiteralValue.h b/runtime/cpp/Compiler/LiteralValue.h index 178eefe8..d3f76e21 100644 --- a/runtime/cpp/Compiler/LiteralValue.h +++ b/runtime/cpp/Compiler/LiteralValue.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -10,7 +10,7 @@ #include #include #include -#include "egg_string.h" +#include "Utils/egg_string.h" namespace Egg { @@ -54,8 +54,8 @@ struct LiteralValue { }; BlockInfo blockInfo; - // Strings and symbols share this field (union can't hold egg::string) - egg::string strVal; + // Strings and symbols share this field (union can't hold Egg::string) + Egg::string strVal; // For literal arrays std::vector elements; @@ -84,14 +84,14 @@ struct LiteralValue { return lit; } - static LiteralValue fromString(const egg::string& v) { + static LiteralValue fromString(const Egg::string& v) { LiteralValue lit; lit.tag = String; lit.strVal = v; return lit; } - static LiteralValue fromSymbol(const egg::string& v) { + static LiteralValue fromSymbol(const Egg::string& v) { LiteralValue lit; lit.tag = Symbol; lit.strVal = v; @@ -162,7 +162,7 @@ struct LiteralValue { uint32_t asCharacter() const { assert(tag == Character); return charVal; } bool asBoolean() const { assert(tag == Boolean); return boolVal; } - const egg::string& asString() const { + const Egg::string& asString() const { assert(tag == String || tag == Symbol); return strVal; } @@ -182,14 +182,14 @@ struct LiteralValue { * Also used as a backwards-compatible "value()" for code that * previously relied on the string representation. */ - egg::string printString() const { + Egg::string printString() const { switch (tag) { case None: return ""; - case Integer: return egg::string(std::to_string(intVal)); - case Float: return egg::string(std::to_string(floatVal)); + case Integer: return Egg::string(std::to_string(intVal)); + case Float: return Egg::string(std::to_string(floatVal)); case String: return strVal; case Symbol: return strVal; - case Character: { egg::string s; s += (char32_t)charVal; return s; } + case Character: { Egg::string s; s += (char32_t)charVal; return s; } case Boolean: return boolVal ? "true" : "false"; case Nil: return "nil"; case Array: return "#(...)"; diff --git a/runtime/cpp/Compiler/MessageInliner.cpp b/runtime/cpp/Compiler/MessageInliner.cpp index 1a3f7ac2..dea31375 100644 --- a/runtime/cpp/Compiler/MessageInliner.cpp +++ b/runtime/cpp/Compiler/MessageInliner.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -25,7 +25,7 @@ void MessageInliner::inline_(SMessageNode* aMessageNode) { } SSelectorNode* selectorNode = static_cast(_message->selector()); - egg::string s = selectorNode->value(); + Egg::string s = selectorNode->value(); if (s == "ifTrue:" || s == "ifFalse:" || s == "or:" || s == "and:" || s == "timesRepeat:" || s == "andNot:" || s == "orNot:" || s == "ifNil:") { @@ -73,11 +73,11 @@ void MessageInliner::inline_(SMessageNode* aMessageNode) { return; } - std::vector keywords; + std::vector keywords; size_t pos = 0; while (pos < s.length()) { size_t colon = s.find(':', pos); - if (colon == egg::string::npos) break; + if (colon == Egg::string::npos) break; keywords.push_back(s.substr(pos, colon - pos)); pos = colon + 1; } @@ -88,30 +88,30 @@ void MessageInliner::inline_(SMessageNode* aMessageNode) { } bool allAnd = std::all_of(keywords.begin(), keywords.end(), - [](const egg::string& k) { return k == "and"; }); + [](const Egg::string& k) { return k == "and"; }); if (allAnd) { inlineConditional(); return; } bool allOr = std::all_of(keywords.begin(), keywords.end(), - [](const egg::string& k) { return k == "or"; }); + [](const Egg::string& k) { return k == "or"; }); if (allOr) { inlineConditional(); return; } if (keywords.size() > 1) { - egg::string last = keywords.back(); + Egg::string last = keywords.back(); bool allButLastAnd = std::all_of(keywords.begin(), keywords.end() - 1, - [](const egg::string& k) { return k == "and"; }); + [](const Egg::string& k) { return k == "and"; }); if (allButLastAnd && (last == "ifTrue" || last == "ifFalse")) { inlineConditional(); return; } bool allButLastOr = std::all_of(keywords.begin(), keywords.end() - 1, - [](const egg::string& k) { return k == "or"; }); + [](const Egg::string& k) { return k == "or"; }); if (allButLastOr && (last == "ifTrue" || last == "ifFalse")) { inlineConditional(); return; diff --git a/runtime/cpp/Compiler/MessageInliner.h b/runtime/cpp/Compiler/MessageInliner.h index fc05e979..bcedead1 100644 --- a/runtime/cpp/Compiler/MessageInliner.h +++ b/runtime/cpp/Compiler/MessageInliner.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/Parser/SSmalltalkParser.cpp b/runtime/cpp/Compiler/Parser/SSmalltalkParser.cpp index 23ab0e4c..9879c0af 100644 --- a/runtime/cpp/Compiler/Parser/SSmalltalkParser.cpp +++ b/runtime/cpp/Compiler/Parser/SSmalltalkParser.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -45,7 +45,7 @@ SToken* SSmalltalkParser::peek_() { } _next.reset(_scanner->nextToken().release()); - std::vector comments; + std::vector comments; while (_next && _next->isComment()) { comments.push_back(_next->value()); _next.reset(_scanner->nextToken().release()); @@ -63,7 +63,7 @@ SToken* SSmalltalkParser::peek_() { SToken* SSmalltalkParser::step_() { SToken* save = _token.get(); next_(); - std::vector comments; + std::vector comments; while (_token && _token->isComment()) { comments.push_back(_token->value()); next_(); @@ -182,7 +182,7 @@ SMethodNode* SSmalltalkParser::keywordSignature_() { return nullptr; } - egg::string selector; + Egg::string selector; std::vector arguments; uint32_t start = _token->position().start(); @@ -508,7 +508,7 @@ SParseNode* SSmalltalkParser::keywordSequence_(SParseNode* receiver) { } void SSmalltalkParser::keywordMessage_(SMessageNode* message) { - egg::string selector; + Egg::string selector; std::vector arguments; uint32_t start = _token->position().start(); while (_token && _token->isKeyword()) { @@ -608,7 +608,7 @@ SParseNode* SSmalltalkParser::literalArray_() { } } else if (_token->isName()) { // pseudoLiteralValue: convert nil/true/false to actual values - egg::string val = _token->value(); + Egg::string val = _token->value(); if (val == "nil") { elements.push_back(LiteralValue::nil()); } else if (val == "true") { @@ -620,7 +620,7 @@ SParseNode* SSmalltalkParser::literalArray_() { } } else if (_token->isKeyword()) { // literalKeyword: collect multi-part keyword symbol (e.g., at:put:) - egg::string keyword = _token->value(); + Egg::string keyword = _token->value(); step_(); while (_token && _token->isKeyword()) { keyword += _token->value(); @@ -740,7 +740,7 @@ bool SSmalltalkParser::attachPragmaTo_(SMethodNode* method) { SPragmaNode* pragma = nullptr; if (_token && _token->isKeyword()) { - egg::string keyword = _token->value(); + Egg::string keyword = _token->value(); if (keyword == "primitive:") { pragma = pragma_(); } else { @@ -797,7 +797,7 @@ SPragmaNode* SSmalltalkParser::numberedPrimitive_() { } SPragmaNode* SSmalltalkParser::namedPrimitive_() { - egg::string name = _token->value(); + Egg::string name = _token->value(); uint32_t position = _token->position().start(); SPragmaNode* pragma = new SPragmaNode(_compiler); @@ -809,7 +809,7 @@ SPragmaNode* SSmalltalkParser::namedPrimitive_() { } SPragmaNode* SSmalltalkParser::symbolicPragma_() { - egg::string symbol = _token->value(); + Egg::string symbol = _token->value(); uint32_t position = _token->position().start(); SPragmaNode* pragma = new SPragmaNode(_compiler); diff --git a/runtime/cpp/Compiler/Parser/SSmalltalkParser.h b/runtime/cpp/Compiler/Parser/SSmalltalkParser.h index f988e14f..7be81f7f 100644 --- a/runtime/cpp/Compiler/Parser/SSmalltalkParser.h +++ b/runtime/cpp/Compiler/Parser/SSmalltalkParser.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/Parser/SSmalltalkScanner.cpp b/runtime/cpp/Compiler/Parser/SSmalltalkScanner.cpp index da1fab96..9a87da82 100644 --- a/runtime/cpp/Compiler/Parser/SSmalltalkScanner.cpp +++ b/runtime/cpp/Compiler/Parser/SSmalltalkScanner.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -20,11 +20,11 @@ void SSmalltalkScanner::compiler_(SSmalltalkCompiler* compiler) { _compiler = compiler; } -void SSmalltalkScanner::on_(const egg::string& source) { +void SSmalltalkScanner::on_(const Egg::string& source) { stream.on_(source); } -void SSmalltalkScanner::sourceCode_(const egg::string& source) { +void SSmalltalkScanner::sourceCode_(const Egg::string& source) { stream.on_(source); } @@ -72,27 +72,27 @@ bool SSmalltalkScanner::isBinary_(uint32_t ch) const { } std::unique_ptr SSmalltalkScanner::nextArrayPrefix() { - egg::string string = stream.copyFrom_to_(stream.position() - 2, stream.position()); + Egg::string string = stream.copyFrom_to_(stream.position() - 2, stream.position()); auto token = new SDelimiterToken(Stretch(0, 0), U""); return buildToken_at_with_(token, stream.position() - 2, string); } std::unique_ptr SSmalltalkScanner::nextAssignment() { auto token = new SDelimiterToken(Stretch(0, 0), U""); - return buildToken_at_with_(token, stream.position(), egg::string(U":=")); + return buildToken_at_with_(token, stream.position(), Egg::string(U":=")); } std::unique_ptr SSmalltalkScanner::nextBinarySelector() { stream.back(); size_t start = stream.position(); - egg::string value = scanBinarySymbol(); + Egg::string value = scanBinarySymbol(); auto token = new SSymbolicToken(Stretch(0, 0), U"", true); return buildToken_at_with_(token, start, value); } std::unique_ptr SSmalltalkScanner::nextBinarySymbol() { size_t start = stream.position(); - egg::string value = scanBinarySymbol(); + Egg::string value = scanBinarySymbol(); auto token = new SStringToken(Stretch(0, 0), U"", SStringToken::LitSymbol); return buildToken_at_with_(token, start, value); } @@ -136,7 +136,7 @@ std::unique_ptr SSmalltalkScanner::nextComment() { } stream.position_(start); - egg::string comment = stream.upTo_('"'); + Egg::string comment = stream.upTo_('"'); return nextToken(); } @@ -158,7 +158,7 @@ std::unique_ptr SSmalltalkScanner::nextIdentifierOrKeyword() { std::unique_ptr SSmalltalkScanner::nextKeyword() { size_t start = stream.position(); skipKeyword(); - egg::string string = stream.copyFrom_to_(start, stream.position()); + Egg::string string = stream.copyFrom_to_(start, stream.position()); auto token = new SStringToken(Stretch(0, 0), U"", SStringToken::LitSymbol); return buildToken_at_with_(token, start, string); } @@ -168,14 +168,14 @@ std::unique_ptr SSmalltalkScanner::nextLiteralCharacter() { error_("character expected"); } uint32_t cp = stream.next(); - egg::string value(1, (char32_t)cp); + Egg::string value(1, (char32_t)cp); auto token = new SStringToken(Stretch(0, 0), U"", SStringToken::LitCharacter); return buildToken_at_with_(token, stream.position(), value); } std::unique_ptr SSmalltalkScanner::nextLiteralString() { size_t start = stream.position(); - egg::string value = scanString(); + Egg::string value = scanString(); auto token = new SStringToken(Stretch(0, 0), U""); return buildToken_at_with_(token, start, value); } @@ -236,7 +236,7 @@ std::unique_ptr SSmalltalkScanner::nextNumber() { } } - egg::string value = stream.copyFrom_to_(start, stream.position()); + Egg::string value = stream.copyFrom_to_(start, stream.position()); auto token = new SStringToken(Stretch(0, 0), U"", SStringToken::LitNumber); return buildToken_at_with_(token, start, value); } @@ -328,10 +328,10 @@ std::unique_ptr SSmalltalkScanner::nextToken() { return nextSpecialCharacter(); } -egg::string SSmalltalkScanner::scanBinarySymbol() { +Egg::string SSmalltalkScanner::scanBinarySymbol() { size_t start = stream.position(); skipBinary(); - egg::string symbol = stream.copyFrom_to_(start, stream.position()); + Egg::string symbol = stream.copyFrom_to_(start, stream.position()); return symbol; } @@ -349,13 +349,13 @@ uint32_t SSmalltalkScanner::scanChar() { return stream.next(); } -egg::string SSmalltalkScanner::scanString() { +Egg::string SSmalltalkScanner::scanString() { size_t current = stream.position(); size_t start = current; - egg::string result; + Egg::string result; while (true) { - egg::string fragment = stream.upTo_('\''); + Egg::string fragment = stream.upTo_('\''); result += fragment; if (current < stream.position()) { diff --git a/runtime/cpp/Compiler/Parser/SSmalltalkScanner.h b/runtime/cpp/Compiler/Parser/SSmalltalkScanner.h index 1585866b..764211d5 100644 --- a/runtime/cpp/Compiler/Parser/SSmalltalkScanner.h +++ b/runtime/cpp/Compiler/Parser/SSmalltalkScanner.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -27,12 +27,12 @@ class SSmalltalkScanner { template std::unique_ptr buildToken_at_(T* token, size_t position) { - egg::string string = stream.copyFrom_to_(position, stream.position()); + Egg::string string = stream.copyFrom_to_(position, stream.position()); return buildToken_at_with_(token, position, string); } template - std::unique_ptr buildToken_at_with_(T* token, size_t position, const egg::string& value) { + std::unique_ptr buildToken_at_with_(T* token, size_t position, const Egg::string& value) { token->position_(Stretch(position, stream.position())); token->value_(value); return std::unique_ptr(token); @@ -61,9 +61,9 @@ class SSmalltalkScanner { std::unique_ptr nextSpecialCharacter(); std::unique_ptr nextSymbolOrArrayPrefix(); - egg::string scanBinarySymbol(); + Egg::string scanBinarySymbol(); uint32_t scanChar(); - egg::string scanString(); + Egg::string scanString(); void skipBinary(); void skipIdentifier(); void skipKeyword(); @@ -75,8 +75,8 @@ class SSmalltalkScanner { void compiler_(SSmalltalkCompiler* compiler); std::unique_ptr next(); std::unique_ptr nextToken(); - void on_(const egg::string& source); - void sourceCode_(const egg::string& source); + void on_(const Egg::string& source); + void sourceCode_(const Egg::string& source); }; } // namespace Egg diff --git a/runtime/cpp/Compiler/Parser/SToken.h b/runtime/cpp/Compiler/Parser/SToken.h index f1b1b164..88899f56 100644 --- a/runtime/cpp/Compiler/Parser/SToken.h +++ b/runtime/cpp/Compiler/Parser/SToken.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -11,7 +11,7 @@ #include #include "../Stretch.h" #include "../Object.h" -#include "../egg_string.h" +#include "Utils/egg_string.h" namespace Egg { @@ -22,7 +22,7 @@ class SToken { protected: SSmalltalkCompiler* _compiler; Stretch _stretch; - std::vector _comments; + std::vector _comments; public: SToken() : _compiler(nullptr) {} @@ -37,14 +37,14 @@ class SToken { Stretch stretch() const { return _stretch; } void stretch_(const Stretch& pos) { _stretch = pos; } - virtual egg::string value() const { return ""; } - virtual void value_(const egg::string& val) {} + virtual Egg::string value() const { return ""; } + virtual void value_(const Egg::string& val) {} - void addComment_(const egg::string& comment) { + void addComment_(const Egg::string& comment) { _comments.push_back(comment); } - const std::vector& comments() const { return _comments; } + const std::vector& comments() const { return _comments; } void moveCommentsFrom_(SToken* other) { if (other) { @@ -67,8 +67,8 @@ class SToken { virtual bool is_(char ch) const { return false; } bool is(char ch) const { return is_(ch); } - virtual bool is_(const egg::string& str) const { return false; } - bool is(const egg::string& str) const { return is_(str); } + virtual bool is_(const Egg::string& str) const { return false; } + bool is(const Egg::string& str) const { return is_(str); } virtual bool endsExpression() const { return isEnd() || is_(']') || is_(')') || is_('}') || is_('.'); @@ -87,15 +87,15 @@ class SEndToken : public SToken { class SSymbolicToken : public SToken { protected: - egg::string _value; + Egg::string _value; bool _isSymbol; public: - SSymbolicToken(const Stretch& pos, const egg::string& val, bool isSymbol = false) + SSymbolicToken(const Stretch& pos, const Egg::string& val, bool isSymbol = false) : SToken(pos), _value(val), _isSymbol(isSymbol) {} - egg::string value() const override { return _value; } - void value_(const egg::string& val) override { _value = val; } + Egg::string value() const override { return _value; } + void value_(const Egg::string& val) override { _value = val; } void beSymbol_() { _isSymbol = true; } bool isSymbol() const { return _isSymbol; } @@ -112,7 +112,7 @@ class SSymbolicToken : public SToken { return _value.length() == 1 && _value[0] == (char32_t)ch; } - bool is_(const egg::string& str) const override { + bool is_(const Egg::string& str) const override { return _value == str; } @@ -122,7 +122,7 @@ class SSymbolicToken : public SToken { class SDelimiterToken : public SSymbolicToken { public: - SDelimiterToken(const Stretch& pos, const egg::string& val) + SDelimiterToken(const Stretch& pos, const Egg::string& val) : SSymbolicToken(pos, val, false) {} bool isDelimiter() const override { return true; } bool isSymbolic() const override { return false; } @@ -133,7 +133,7 @@ class SStringToken : public SSymbolicToken { public: enum LiteralKind { LitString, LitSymbol, LitNumber, LitCharacter }; - SStringToken(const Stretch& pos, const egg::string& val, LiteralKind kind = LitString) + SStringToken(const Stretch& pos, const Egg::string& val, LiteralKind kind = LitString) : SSymbolicToken(pos, val, false), _kind(kind) {} bool isLiteral() const override { return true; } bool isString() const override { return true; } @@ -142,7 +142,7 @@ class SStringToken : public SSymbolicToken { // Literal tokens should never match delimiter checks like is_('.') bool is_(char ch) const override { return false; } - bool is_(const egg::string& str) const override { return false; } + bool is_(const Egg::string& str) const override { return false; } LiteralKind literalKind() const { return _kind; } void literalKind_(LiteralKind k) { _kind = k; } diff --git a/runtime/cpp/Compiler/Parser/Stream.h b/runtime/cpp/Compiler/Parser/Stream.h index 9815d44b..5e324b9d 100644 --- a/runtime/cpp/Compiler/Parser/Stream.h +++ b/runtime/cpp/Compiler/Parser/Stream.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ @@ -9,20 +9,20 @@ #include #include #include -#include "../egg_string.h" +#include "Utils/egg_string.h" namespace Egg { class Stream { private: - egg::string _source; + Egg::string _source; size_t _position; public: Stream() : _position(0) {} - explicit Stream(const egg::string& source) : _source(source), _position(0) {} + explicit Stream(const Egg::string& source) : _source(source), _position(0) {} - void on_(const egg::string& source) { + void on_(const Egg::string& source) { _source = source; _position = 0; } @@ -53,18 +53,18 @@ class Stream { _position = pos; } - egg::string copyFrom_to_(size_t start, size_t end) const { + Egg::string copyFrom_to_(size_t start, size_t end) const { if (end > _source.length()) end = _source.length(); if (start > end) return ""; return _source.substr(start, end - start); } - egg::string upTo_(uint32_t delimiter) { + Egg::string upTo_(uint32_t delimiter) { size_t start = _position; while (!atEnd() && peek() != delimiter) { next(); } - egg::string result = copyFrom_to_(start, _position); + Egg::string result = copyFrom_to_(start, _position); if (!atEnd() && peek() == delimiter) { next(); } diff --git a/runtime/cpp/Compiler/SCompiler.cpp b/runtime/cpp/Compiler/SCompiler.cpp index b53b0f9a..1bd70453 100644 --- a/runtime/cpp/Compiler/SCompiler.cpp +++ b/runtime/cpp/Compiler/SCompiler.cpp @@ -1,10 +1,10 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #include "SCompiler.h" -#include "egg_string.h" +#include "Utils/egg_string.h" using namespace Egg; @@ -12,9 +12,9 @@ SCompiler::SCompiler() : _classBinding(nullptr) { } bool SCompiler::canStartIdentifier_(uint32_t ch) const { - return egg::isLetter(static_cast(ch)) || ch == '_'; + return Egg::isLetter(static_cast(ch)) || ch == '_'; } bool SCompiler::canBeInIdentifier_(uint32_t ch) const { - return egg::isLetter(static_cast(ch)) || egg::isDigit(static_cast(ch)) || ch == '_'; + return Egg::isLetter(static_cast(ch)) || Egg::isDigit(static_cast(ch)) || ch == '_'; } diff --git a/runtime/cpp/Compiler/SCompiler.h b/runtime/cpp/Compiler/SCompiler.h index 4b785a35..b15b5bb4 100644 --- a/runtime/cpp/Compiler/SCompiler.h +++ b/runtime/cpp/Compiler/SCompiler.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/SSmalltalkCompiler.cpp b/runtime/cpp/Compiler/SSmalltalkCompiler.cpp index 9a4f2e32..8915a0e3 100644 --- a/runtime/cpp/Compiler/SSmalltalkCompiler.cpp +++ b/runtime/cpp/Compiler/SSmalltalkCompiler.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #include "SSmalltalkCompiler.h" @@ -101,14 +101,14 @@ SCommentNode* SSmalltalkCompiler::commentNode() { return new SCommentNode(this); } -CompilationError* SSmalltalkCompiler::compilationError_stretch_(const egg::string& aString, Stretch* aStretch) { +CompilationError* SSmalltalkCompiler::compilationError_stretch_(const Egg::string& aString, Stretch* aStretch) { CompilationError* error = new CompilationError(aString); error->compiler_(this); error->stretch_(aStretch); return error; } -CompilationResult* SSmalltalkCompiler::compileMethod_(const egg::string& aString) { +CompilationResult* SSmalltalkCompiler::compileMethod_(const Egg::string& aString) { _source = aString; if (_frontend) { parseMethod(); @@ -130,12 +130,12 @@ SEndToken* SSmalltalkCompiler::endToken() { return new SEndToken(Stretch(0, 0)); } -CompilationError* SSmalltalkCompiler::error_at_(const egg::string& aString, int anInteger) { +CompilationError* SSmalltalkCompiler::error_at_(const Egg::string& aString, int anInteger) { Stretch* stretch = new Stretch(anInteger, anInteger); return error_stretch_(aString, stretch); } -CompilationError* SSmalltalkCompiler::error_stretch_(const egg::string& aString, Stretch* aStretch) { +CompilationError* SSmalltalkCompiler::error_stretch_(const Egg::string& aString, Stretch* aStretch) { auto error = compilationError_stretch_(aString, aStretch); error->beFatal(); throw *error; @@ -198,7 +198,7 @@ void SSmalltalkCompiler::parseFragment() { } } -SMethodNode* SSmalltalkCompiler::parseFragment_(const egg::string& aString) { +SMethodNode* SSmalltalkCompiler::parseFragment_(const Egg::string& aString) { _source = aString; try { parseFragment(); @@ -215,7 +215,7 @@ void SSmalltalkCompiler::parseMethod() { if (_result) _result->ast_(_ast); } -CompilationResult* SSmalltalkCompiler::parseMethod_(const egg::string& aString) { +CompilationResult* SSmalltalkCompiler::parseMethod_(const Egg::string& aString) { _source = aString; if (_frontend) { parseMethod(); @@ -270,11 +270,11 @@ SSelectorNode* SSmalltalkCompiler::selectorNode() { return new SSelectorNode(this); } -egg::string SSmalltalkCompiler::sourceCode() { +Egg::string SSmalltalkCompiler::sourceCode() { return _source; } -void SSmalltalkCompiler::sourceCode_(const egg::string& aString) { +void SSmalltalkCompiler::sourceCode_(const Egg::string& aString) { _source = aString; } @@ -286,7 +286,7 @@ bool SSmalltalkCompiler::supportsBraceNodes() { return true; } -void SSmalltalkCompiler::warning_at_(const egg::string& aString, Stretch* aStretch) { +void SSmalltalkCompiler::warning_at_(const Egg::string& aString, Stretch* aStretch) { auto error = compilationError_stretch_(aString, aStretch); error->beWarning(); throw *error; diff --git a/runtime/cpp/Compiler/SSmalltalkCompiler.h b/runtime/cpp/Compiler/SSmalltalkCompiler.h index 6cf0a3a6..bb918124 100644 --- a/runtime/cpp/Compiler/SSmalltalkCompiler.h +++ b/runtime/cpp/Compiler/SSmalltalkCompiler.h @@ -1,11 +1,11 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ #ifndef _SSMALLTALKCOMPILER_H_ #define _SSMALLTALKCOMPILER_H_ #include "SCompiler.h" -#include "egg_string.h" +#include "Utils/egg_string.h" #include #include @@ -47,7 +47,7 @@ class SSmalltalkCompiler { SCompiler* _frontend; std::unique_ptr _scanner; std::unique_ptr _parser; - egg::string _source; + Egg::string _source; SMethodNode* _ast; CompilationResult* _result; bool _headless; @@ -73,12 +73,12 @@ class SSmalltalkCompiler { SCascadeMessageNode* cascadeSMessageNode(); SCascadeNode* cascadeNode(); SCommentNode* commentNode(); - CompilationError* compilationError_stretch_(const egg::string& aString, Stretch* aStretch); - CompilationResult* compileMethod_(const egg::string& aString); + CompilationError* compilationError_stretch_(const Egg::string& aString, Stretch* aStretch); + CompilationResult* compileMethod_(const Egg::string& aString); SToken* delimiterToken(); SEndToken* endToken(); - CompilationError* error_at_(const egg::string& aString, int anInteger); - CompilationError* error_stretch_(const egg::string& aString, Stretch* aStretch); + CompilationError* error_at_(const Egg::string& aString, int anInteger); + CompilationError* error_stretch_(const Egg::string& aString, Stretch* aStretch); SCompiler* frontend(); void frontend_(SCompiler* aSCompiler); bool hasBlocks(); @@ -91,9 +91,9 @@ class SSmalltalkCompiler { void noticeSend(); SNumberNode* numericSLiteralNode(); void parseFragment(); - SMethodNode* parseFragment_(const egg::string& aString); + SMethodNode* parseFragment_(const Egg::string& aString); void parseMethod(); - CompilationResult* parseMethod_(const egg::string& aString); + CompilationResult* parseMethod_(const Egg::string& aString); SSmalltalkParser* parser(); SPragmaNode* pragmaNode(); void reset(); @@ -103,11 +103,11 @@ class SSmalltalkCompiler { SReturnNode* returnNode(); SSmalltalkScanner* scanner(); SSelectorNode* selectorNode(); - egg::string sourceCode(); - void sourceCode_(const egg::string& aString); + Egg::string sourceCode(); + void sourceCode_(const Egg::string& aString); SStringToken* stringToken(); bool supportsBraceNodes(); - void warning_at_(const egg::string& aString, Stretch* aStretch); + void warning_at_(const Egg::string& aString, Stretch* aStretch); }; } // namespace Egg diff --git a/runtime/cpp/Compiler/SemanticVisitor.cpp b/runtime/cpp/Compiler/SemanticVisitor.cpp index 98932395..3599cdb0 100644 --- a/runtime/cpp/Compiler/SemanticVisitor.cpp +++ b/runtime/cpp/Compiler/SemanticVisitor.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/SemanticVisitor.h b/runtime/cpp/Compiler/SemanticVisitor.h index d36e2b42..21d17e5f 100644 --- a/runtime/cpp/Compiler/SemanticVisitor.h +++ b/runtime/cpp/Compiler/SemanticVisitor.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/Stretch.h b/runtime/cpp/Compiler/Stretch.h index 50a1e0fd..ddcf6119 100644 --- a/runtime/cpp/Compiler/Stretch.h +++ b/runtime/cpp/Compiler/Stretch.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2025, Javier Pimás. + Copyright (c) 2025-2026, Javier Pimás. See (MIT) license in root directory. */ diff --git a/runtime/cpp/Compiler/TreecodeEncoder.cpp b/runtime/cpp/Compiler/TreecodeEncoder.cpp index 66bdda7d..82078693 100644 --- a/runtime/cpp/Compiler/TreecodeEncoder.cpp +++ b/runtime/cpp/Compiler/TreecodeEncoder.cpp @@ -31,7 +31,7 @@ void TreecodeEncoder::visitMethod_(SMethodNode* node) { if (node->pragma() && node->pragma()->isUsed()) { nextTypePut(PragmaId); - const egg::string& pragmaName = node->pragma()->name(); + const Egg::string& pragmaName = node->pragma()->name(); if (!pragmaName.empty()) { nextSymbolPut(pragmaName); } else { @@ -75,7 +75,7 @@ void TreecodeEncoder::visitMessage_(SMessageNode* node) { nextBooleanPut(node->isInlined()); - egg::string selectorStr = node->selector()->symbol(); + Egg::string selectorStr = node->selector()->symbol(); nextSymbolPut(selectorStr); if (node->receiver()) { @@ -164,7 +164,7 @@ void TreecodeEncoder::visitCascade_(SCascadeNode* node) { nextIntegerPut(messages.size()); for (auto* msg : messages) { - egg::string selectorStr = msg->selector()->symbol(); + Egg::string selectorStr = msg->selector()->symbol(); nextSymbolPut(selectorStr); @@ -242,14 +242,14 @@ void TreecodeEncoder::nextBooleanPut(bool value) { nextPut(value ? 1 : 0); } -void TreecodeEncoder::nextSymbolPut(const egg::string& symbol) { +void TreecodeEncoder::nextSymbolPut(const Egg::string& symbol) { auto symLv = LiteralValue::fromSymbol(symbol); int index = _method->indexOf(symLv); ASSERT(index != 0); nextIntegerPut(index); } -void TreecodeEncoder::nextLiteralPut(const egg::string& literal) { +void TreecodeEncoder::nextLiteralPut(const Egg::string& literal) { auto litLv = LiteralValue::fromString(literal); int index = _method->indexOf(litLv); ASSERT(index != 0); @@ -333,12 +333,12 @@ void TreecodeEncoder::encodePopR() { stream_.push_back(PopRid); } -void TreecodeEncoder::encodeDynamicVar_(const egg::string& name) { +void TreecodeEncoder::encodeDynamicVar_(const Egg::string& name) { stream_.push_back(DynamicVarId); nextSymbolPut(name); } -void TreecodeEncoder::encodeNestedDynamicVar_(const egg::string& name) { +void TreecodeEncoder::encodeNestedDynamicVar_(const Egg::string& name) { stream_.push_back(NestedDynamicVarId); nextLiteralPut(name); } diff --git a/runtime/cpp/Compiler/TreecodeEncoder.h b/runtime/cpp/Compiler/TreecodeEncoder.h index 3aea2153..91b2a4f3 100644 --- a/runtime/cpp/Compiler/TreecodeEncoder.h +++ b/runtime/cpp/Compiler/TreecodeEncoder.h @@ -75,8 +75,8 @@ class TreecodeEncoder : public SParseNodeVisitor { void encodeSuper(); void encodePushR(); void encodePopR(); - void encodeDynamicVar_(const egg::string& name); - void encodeNestedDynamicVar_(const egg::string& name); + void encodeDynamicVar_(const Egg::string& name); + void encodeNestedDynamicVar_(const Egg::string& name); void encodeArgument_env_(int index, LocalEnvironment* environment); void encodeTemporary_env_(int index, LocalEnvironment* environment); @@ -90,8 +90,8 @@ class TreecodeEncoder : public SParseNodeVisitor { void nextIntegerPut(int64_t value); void nextBigIntegerPut(int64_t value); void nextBooleanPut(bool value); - void nextSymbolPut(const egg::string& symbol); - void nextLiteralPut(const egg::string& literal); + void nextSymbolPut(const Egg::string& symbol); + void nextLiteralPut(const Egg::string& literal); void nextTypePut(uint8_t typeId); int encodedEnvironment_(LocalEnvironment* environment); diff --git a/runtime/cpp/Compiler/egg_string.h b/runtime/cpp/Utils/egg_string.h similarity index 93% rename from runtime/cpp/Compiler/egg_string.h rename to runtime/cpp/Utils/egg_string.h index 7abd3610..cda1c866 100644 --- a/runtime/cpp/Compiler/egg_string.h +++ b/runtime/cpp/Utils/egg_string.h @@ -11,7 +11,7 @@ #include #include -namespace egg { +namespace Egg { /** * Unicode string type for the Egg compiler pipeline. @@ -111,13 +111,29 @@ class string : public std::u32string { return result; } - // ---- Override methods that return std::u32string to return egg::string ---- + // ---- Latin-1 support ---- + + bool isLatin1() const { + for (char32_t cp : *this) + if (cp > 0xFF) return false; + return true; + } + + // Returns a new[]-allocated Latin-1 byte array. Caller must delete[]. + char* toBytes() const { + char* bytes = new char[size()]; + for (size_type i = 0; i < size(); i++) + bytes[i] = static_cast((*this)[i]); + return bytes; + } + + // ---- Override methods that return std::u32string to return Egg::string ---- string substr(size_type pos = 0, size_type count = npos) const { return string(std::u32string::substr(pos, count)); } - // ---- Concatenation returning egg::string ---- + // ---- Concatenation returning Egg::string ---- string operator+(const string& other) const { return string(static_cast(*this) + @@ -259,13 +275,13 @@ inline bool isUppercase(char32_t ch) { return false; } -} // namespace egg +} // namespace Egg -// Allow egg::string as key in std::unordered_map +// Allow Egg::string as key in std::unordered_map namespace std { template<> -struct hash { - size_t operator()(const egg::string& s) const { +struct hash { + size_t operator()(const Egg::string& s) const { return hash()(s); } }; From 878716f3f57378c3bf3bb63ceb9cd19e0e2e827d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Wed, 18 Mar 2026 00:49:04 -0300 Subject: [PATCH 09/15] Refactor ImageSegment into base class + FileImageSegment Extract ImageSegment as a lightweight base class providing only the common interface (header, memory bounds, exports, dumpObjects) used by Runtime. Move file-loading logic (load, fixPointerSlots, import resolution) into a new FileImageSegment subclass. BootstrappedKernel now extends ImageSegment directly, eliminating the null-stream constructor hack and dummy header workaround. --- runtime/cpp/Bootstrap/BootstrappedKernel.cpp | 31 +- runtime/cpp/Bootstrap/BootstrappedKernel.h | 4 - runtime/cpp/Bootstrap/CMakeLists.txt | 33 ++ runtime/cpp/Bootstrap/SourceModuleLoader.cpp | 346 +++++++++++++++++++ runtime/cpp/Bootstrap/SourceModuleLoader.h | 51 +++ runtime/cpp/Compiler/Binding/CMakeLists.txt | 11 + runtime/cpp/Compiler/CMakeLists.txt | 15 + runtime/cpp/Evaluator/Runtime.h | 30 +- runtime/cpp/FileImageSegment.cpp | 166 +++++++++ runtime/cpp/FileImageSegment.h | 56 +++ runtime/cpp/ImageSegment.cpp | 174 ---------- runtime/cpp/ImageSegment.h | 44 +-- runtime/cpp/Launcher.cpp | 48 +-- runtime/cpp/Launcher.h | 2 - runtime/cpp/Loader.cpp | 18 +- runtime/cpp/Loader.h | 16 +- 16 files changed, 720 insertions(+), 325 deletions(-) create mode 100644 runtime/cpp/Bootstrap/CMakeLists.txt create mode 100644 runtime/cpp/Bootstrap/SourceModuleLoader.cpp create mode 100644 runtime/cpp/Bootstrap/SourceModuleLoader.h create mode 100644 runtime/cpp/Compiler/Binding/CMakeLists.txt create mode 100644 runtime/cpp/Compiler/CMakeLists.txt create mode 100644 runtime/cpp/FileImageSegment.cpp create mode 100644 runtime/cpp/FileImageSegment.h diff --git a/runtime/cpp/Bootstrap/BootstrappedKernel.cpp b/runtime/cpp/Bootstrap/BootstrappedKernel.cpp index 61a111ed..45645943 100644 --- a/runtime/cpp/Bootstrap/BootstrappedKernel.cpp +++ b/runtime/cpp/Bootstrap/BootstrappedKernel.cpp @@ -4,41 +4,16 @@ */ #include "BootstrappedKernel.h" -#include #include namespace Egg { -std::istringstream BootstrappedKernel::createDummyStream() { - // Create a minimal valid EGG_IS stream so ImageSegment constructor doesn't crash. - // We'll override all the header fields after construction. - ImageSegmentHeader dummyHeader; - memset(&dummyHeader, 0, sizeof(dummyHeader)); - - // Set signature - const char* sig = "EGG_IS\n"; - memcpy(dummyHeader.signature, sig, 8); - - // Set minimal size (just the header) - dummyHeader.size = sizeof(ImageSegmentHeader); - dummyHeader.reservedSize = sizeof(ImageSegmentHeader); - dummyHeader.baseAddress = 0; - dummyHeader.module = nullptr; - - std::string data(reinterpret_cast(&dummyHeader), sizeof(dummyHeader)); - return std::istringstream(data); -} - BootstrappedKernel::BootstrappedKernel(uintptr_t base, uintptr_t size, uintptr_t objectsEnd) - : ImageSegment(nullptr) // bypass load - we pass nullptr to skip it { - // Override the header with actual bootstrap segment info - const char* sig = "EGG_IS\n"; - memcpy(header.signature, sig, 8); - header.baseAddress = base; - // Offset _currentBase so that spaceStart() (= _currentBase + sizeof(ImageSegmentHeader)) - // returns 'base', where bootstrap objects actually begin (no on-disk header in memory). + // Bootstrap objects live directly in memory with no prefixed header, + // so we offset _currentBase so that spaceStart() returns 'base'. _currentBase = base - sizeof(ImageSegmentHeader); + header.baseAddress = base; header.size = objectsEnd - _currentBase; header.reservedSize = size + sizeof(ImageSegmentHeader); header.module = nullptr; diff --git a/runtime/cpp/Bootstrap/BootstrappedKernel.h b/runtime/cpp/Bootstrap/BootstrappedKernel.h index 1893f942..f8a471fc 100644 --- a/runtime/cpp/Bootstrap/BootstrappedKernel.h +++ b/runtime/cpp/Bootstrap/BootstrappedKernel.h @@ -7,7 +7,6 @@ #define _BOOTSTRAPPED_KERNEL_H_ #include "../ImageSegment.h" -#include #include namespace Egg { @@ -23,9 +22,6 @@ class BootstrappedKernel : public ImageSegment { void addExport(const std::string& name, HeapObject* obj) { _exports[name] = obj; } - -private: - static std::istringstream createDummyStream(); }; } // namespace Egg diff --git a/runtime/cpp/Bootstrap/CMakeLists.txt b/runtime/cpp/Bootstrap/CMakeLists.txt new file mode 100644 index 00000000..ec2af2e7 --- /dev/null +++ b/runtime/cpp/Bootstrap/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.10) +project(KernelBootstrapper) + +# Create bootstrapper library +add_library(bootstrapper_lib + CodeSpecs.cpp + Bootstrapper.cpp + SourceModuleLoader.cpp + BootstrappedKernel.cpp + TonelReader.cpp +) + +# Include directories +target_include_directories(bootstrapper_lib PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +# Link bootstrapper with runtime +target_link_libraries(bootstrapper_lib PUBLIC egg_runtime) + +# Create bootstrap demonstration executable +add_executable(bootstrap_demo + bootstrap_demo.cpp +) + +# Link demo with bootstrapper library and compiler +target_link_libraries(bootstrap_demo + bootstrapper_lib + egg_compiler +) + +# Add tests subdirectory +add_subdirectory(tests) diff --git a/runtime/cpp/Bootstrap/SourceModuleLoader.cpp b/runtime/cpp/Bootstrap/SourceModuleLoader.cpp new file mode 100644 index 00000000..9cd0f3a0 --- /dev/null +++ b/runtime/cpp/Bootstrap/SourceModuleLoader.cpp @@ -0,0 +1,346 @@ +/* + Copyright (c) 2025-2026, Javier Pimás. + See (MIT) license in root directory. + */ + +#include "SourceModuleLoader.h" +#include "TonelReader.h" +#include "../Compiler/SSmalltalkCompiler.h" +#include "../Compiler/LiteralValue.h" +#include "../Compiler/Backend/SCompiledMethod.h" +#include "../Compiler/CompilationResult.h" +#include "../Evaluator/Runtime.h" +#include "../KnownConstants.h" +#include "../GCedRef.h" +#include +#include +#include +#include +#include + +namespace Egg { + +SourceModuleLoader::SourceModuleLoader(Runtime* runtime) + : _runtime(runtime) { + _compiler = std::make_unique(); +} + +SourceModuleLoader::~SourceModuleLoader() { +} + +Object* SourceModuleLoader::kernelNamespace_() { + auto kernelModule = (Object*)_runtime->_kernel->_exports["__module__"]; + auto ns = _runtime->sendLocal_to_("namespace", kernelModule); + return ns; +} + +Object* SourceModuleLoader::lookupClass_(const Egg::string& name) { + auto namespace_ = kernelNamespace_(); + auto symbol = (Object*)_runtime->addSymbol_(name.toUtf8()); + return _runtime->sendLocal_to_with_("at:", namespace_, symbol); +} + +HeapObject* SourceModuleLoader::loadModuleFromSource(const std::string& modulePath) { + namespace fs = std::filesystem; + TonelReader reader; + + // Phase 1: Parse all .st files in the module directory + std::vector newClassNames; + for (const auto& entry : fs::directory_iterator(modulePath)) { + if (entry.path().extension() == ".st") { + std::string filename = entry.path().filename().string(); + std::string className = filename.substr(0, filename.length() - 3); + if (className == "package") continue; + + std::ifstream file(entry.path()); + if (!file.is_open()) continue; + std::string source((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + ClassSpec* spec = reader.parseFile(source); + _moduleSpec.addClass(spec); + newClassNames.push_back(spec->name()); + } + } + + // Phase 2: Find module class (subclass of Module) + Egg::string moduleClassName; + for (const auto& name : newClassNames) { + ClassSpec* spec = _moduleSpec.resolveClass(name); + Egg::string sup = spec->supername(); + while (!sup.empty() && sup != "nil") { + if (sup == "Module") { + moduleClassName = name; + break; + } + ClassSpec* superSpec = _moduleSpec.resolveClass(sup); + if (!superSpec) break; + sup = superSpec->supername(); + } + if (!moduleClassName.empty()) break; + } + + if (moduleClassName.empty()) { + std::cerr << "ERROR: No Module subclass found in " << modulePath << std::endl; + std::exit(1); + } + + // Phase 3: Create module class and instance + auto moduleSpec = _moduleSpec.resolveClass(moduleClassName); + auto moduleClass = createNewClassFrom_(moduleSpec, nullptr); + + GCedRef moduleClassRef(moduleClass); + createMethodsOf_(moduleClassRef.get(), moduleSpec); + auto moduleInstance = _runtime->sendLocal_to_("new", moduleClassRef.get()); + GCedRef moduleRef(moduleInstance); + std::string modName = fs::path(modulePath).filename().string(); + auto nameObj = (Object*)_runtime->newString_(modName); + _runtime->sendLocal_to_with_("name:", moduleRef.get(), nameObj); + + // Now set the module class's module to the new instance + _runtime->sendLocal_to_with_("module:", moduleClassRef.get(), moduleRef.get()); + _runtime->sendLocal_to_with_("addClass:", moduleRef.get(), moduleClassRef.get()); + + // Phase 4: Create remaining classes (sorted so superclasses come first) + // Simple topological sort: process classes whose superclass is already available + std::vector remaining; + // Collect module-defined class names for dependency checking + std::set moduleClassNames; + for (const auto& name : newClassNames) { + moduleClassNames.insert(name); + if (name != moduleClassName) + remaining.push_back(name); + } + + std::vector sorted; + std::set created; + created.insert(moduleClassName); // Module class already created + + while (!remaining.empty()) { + bool progress = false; + for (auto it = remaining.begin(); it != remaining.end(); ) { + auto spec = _moduleSpec.resolveClass(*it); + auto sup = spec->supername(); + // Superclass is available if it's already created or is not a module class + // (i.e., it's a kernel class, which is always available) + bool available = !moduleClassNames.count(sup) || created.count(sup); + if (available) { + sorted.push_back(*it); + created.insert(*it); + it = remaining.erase(it); + progress = true; + } else { + ++it; + } + } + if (!progress) { + // Remaining classes have unresolved superclasses, add them anyway + for (const auto& name : remaining) + sorted.push_back(name); + break; + } + } + + for (const auto& name : sorted) { + ClassSpec* spec = _moduleSpec.resolveClass(name); + auto cls = createNewClassFrom_(spec, moduleRef.get()); + GCedRef clsRef(cls); + createMethodsOf_(clsRef.get(), spec); + } + + // Phase 5: Set up module namespace and finalize + _runtime->sendLocal_to_("bindKernelExports", moduleRef.get()); + _runtime->sendLocal_to_("importRequiredModules", moduleRef.get()); + _runtime->sendLocal_to_("justLoaded", moduleRef.get()); + + return moduleRef.get()->asHeapObject(); +} + +Object* SourceModuleLoader::createNewClassFrom_(ClassSpec* spec, Object* module) { + // Look up superclass + auto supername = spec->supername(); + Object* superclass; + if (module) { + auto namespace_ = _runtime->sendLocal_to_("namespace", module); + auto superSymbol = (Object*)_runtime->addSymbol_(supername.toUtf8()); + superclass = _runtime->sendLocal_to_with_("at:", namespace_, superSymbol); + } else { + superclass = lookupClass_(supername); + } + + // Create class: Class newSubclassOf: superclass + auto classClass = lookupClass_("Class"); + auto newClass = _runtime->sendLocal_to_with_("newSubclassOf:", classClass, superclass); + GCedRef classRef(newClass); + + // Set name + auto nameStr = (Object*)_runtime->newString_(spec->name().toUtf8()); + _runtime->sendLocal_to_with_("name:", classRef.get(), nameStr); + + // Set instance variables + const auto& ivarNames = spec->instVarNames(); + if (!ivarNames.empty()) { + std::vector ivars; + for (const auto& ivar : ivarNames) { + ivars.push_back((Object*)_runtime->addSymbol_(ivar.toUtf8())); + } + auto ivarArray = (Object*)_runtime->newArray_(ivars); + _runtime->sendLocal_to_with_("instVarNames:", classRef.get(), ivarArray); + } + + // Set bytes flag if needed + if (!spec->isPointers()) { + _runtime->sendLocal_to_("beBytes", classRef.get()); + } + + // Set class variables + const auto& classVarNames = spec->classVarNames(); + if (!classVarNames.empty()) { + auto namespaceClass = lookupClass_("Namespace"); + auto ns = _runtime->sendLocal_to_("new", namespaceClass); + GCedRef nsRef(ns); + for (const auto& cvar : classVarNames) { + auto key = (Object*)_runtime->addSymbol_(cvar.toUtf8()); + _runtime->sendLocal_to_with_with_("at:put:", nsRef.get(), key, (Object*)_runtime->_nilObj); + } + _runtime->sendLocal_to_with_("classVariables:", classRef.get(), nsRef.get()); + } + + // Add class to module + if (module) { + _runtime->sendLocal_to_with_("addClass:", module, classRef.get()); + _runtime->sendLocal_to_with_("module:", classRef.get(), module); + } + + return classRef.get(); +} + +void SourceModuleLoader::createMethodsOf_(Object* cls, ClassSpec* spec) { + GCedRef clsRef(cls); + + // Instance methods + for (const auto& method : spec->methods()) { + createNewMethod_(method.source(), clsRef.get()); + } + + // Class methods + auto metaclass = _runtime->sendLocal_to_("class", clsRef.get()); + GCedRef metaRef(metaclass); + for (const auto& method : spec->metaclass()->methods()) { + createNewMethod_(method.source(), metaRef.get()); + } +} + +void SourceModuleLoader::createNewMethod_(const Egg::string& source, Object* species) { + CompilationResult* result = _compiler->compileMethod_(source); + SCompiledMethod* smethod = static_cast(result->method()); + if (!smethod) { + std::cerr << "ERROR: Failed to compile method from source: '" + << source.substr(0, std::min(source.length(), size_t(60))) << "...'" << std::endl; + return; + } + + GCedRef speciesRef(species); + + const auto& treecodes = smethod->treecodes(); + const auto& literals = smethod->literals(); + uint32_t literalCount = literals.size(); + + // Create compiled method: CompiledMethod new: literalCount + auto cmClass = lookupClass_("CompiledMethod"); + auto size = (Object*)_runtime->newInteger_(literalCount); + auto method = _runtime->sendLocal_to_with_("new:", cmClass, size); + GCedRef methodRef(method); + + // Set method fields + auto format = (Object*)_runtime->newInteger_(smethod->format()); + _runtime->sendLocal_to_with_("format:", methodRef.get(), format); + + auto selector = (Object*)_runtime->addSymbol_(smethod->selector().toUtf8()); + _runtime->sendLocal_to_with_("selector:", methodRef.get(), selector); + + // Create treecodes ByteArray + auto baClass = _runtime->_kernel->_exports["ByteArray"]; + auto ba = _runtime->newBytes_size_(baClass, treecodes.size()); + std::memcpy((void*)ba, treecodes.data(), treecodes.size()); + _runtime->sendLocal_to_with_("treecodes:", methodRef.get(), (Object*)ba); + + _runtime->sendLocal_to_with_("classBinding:", methodRef.get(), speciesRef.get()); + + auto sourceObj = (Object*)_runtime->newString_(source.toUtf8()); + _runtime->sendLocal_to_with_("sourceObject:", methodRef.get(), sourceObj); + + // Transfer literals + for (uint32_t i = 0; i < literalCount; i++) { + auto literal = transferLiteral_(literals[i], methodRef.get()); + methodRef.get()->asHeapObject()->slot(Offsets::MethodInstSize + i) = literal; + } + + // Install method: species addSelector: selector withMethod: method + _runtime->sendLocal_to_with_with_("addSelector:withMethod:", speciesRef.get(), selector, methodRef.get()); +} + +Object* SourceModuleLoader::transferLiteral_(const LiteralValue& lit, Object* method) { + switch (lit.tag) { + case LiteralValue::Symbol: + return (Object*)_runtime->addSymbol_(lit.asString().toUtf8()); + case LiteralValue::String: + return (Object*)_runtime->newString_(lit.asString().toUtf8()); + case LiteralValue::Integer: + return (Object*)_runtime->newInteger_(lit.asInteger()); + case LiteralValue::Float: + return (Object*)_runtime->newDouble_(lit.asFloat()); + case LiteralValue::Character: + return (Object*)_runtime->newInteger_((intptr_t)lit.asCharacter()); + case LiteralValue::Boolean: + return (Object*)(lit.asBoolean() ? _runtime->_trueObj : _runtime->_falseObj); + case LiteralValue::Nil: + return (Object*)_runtime->_nilObj; + case LiteralValue::Array: + return (Object*)transferArray_(lit.asArray()); + case LiteralValue::ByteArray: { + auto& bytes = lit.asByteArray(); + auto baClass = _runtime->_kernel->_exports["ByteArray"]; + auto ba = _runtime->newBytes_size_(baClass, bytes.size()); + std::memcpy((void*)ba, bytes.data(), bytes.size()); + return (Object*)ba; + } + case LiteralValue::Block: + return transferBlock_(lit.asBlock(), method); + default: + error("transferLiteral_: unimplemented literal tag"); + return (Object*)_runtime->_nilObj; + } +} + +Object* SourceModuleLoader::transferArray_(const std::vector& elements) { + auto arr = _runtime->newArraySized_(elements.size()); + GCedRef arrRef((Object*)arr); + auto arrayBehavior = _runtime->speciesInstanceBehavior_(_runtime->_kernel->_exports["Array"]); + arrRef.get()->asHeapObject()->behavior(arrayBehavior); + for (size_t i = 0; i < elements.size(); i++) { + arrRef.get()->asHeapObject()->slot(i) = transferLiteral_(elements[i], nullptr); + } + return arrRef.get(); +} + +Object* SourceModuleLoader::transferBlock_(const LiteralValue::BlockInfo& info, Object* method) { + auto blockClass = _runtime->_kernel->_exports["CompiledBlock"]; + auto block = _runtime->newSlotsOf_(blockClass); + GCedRef blockRef((Object*)block); + + uint32_t format = (info.argCount & 0x3F) + | ((info.tempCount & 0xFF) << 6) + | ((info.id & 0xFF) << 14) + | (info.capturesSelf ? 0x400000 : 0) + | (info.capturesHome ? 0x800000 : 0) + | ((info.envCount & 0x7F) << 24); + + blockRef.get()->asHeapObject()->slot(Offsets::BlockFormat) = (Object*)_runtime->newInteger_(format); + blockRef.get()->asHeapObject()->slot(Offsets::BlockExecutableCode) = (Object*)_runtime->_nilObj; + blockRef.get()->asHeapObject()->slot(Offsets::BlockMethod) = method; + + return blockRef.get(); +} + +} // namespace Egg diff --git a/runtime/cpp/Bootstrap/SourceModuleLoader.h b/runtime/cpp/Bootstrap/SourceModuleLoader.h new file mode 100644 index 00000000..3d1c1b18 --- /dev/null +++ b/runtime/cpp/Bootstrap/SourceModuleLoader.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2025-2026, Javier Pimás. + See (MIT) license in root directory. + */ + +#ifndef _SOURCE_MODULE_LOADER_H_ +#define _SOURCE_MODULE_LOADER_H_ + +#include +#include +#include +#include "../HeapObject.h" +#include "Utils/egg_string.h" +#include "../Compiler/LiteralValue.h" +#include "CodeSpecs.h" + +namespace Egg { + +class Runtime; +class SSmalltalkCompiler; + +class SourceModuleLoader { + Runtime* _runtime; + ModuleSpec _moduleSpec; + std::unique_ptr _compiler; + +public: + SourceModuleLoader(Runtime* runtime); + ~SourceModuleLoader(); + + HeapObject* loadModuleFromSource(const std::string& modulePath); + +private: + // Class creation via runtime messages + Object* createNewClassFrom_(ClassSpec* spec, Object* module); + void createMethodsOf_(Object* cls, ClassSpec* spec); + void createNewMethod_(const Egg::string& source, Object* species); + + // Literal transfer helpers + Object* transferLiteral_(const LiteralValue& lit, Object* method); + Object* transferBlock_(const LiteralValue::BlockInfo& blockInfo, Object* method); + Object* transferArray_(const std::vector& elements); + + // Lookup helpers + Object* lookupClass_(const Egg::string& name); + Object* kernelNamespace_(); +}; + +} // namespace Egg + +#endif // _SOURCE_MODULE_LOADER_H_ diff --git a/runtime/cpp/Compiler/Binding/CMakeLists.txt b/runtime/cpp/Compiler/Binding/CMakeLists.txt new file mode 100644 index 00000000..0f895739 --- /dev/null +++ b/runtime/cpp/Compiler/Binding/CMakeLists.txt @@ -0,0 +1,11 @@ +set(BINDING_SRC + Binding.h + VariableBinding.h + ArgumentBinding.h + TemporaryBinding.h + FieldBinding.h + MethodBinding.h + ClassBinding.h + GlobalBinding.h + ConstantBinding.h +) diff --git a/runtime/cpp/Compiler/CMakeLists.txt b/runtime/cpp/Compiler/CMakeLists.txt new file mode 100644 index 00000000..ec5bbffa --- /dev/null +++ b/runtime/cpp/Compiler/CMakeLists.txt @@ -0,0 +1,15 @@ +# Compiler library +file(GLOB COMPILER_SRC + "*.cpp" + "Parser/*.cpp" + "AST/*.cpp" + "Binding/*.cpp" + "Backend/*.cpp" +) + +add_library(egg_compiler STATIC ${COMPILER_SRC}) +target_include_directories(egg_compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +# Tests +enable_testing() +add_subdirectory(tests) diff --git a/runtime/cpp/Evaluator/Runtime.h b/runtime/cpp/Evaluator/Runtime.h index 21a6eff3..9869bdc6 100644 --- a/runtime/cpp/Evaluator/Runtime.h +++ b/runtime/cpp/Evaluator/Runtime.h @@ -11,6 +11,7 @@ #include "KnownConstants.h" #include "PlatformCode.h" #include "GCedRef.h" +#include "SymbolProvider.h" namespace Egg { @@ -19,7 +20,7 @@ class SAbstractMessage; class GCHeap; class SExpression; class Runtime; -class Bootstrapper; +class Loader; extern Runtime *debugRuntime; @@ -27,12 +28,11 @@ class Runtime { std::string printGlobalCache(); public: void checkCache(); - Bootstrapper *_bootstrapper; + Loader *_loader; ImageSegment *_kernel; Evaluator *_evaluator; GCHeap *_heap; - - std::map _knownSymbols; + SymbolProvider *_symbolProvider; //typedef std::vector inline_cache; std::map *, GCedRef::Comparator > _inlineCaches; @@ -44,7 +44,7 @@ class Runtime { uint16_t _lastHash; public: - Runtime(Bootstrapper *bootstrapper, ImageSegment *kernel); + Runtime(Loader *loader, ImageSegment *kernel, SymbolProvider *symbolProvider); std::string print_(HeapObject* obj); void log_code_(std::string &string, uintptr_t code); @@ -59,13 +59,16 @@ class Runtime { Object* sendLocal_to_withArgs_(const std::string &selector, Object *receiver, std::vector &arguments); Object* sendLocal_to_with_(const std::string &selector, Object *receiver, Object *arg1); Object* sendLocal_to_with_with_(const std::string &selector, Object *receiver, Object *arg1, Object* arg2); + + // Overloads taking a symbol Object* directly + Object* send_to_(Object *symbol, Object *receiver); + Object* send_to_with_(Object *symbol, Object *receiver, Object *arg1); Object* lookup_startingAt_(Object *symbol, HeapObject *behavior); Object* doLookup_startingAt_(Object *symbol, HeapObject *behavior); Object* methodFor_in_(Object *symbol, HeapObject *behavior); Object* existingSymbolFrom_(const std::string &selector); - Object* symbolTableAt_(const std::string &selector); HeapObject* lookupAssociationFor_in_(Object *symbol, HeapObject *dictionary); @@ -100,9 +103,7 @@ class Runtime { HeapObject* newExecutableCodeFor_with_(HeapObject *compiledCode, PlatformCode *platformCode); HeapObject* newString_(const std::string &str); HeapObject* addSymbol_(const std::string &str); - void addKnownSymbol_(const std::string &str, Object *symbol) { - _knownSymbols[str] = new GCedRef(symbol); - } + void switchToDynamicSymbolProvider_(HeapObject* symbolTable); HeapObject* loadModule_(HeapObject *name); void addSegmentSpace_(ImageSegment *segment); @@ -507,17 +508,12 @@ class Runtime { this->_behaviorClass = _kernel->_exports["Behavior"]; this->_ephemeronClass = _kernel->_exports["Ephemeron"]; this->_processStackClass = _kernel->_exports["ProcessVMStack"]; - this->_symbolTable = _kernel->_exports["SymbolTable"]; + this->_openHashTableClass = _kernel->_exports["OpenHashTable"]; this->_smallIntegerBehavior = this->speciesInstanceBehavior_(_smallIntegerClass); } - void initializeClosureReturnMethod() - { - auto symbol = this->addSymbol_("return:"); - auto behavior = this->speciesInstanceBehavior_(_closureClass); - this->_closureReturnMethod = this->lookup_startingAt_((Object*)symbol, behavior)->asHeapObject(); - } + void initializeClosureReturnMethod(); HeapObject *_falseObj; HeapObject *_trueObj; @@ -538,7 +534,7 @@ class Runtime { HeapObject *_behaviorClass; HeapObject *_ephemeronClass; HeapObject *_processStackClass; - HeapObject *_symbolTable; + HeapObject *_openHashTableClass; HeapObject *_closureReturnMethod; HeapObject *_smallIntegerBehavior; diff --git a/runtime/cpp/FileImageSegment.cpp b/runtime/cpp/FileImageSegment.cpp new file mode 100644 index 00000000..117fce75 --- /dev/null +++ b/runtime/cpp/FileImageSegment.cpp @@ -0,0 +1,166 @@ +/* + Copyright (c) 2019-2026 Javier Pimás. + Copyright (c) 2019 Jan Vrany. + See (MIT) license in root directory. + */ + +#include +#include +#include +#include +#include + +#include "Util.h" +#include "FileImageSegment.h" +#include "Allocator/Memory.h" + +namespace Egg +{ + +FileImageSegment::FileImageSegment(std::istream* data) +{ + this->load(data); +} + +uintptr_t +FileImageSegment::alloc(uintptr_t base, size_t size) +{ + ASSERT(base == pagealign(base)); + auto ptr = ReserveMemory(base, size); + CommitMemory(ptr, size); + return ptr; +} + +void +FileImageSegment::load(std::istream *data) +{ + data->read(reinterpret_cast(&header), sizeof(header)); + + if (strncmp((char*)&header.signature, "EGG_IS\n", 8) != 0) + error("wrong image segment signature"); + + _currentBase = this->alloc(header.baseAddress, header.reservedSize); + + data->seekg(0, std::ios::beg); + data->read(reinterpret_cast(_currentBase), header.size); + + if (data->fail()) + error("error reading image segment"); + + this->readImportStrings(data); + this->readImportDescriptors(data); + this->readExports(data); +} + +void FileImageSegment::fixPointerSlots(const std::vector& imports) +{ + intptr_t delta = this->_currentBase - this->header.baseAddress; + uintptr_t oldBehaviorBase = this->header.baseAddress & (((uintptr_t)-1) << 32); // discards lower 32 bits + auto spaceStart = this->spaceStart(); + auto current = ((HeapObject::ObjectHeader*)spaceStart)->object(); + auto end = (HeapObject*)this->spaceEnd(); + while (current < end) + { + auto behavior = current->basicBehavior(); + if (((uintptr_t)behavior & 0x3) == 0x0) // if an oop + { + auto newBehavior = oldBehaviorBase + (intptr_t)behavior + delta; + current->behavior((HeapObject*)newBehavior); + } + else if (((uintptr_t)behavior & 0x3) == 0x2) // if an import + { + current->behavior(imports[((uintptr_t)behavior)>>2]->asHeapObject()); + } + + for (uintptr_t i = 0; i < current->pointersSize(); i++) + { + auto &slot = current->slot(i); + if (((uintptr_t)slot & 0x3) == 0x0) + { + slot = (Object*)(((intptr_t)slot) + delta); + } + else if (((uintptr_t)slot & 0x3) == 0x2) + { + slot = imports[((uintptr_t)slot)>>2]; + } + } + current = current->nextObject(); + } + + header.module = relocatedAddress_(header.module); +} + +std::string& FileImageSegment::importStringAt_(uint32_t index) +{ + return _importStrings[index]; +} + +HeapObject* FileImageSegment::relocatedAddress_(const HeapObject* object) +{ + uintptr_t delta = _currentBase - header.baseAddress; + return (HeapObject*)((uintptr_t)object + delta); +} + +void FileImageSegment::readImportStrings(std::istream *data) +{ + uint32_t importStringsSize; + data->read((char*)&importStringsSize, sizeof(importStringsSize)); + + uint32_t bufferSize = 1000; + char *buffer = new char[bufferSize]; + for (int i = 0; i < importStringsSize; i++) + { + uint32_t stringSize; + data->read((char*)&stringSize, sizeof(stringSize)); + if (stringSize > bufferSize) + { + delete[] buffer; + bufferSize = std::max(stringSize, bufferSize * 2); + buffer = new char[bufferSize]; + } + data->read(buffer, stringSize); + + _importStrings.push_back(std::string(buffer, stringSize)); + } + delete[] buffer; +} + +void FileImageSegment::readImportDescriptors(std::istream *data) +{ + uint32_t importDescriptorsSize; + data->read((char*)&importDescriptorsSize, sizeof(importDescriptorsSize)); + + for (int i = 0; i < importDescriptorsSize; i++) + { + uint32_t descriptorSize; + data->read((char*)&descriptorSize, sizeof(descriptorSize)); + std::vector descriptor; + descriptor.resize(descriptorSize); + data->read((char*)&descriptor[0], descriptorSize * sizeof(uint32_t)); + + _importDescriptors.push_back(descriptor); + } +} + +void FileImageSegment::readExports(std::istream *data) +{ + uint32_t exportDescriptorsSize; + data->read((char*)&exportDescriptorsSize, sizeof(exportDescriptorsSize)); + for (int i = 0; i < exportDescriptorsSize; i++) + { + uint64_t exportHeapAddress; + uint64_t exportSizeOfName; + + data->read(reinterpret_cast(&exportHeapAddress), sizeof(exportHeapAddress)); + data->read(reinterpret_cast(&exportSizeOfName), sizeof(exportSizeOfName)); + + std::string exportName; + exportName.resize(exportSizeOfName); + data->read(&exportName[0], exportSizeOfName); + + _exports[exportName] = this->relocatedAddress_(reinterpret_cast(exportHeapAddress)); + } + ASSERT(data->peek() == EOF); +} + +} // namespace Egg diff --git a/runtime/cpp/FileImageSegment.h b/runtime/cpp/FileImageSegment.h new file mode 100644 index 00000000..c52f84e3 --- /dev/null +++ b/runtime/cpp/FileImageSegment.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2019-2026 Javier Pimás, Jan Vrany, Labware. + See (MIT) license in root directory. + */ + +#ifndef _FILE_IMAGE_SEGMENT_H_ +#define _FILE_IMAGE_SEGMENT_H_ + +#include +#include + +#include "ImageSegment.h" + +namespace Egg +{ + +/** + * An ImageSegment loaded from an .ems file on disk. + * Handles reading, pointer relocation, and import resolution. + */ +class FileImageSegment : public ImageSegment +{ + public: + std::vector _importStrings; + std::vector> _importDescriptors; + + FileImageSegment(std::istream* data); + + /** + * Allocate a new segment of given `size` at given `base` address. + * Contents of the segment is zeroed. + * Return value is address allocated when passed null as base. + */ + uintptr_t alloc(uintptr_t base, size_t size); + + /** + * Traverses the image segment space looking for pointers. + * - References to other objects in same space need to be relocated. + * - References to imports (last two bits are 10b) are indices in import table, + * and need to be changed to actual object addresses. + */ + void fixPointerSlots(const std::vector &imports); + + std::string& importStringAt_(uint32_t index); + HeapObject* relocatedAddress_(const HeapObject* object); + + private: + void load(std::istream* data); + void readImportStrings(std::istream *data); + void readImportDescriptors(std::istream *data); + void readExports(std::istream *data); +}; + +} // namespace Egg + +#endif // _FILE_IMAGE_SEGMENT_H_ diff --git a/runtime/cpp/ImageSegment.cpp b/runtime/cpp/ImageSegment.cpp index 354d3a1c..c702a3a7 100644 --- a/runtime/cpp/ImageSegment.cpp +++ b/runtime/cpp/ImageSegment.cpp @@ -7,109 +7,13 @@ #include #include #include -#include -#include #include - -#include "Util.h" #include "ImageSegment.h" -#include "Allocator/Memory.h" - namespace Egg { - -uintptr_t -ImageSegment::alloc(uintptr_t base, size_t size) -{ - ASSERT(base == pagealign(base)); - auto ptr = ReserveMemory(base, size); - CommitMemory(ptr, size); - return ptr; -} - -void -ImageSegment::load(std::istream *data) -{ - data->read(reinterpret_cast(&header), sizeof(header)); - - if (strncmp((char*)&header.signature, "EGG_IS\n", 8) != 0) - error("wrong image segment signature"); - - _currentBase = this->alloc(header.baseAddress, header.reservedSize); - - data->seekg(0, std::ios::beg); - data->read(reinterpret_cast(_currentBase), header.size); - - if (data->fail()) - error("error reading image segment"); - - this->readImportStrings(data); - this->readImportDescriptors(data); - this->readExports(data); -} -#include -void ImageSegment::fixPointerSlots(const std::vector& imports) -{ - intptr_t delta = this->_currentBase - this->header.baseAddress; - uintptr_t oldBehaviorBase = this->header.baseAddress & (((uintptr_t)-1) << 32); // discards lower 32 bits - auto spaceStart = this->spaceStart(); - auto current = ((HeapObject::ObjectHeader*)spaceStart)->object(); - auto end = (HeapObject*)this->spaceEnd(); - while (current < end) - { - auto behavior = current->basicBehavior(); - if (((uintptr_t)behavior & 0x3) == 0x0) // if an oop - { - auto newBehavior = oldBehaviorBase + (intptr_t)behavior + delta; - current->behavior((HeapObject*)newBehavior); - } - else if (((uintptr_t)behavior & 0x3) == 0x2) // if an import - { - current->behavior(imports[((uintptr_t)behavior)>>2]->asHeapObject()); - } - - for (uintptr_t i = 0; i < current->pointersSize(); i++) - { - auto &slot = current->slot(i); - if (((uintptr_t)slot & 0x3) == 0x0) - { - slot = (Object*)(((intptr_t)slot) + delta); - } - else if (((uintptr_t)slot & 0x3) == 0x2) - { - slot = imports[((uintptr_t)slot)>>2]; - } - } - current = current->nextObject(); - } - - header.module = relocatedAddress_(header.module); -} - -uintptr_t ImageSegment::spaceStart() -{ - return this->_currentBase + sizeof(ImageSegmentHeader); -} - -uintptr_t ImageSegment::spaceEnd() -{ - return this->_currentBase + header.size; -} - -std::string& ImageSegment::importStringAt_(uint32_t index) -{ - return _importStrings[index]; -} - -HeapObject* ImageSegment::relocatedAddress_(const HeapObject* object) -{ - uintptr_t delta = _currentBase - header.baseAddress; - return (HeapObject*)((uintptr_t)object + delta); -} - void ImageSegment::dumpObjects() { auto heapStart = _currentBase + sizeof(ImageSegmentHeader); auto current = ((HeapObject::ObjectHeader*)heapStart)->object(); @@ -118,7 +22,6 @@ void ImageSegment::dumpObjects() { { auto behavior = current->behavior(); std::cout << "obj at: " << current << " (" << current->printString() << ")" << std::endl; - //std::cout << "behavior: " << behavior->printString() << std::endl; std::cout << "size: " << std::dec << current->size() << ", flags: " << current->flags() << std::endl; if (current->isBytes()) { @@ -140,81 +43,4 @@ void ImageSegment::dumpObjects() { } } - -void ImageSegment::readImportStrings(std::istream *data) -{ - // std::cout << "import strings position: " << data->tellg() << std::endl; - uint32_t importStringsSize; - data->read((char*)&importStringsSize, sizeof(importStringsSize)); - - uint32_t bufferSize = 1000; - char *buffer = new char[bufferSize]; - for (int i = 0; i < importStringsSize; i++) - { - uint32_t stringSize; - data->read((char*)&stringSize, sizeof(stringSize)); - if (stringSize > bufferSize) - { - delete[] buffer; - bufferSize = std::max(stringSize, bufferSize * 2); - buffer = new char[bufferSize]; - } - data->read(buffer, stringSize); - - _importStrings.push_back(std::string(buffer, stringSize)); - } - delete[] buffer; -} - -void ImageSegment::readImportDescriptors(std::istream *data) -{ - uint32_t importDescriptorsSize; - data->read((char*)&importDescriptorsSize, sizeof(importDescriptorsSize)); - - for (int i = 0; i < importDescriptorsSize; i++) - { - uint32_t descriptorSize; - data->read((char*)&descriptorSize, sizeof(descriptorSize)); - std::vector descriptor; - descriptor.resize(descriptorSize); - data->read((char*)&descriptor[0], descriptorSize * sizeof(uint32_t)); - - //std::cout << "found descriptor " << i << " - "; - //for (int j = 0; j < descriptorSize; j++) - // std::cout << _importStrings[descriptor[j]] << "(" << descriptor[j] << ") "; - //std::cout << std::endl; - - _importDescriptors.push_back(descriptor); - } -} - -void ImageSegment::readExports(std::istream *data) -{ - uint32_t exportDescriptorsSize; - data->read((char*)&exportDescriptorsSize, sizeof(exportDescriptorsSize)); - for (int i = 0; i < exportDescriptorsSize; i++) - { - uint64_t exportHeapAddress; - uint64_t exportSizeOfName; - - // Read the export heap address - data->read(reinterpret_cast(&exportHeapAddress), sizeof(exportHeapAddress)); - - // Read the size of the export name - data->read(reinterpret_cast(&exportSizeOfName), sizeof(exportSizeOfName)); - - // Read the export name bytes - std::string exportName; - exportName.resize(exportSizeOfName); - data->read(&exportName[0], exportSizeOfName); - - //std::cout << "Found " << exportName << " at 0x" << std::hex << exportHeapAddress << std::endl; - - // Map the export name to the heap address - _exports[exportName] = this->relocatedAddress_(reinterpret_cast(exportHeapAddress)); - } - ASSERT(data->peek() == EOF); - -} - } // namespace Egg diff --git a/runtime/cpp/ImageSegment.h b/runtime/cpp/ImageSegment.h index aa31b98d..b6f36f4f 100644 --- a/runtime/cpp/ImageSegment.h +++ b/runtime/cpp/ImageSegment.h @@ -7,11 +7,8 @@ #define _IMAGE_SEGMENT_H_ #include -#include -#include #include #include -#include #include "HeapObject.h" @@ -51,50 +48,23 @@ typedef struct _ImageSegmentHeader static_assert(sizeof(ImageSegmentHeader) == 40 /*bytes*/, "segment_header size not 40 bytes"); +/** + * Base class for memory segments containing Egg objects. + * Provides the common interface used by Runtime: memory bounds and exports. + */ class ImageSegment { public: ImageSegmentHeader header; - public: uint64_t _currentBase; std::map _exports; - std::vector _importStrings; - std::vector> _importDescriptors; - ImageSegment(std::istream* data) { this->load(data); } - - /** - * Allocate a new segment of given `size` at given `base` address. - * Contents of the segment is zeroed. - * Return value is address allocated when passed null as base. - */ - uintptr_t alloc(uintptr_t base, size_t size); - - /** - * Load a segment from given stream and return it. The stream should - * be positioned to the beginning of segment prior calling load() - */ - void load(std::istream* data); - - /** - * Traverses the image segment space looking for pointers. - * - References to other objects in same space need to be relocated. - * - References to imports (last two bits are 10b) are indices in import table, - * and need to be changed to actual object addresses. - */ - void fixPointerSlots(const std::vector &imports); - uintptr_t spaceStart(); - uintptr_t spaceEnd(); + virtual ~ImageSegment() = default; - std::string& importStringAt_(uint32_t index); - HeapObject* relocatedAddress_(const HeapObject* object); + uintptr_t spaceStart() { return _currentBase + sizeof(ImageSegmentHeader); } + uintptr_t spaceEnd() { return _currentBase + header.size; } void dumpObjects(); - - private: - void readImportStrings(std::istream *data); - void readImportDescriptors(std::istream *data); - void readExports(std::istream *data); }; } // namespace Egg diff --git a/runtime/cpp/Launcher.cpp b/runtime/cpp/Launcher.cpp index 88620fd1..19196e75 100644 --- a/runtime/cpp/Launcher.cpp +++ b/runtime/cpp/Launcher.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2019-2023 Javier Pimás, Jan Vrany, Labware. + Copyright (c) 2019-2025 Javier Pimás, Jan Vrany, Labware. See (MIT) license in root directory. */ @@ -55,50 +55,4 @@ Launcher::main(const int argc, const char** argv) runtime->sendLocal_to_with_("main:", moduleRef.get(), (Object*)array); return 0; -} - -void start(Runtime *runtime, HeapObject *kernel, std::vector &args) { - HeapObject *name = runtime->sendLocal_to_("name", (Egg::Object*)kernel)->asHeapObject(); - std::cout << "The name of kernel module is " << name->asLocalString() << std::endl; - - std::cout << "Loading module " << args[1]->asHeapObject()->asLocalString() << std::endl; - auto module = runtime->sendLocal_to_with_("load:", (Object*)kernel, (Object*)args[1]); - - name = runtime->sendLocal_to_("name", module)->asHeapObject(); - std::cout << "The name of loaded module is " << name->asLocalString() << std::endl; - - auto array = runtime->newArray_(args); - runtime->sendLocal_to_with_("main:", module, (Object*)array); - -} - -void runBareTests(Runtime *runtime, HeapObject *kernel, std::vector &args) -{ - auto segment = runtime->_bootstrapper->bareLoadModuleFromFile("Kernel.BareTests.ems"); - segment->dumpObjects(); - auto module = segment->_exports["__module__"]; - auto methodDict = runtime->behaviorMethodDictionary_(runtime->behaviorOf_((Object*)module)); - auto table = runtime->dictionaryTable_(methodDict); - std::vector methods; - for (int index = 2; index < table->size(); index += 2) { - auto symbol = table->slotAt_(index)->asHeapObject(); - if (symbol != runtime->_nilObj && symbol->asLocalString().starts_with("test")) - methods.push_back(table->slotAt_(index + 1)->asHeapObject()); - } - - std::sort(methods.begin(), methods.end(), - [runtime](HeapObject *a, HeapObject *b) { - return std::strcmp((char*)runtime->methodSelector_(a), (char*)runtime->methodSelector_(b)) < 0; - }); - - for (auto method : methods) { - // auto result = runtime->_evaluator->invoke_with_(method, (Object*)runtime->_nilObj); - //runtime->_evaluator->evaluate(); - auto selector = runtime->methodSelector_(method)->printString(); - // if (selector == "test161CreateDictionary") - { - auto result = runtime->sendLocal_to_(selector, (Object*)module); - ASSERT(result == (Object*)runtime->_trueObj); - } - } } \ No newline at end of file diff --git a/runtime/cpp/Launcher.h b/runtime/cpp/Launcher.h index e9242f0b..a9d0a5f9 100644 --- a/runtime/cpp/Launcher.h +++ b/runtime/cpp/Launcher.h @@ -9,8 +9,6 @@ #include -#include "ImageSegment.h" - namespace Egg { class Launcher diff --git a/runtime/cpp/Loader.cpp b/runtime/cpp/Loader.cpp index 90958af5..d12f582b 100644 --- a/runtime/cpp/Loader.cpp +++ b/runtime/cpp/Loader.cpp @@ -6,7 +6,7 @@ #include "Loader.h" #include "Bootstrap/Bootstrapper.h" #include "Bootstrap/SourceModuleLoader.h" -#include "ImageSegment.h" +#include "FileImageSegment.h" #include namespace Egg { @@ -104,10 +104,10 @@ HeapObject* Loader::loadModule_(const std::string& name) { // .ems loading support methods -ImageSegment* Loader::loadModuleFromFile(const std::string &filename) { +FileImageSegment* Loader::loadModuleFromFile(const std::string &filename) { auto filepath = this->findInPath(filename); auto stream = std::ifstream(filepath, std::ios::binary); - auto imageSegment = new ImageSegment(&stream); + auto imageSegment = new FileImageSegment(&stream); std::vector imports; this->bindModuleImports(imageSegment, imports); imageSegment->fixPointerSlots(imports); @@ -115,13 +115,13 @@ ImageSegment* Loader::loadModuleFromFile(const std::string &filename) { return imageSegment; } -void Loader::bindModuleImports(ImageSegment *imageSegment, std::vector &imports) { +void Loader::bindModuleImports(FileImageSegment *imageSegment, std::vector &imports) { for (size_t i = 0; i < imageSegment->_importDescriptors.size(); i++) { imports.push_back(this->bindModuleImport(imageSegment, imageSegment->_importDescriptors[i])); } } -Object* Loader::bindModuleImport(ImageSegment* imageSegment, std::vector &descriptor) { +Object* Loader::bindModuleImport(FileImageSegment* imageSegment, std::vector &descriptor) { auto linker = this->importStringAt_(imageSegment, descriptor[0]); HeapObject *token; if (descriptor.size() == 1) @@ -139,7 +139,7 @@ Object* Loader::bindModuleImport(ImageSegment* imageSegment, std::vector_runtime->sendLocal_to_("link", ref); } -HeapObject* Loader::importStringAt_(ImageSegment* imageSegment, uint32_t index) { +HeapObject* Loader::importStringAt_(FileImageSegment* imageSegment, uint32_t index) { return this->transferSymbol(imageSegment->importStringAt_(index)); } @@ -171,10 +171,10 @@ std::filesystem::path Loader::findInPath(const std::string &filename) { // Bare testing support -ImageSegment* Loader::bareLoadModuleFromFile(const std::string &filename) { +FileImageSegment* Loader::bareLoadModuleFromFile(const std::string &filename) { auto filepath = this->findInPath(filename); auto stream = std::ifstream(filepath); - auto imageSegment = new ImageSegment(&stream); + auto imageSegment = new FileImageSegment(&stream); std::vector imports; for (size_t i = 0; i < imageSegment->_importDescriptors.size(); i++) { std::vector &descriptor = imageSegment->_importDescriptors[i]; @@ -186,7 +186,7 @@ ImageSegment* Loader::bareLoadModuleFromFile(const std::string &filename) { return imageSegment; } -Object* Loader::bareBindModuleImport(ImageSegment* imageSegment, std::vector &descriptor) { +Object* Loader::bareBindModuleImport(FileImageSegment* imageSegment, std::vector &descriptor) { auto linker = imageSegment->importStringAt_(descriptor[0]); std::vector tokens; for (size_t i = 1; i < descriptor.size(); i++) diff --git a/runtime/cpp/Loader.h b/runtime/cpp/Loader.h index efef08a1..dd420111 100644 --- a/runtime/cpp/Loader.h +++ b/runtime/cpp/Loader.h @@ -20,6 +20,8 @@ namespace Egg { +class FileImageSegment; + class Loader { public: Runtime *_runtime; @@ -32,25 +34,25 @@ class Loader { HeapObject* loadModule_(const std::string& name); // .ems loading support - ImageSegment* loadModuleFromFile(const std::string &filename); - void bindModuleImports(ImageSegment *imageSegment, std::vector &imports); - Object* bindModuleImport(ImageSegment* imageSegment, std::vector &descriptor); - HeapObject* importStringAt_(ImageSegment* imageSegment, uint32_t index); + FileImageSegment* loadModuleFromFile(const std::string &filename); + void bindModuleImports(FileImageSegment *imageSegment, std::vector &imports); + Object* bindModuleImport(FileImageSegment* imageSegment, std::vector &descriptor); + HeapObject* importStringAt_(FileImageSegment* imageSegment, uint32_t index); HeapObject* transferSymbol(std::string &str); HeapObject* transferArray(std::vector &array); HeapObject* transferArray(std::vector &array); std::filesystem::path findInPath(const std::string &filename); // bare testing support - ImageSegment* bareLoadModuleFromFile(const std::string &filename); - Object* bareBindModuleImport(ImageSegment* imageSegment, std::vector &descriptor); + FileImageSegment* bareLoadModuleFromFile(const std::string &filename); + Object* bareBindModuleImport(FileImageSegment* imageSegment, std::vector &descriptor); std::string findModulesDir_(); private: std::string _modulesDir; std::map _loadedModules; - std::map _segments; + std::map _segments; bool hasEmsFile_(const std::string& name); bool hasSourceDir_(const std::string& name); From 309bd80b58f5e8bd42fb2ff5652c6237b2485be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Wed, 18 Mar 2026 00:55:43 -0300 Subject: [PATCH 10/15] Harden integer and at: primitives - primitiveAt: use failPrimitive instead of error() for type/bounds checks, including bounds check for bytes objects - primitiveSMIBitShift: detect overflow and fail the primitive - primitiveSMIIntDiv/IntQuot: implement floored division/modulo semantics to match Smalltalk // and \\ - underprimitiveBitShiftLeft/SMIBitShiftLeft: detect overflow and return nil instead of silently wrapping - Use existingSymbolFrom_ for doesNotUnderstand: lookup - Remove stale debug comments --- runtime/cpp/Evaluator/Evaluator.cpp | 59 +++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/runtime/cpp/Evaluator/Evaluator.cpp b/runtime/cpp/Evaluator/Evaluator.cpp index bd3e6836..b7016f46 100644 --- a/runtime/cpp/Evaluator/Evaluator.cpp +++ b/runtime/cpp/Evaluator/Evaluator.cpp @@ -306,7 +306,7 @@ void Egg::Evaluator::messageNotUnderstood_(SAbstractMessage *message) auto array = _runtime->newArray_(args); _context->push_(message->selector()); _context->push_((Object*)array); - auto symbol = _runtime->addSymbol_("doesNotUnderstand:"); + auto symbol = _runtime->existingSymbolFrom_("doesNotUnderstand:"); auto behavior = _runtime->behaviorOf_(_regR); auto dnu = _runtime->lookup_startingAt_((Object*)symbol, behavior); if (!dnu) @@ -326,6 +326,7 @@ void Evaluator::doesNotKnow(const Object *symbol) { ASSERT(false); } void Evaluator::visitIdentifier(SIdentifier *identifier) { SBinding* binding = identifier->binding(); + auto value = binding->valueWithin_(_context); if (!value) return this->doesNotKnow(binding->name()); @@ -359,8 +360,6 @@ void Evaluator::visitOpDispatchMessage(SOpDispatchMessage *anSOpDispatchMessage) { SAbstractMessage *message = anSOpDispatchMessage->message(); - //std::cout << "dispatching " << message->selector()->asLocalString() << std::endl; - UndermessagePointer undermessage = message->cachedUndermessage(); if (undermessage != nullptr) { return this->evaluateUndermessage_with_(message, undermessage); @@ -516,15 +515,25 @@ Object* Evaluator::primitiveAt() { auto index = this->_context->firstArgument(); if (receiver->isSmallInteger()) - error("primitiveAt: receiver must not be an integer"); + return this->failPrimitive(); if (!index->isSmallInteger()) - error("primitiveAt: index must be an integer"); + return this->failPrimitive(); auto index_int = index->asSmallInteger()->asNative(); auto heapreceiver = receiver->asHeapObject(); - return heapreceiver->isBytes() ? newIntObject(heapreceiver->byteAt_(index_int)) : _runtime->indexedSlotAt_(heapreceiver, index_int); + if (heapreceiver->isBytes()) { + if (index_int < 1 || (unsigned)(index_int - 1) >= heapreceiver->size()) + return this->failPrimitive(); + return newIntObject(heapreceiver->byteAt_(index_int)); + } else { + auto instSize = heapreceiver->isNamed() ? (int)_runtime->speciesInstanceSize_(_runtime->speciesOf_((Object*)heapreceiver)) : 0; + auto rawSlot = heapreceiver->isNamed() ? index_int + instSize : index_int; + if (rawSlot < 1 || (unsigned)(rawSlot - 1) >= heapreceiver->size()) + return this->failPrimitive(); + return _runtime->indexedSlotAt_(heapreceiver, index_int); + } } Object* Evaluator::primitiveAtPut() { @@ -964,8 +973,12 @@ Object* Evaluator::primitiveSMIBitOr() { Object* Evaluator::primitiveSMIBitShift() { auto self = this->_context->self()->asSmallInteger()->asNative(); auto firstArg = this->_context->firstArgument()->asSmallInteger()->asNative(); - auto shifted = firstArg > 0 ? self << firstArg : self >> -firstArg; - return newIntObject(shifted); + if (firstArg > 0) { + if (firstArg >= 63 || (self != 0 && (self > (SmallInteger::SMALLINT_MAX >> firstArg) || self < (SmallInteger::SMALLINT_MIN >> firstArg)))) + return failPrimitive(); + return newIntObject(self << firstArg); + } + return newIntObject(self >> -firstArg); } Object* Evaluator::primitiveSMIBitXor() { @@ -1001,11 +1014,25 @@ Object* Evaluator::primitiveSMIHighBit() { } Object* Evaluator::primitiveSMIIntDiv() { - return newIntObject(this->_context->self()->asSmallInteger()->asNative() / (this->_context->firstArgument()->asSmallInteger()->asNative())); + // Smalltalk // is floored division (toward negative infinity) + auto a = this->_context->self()->asSmallInteger()->asNative(); + auto b = this->_context->firstArgument()->asSmallInteger()->asNative(); + auto q = a / b; + // Adjust: if remainder is nonzero and signs differ, subtract 1 + if ((a % b != 0) && ((a ^ b) < 0)) + q -= 1; + return newIntObject(q); } Object* Evaluator::primitiveSMIIntQuot() { - return newIntObject(this->_context->self()->asSmallInteger()->asNative() % (this->_context->firstArgument()->asSmallInteger()->asNative())); + // Smalltalk \\ is floored remainder (modulo), same sign as divisor + auto a = this->_context->self()->asSmallInteger()->asNative(); + auto b = this->_context->firstArgument()->asSmallInteger()->asNative(); + auto r = a % b; + // Adjust: if remainder is nonzero and signs differ, add divisor + if (r != 0 && ((a ^ b) < 0)) + r += b; + return newIntObject(r); } Object* Evaluator::primitiveSMIMinus() { @@ -1281,7 +1308,11 @@ Object* Evaluator::underprimitiveBasicHashPut(Object *receiver, std::vector &args) { - auto result = receiver->asSmallInteger()->asNative() << args[0]->asSmallInteger()->asNative(); + auto value = receiver->asSmallInteger()->asNative(); + auto shift = args[0]->asSmallInteger()->asNative(); + if (shift >= 63 || (value != 0 && shift > 0 && (value > (SmallInteger::SMALLINT_MAX >> shift) || value < (SmallInteger::SMALLINT_MIN >> shift)))) + return (Object*)_nilObj; + auto result = value << shift; return newIntObject(result); } @@ -1354,7 +1385,11 @@ Object* Evaluator::underprimitiveSMIBitOr(Object *receiver, std::vector } Object* Evaluator::underprimitiveSMIBitShiftLeft(Object *receiver, std::vector &args) { - return newIntObject((receiver->asSmallInteger()->asNative() << args[0]->asSmallInteger()->asNative())); + auto value = receiver->asSmallInteger()->asNative(); + auto shift = args[0]->asSmallInteger()->asNative(); + if (shift >= 63 || (value != 0 && shift > 0 && (value > (SmallInteger::SMALLINT_MAX >> shift) || value < (SmallInteger::SMALLINT_MIN >> shift)))) + return (Object*)_nilObj; + return newIntObject(value << shift); } Object* Evaluator::underprimitiveSMIBitShiftRight(Object *receiver, std::vector &args) { From 11355b21fa2420564cc7b3bb643755d521d74fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Wed, 18 Mar 2026 01:09:59 -0300 Subject: [PATCH 11/15] Add computeSymbolHash_ method to Bootstrapper for symbol hashing --- runtime/cpp/Bootstrap/Bootstrapper.cpp | 32 ++++++++++++++++++++++++++ runtime/cpp/Bootstrap/Bootstrapper.h | 1 + 2 files changed, 33 insertions(+) diff --git a/runtime/cpp/Bootstrap/Bootstrapper.cpp b/runtime/cpp/Bootstrap/Bootstrapper.cpp index 428b7569..1f8702a1 100644 --- a/runtime/cpp/Bootstrap/Bootstrapper.cpp +++ b/runtime/cpp/Bootstrap/Bootstrapper.cpp @@ -727,9 +727,41 @@ Object* Bootstrapper::newSymbol_(const Egg::string& str) { for (uint32_t i = 0; i < len; i++) ((uint32_t*)symbol)[i] = (uint32_t)str[i]; } + computeSymbolHash_(symbol); return (Object*)symbol; } +void Bootstrapper::computeSymbolHash_(HeapObject* symbol) { + uint32_t basicSize = symbol->size(); + int32_t pseudoindex = (int32_t)basicSize - 1; + if (pseudoindex < 0) return; + + uint32_t begin = 0; + uint32_t middle = (pseudoindex & 0xFFFF) / 2; + uint32_t end = middle * 2; + + uint8_t* bytes = (uint8_t*)symbol; + + // uShortAtOffset reads LE 16-bit at byte offset, with out-of-bounds bytes = 0 + auto uShortAtOffset = [&](uint32_t offset) -> uint32_t { + uint32_t index = offset; // 0-based byte index + uint32_t lo = (index < basicSize) ? bytes[index] : 0; + uint32_t hi = (index + 1 < basicSize) ? bytes[index + 1] : 0; + return (hi << 8) + lo; + }; + auto shortAtOffset = [&](uint32_t offset) -> int32_t { + uint32_t u = uShortAtOffset(offset); + return (u > 0x7FFF) ? (int32_t)(u - 0x10000) : (int32_t)u; + }; + + int32_t first = shortAtOffset(begin) + 256 * (pseudoindex & 0xFF); + int32_t second = shortAtOffset(end); + int32_t third = shortAtOffset(middle); + uint16_t hash = (first + second * 4 + third * 4) & 0x7FFF; + + symbol->hash(hash); +} + HeapObject* Bootstrapper::newString_(const Egg::string& str) { bool isWide = std::ranges::any_of(str, [](char32_t cp) { return cp > 0xFF; }); diff --git a/runtime/cpp/Bootstrap/Bootstrapper.h b/runtime/cpp/Bootstrap/Bootstrapper.h index 152564f8..35c788c8 100644 --- a/runtime/cpp/Bootstrap/Bootstrapper.h +++ b/runtime/cpp/Bootstrap/Bootstrapper.h @@ -117,6 +117,7 @@ namespace Egg HeapObject *newAssociation_value_(Object *key, Object *value); Object *internSymbol_(const Egg::string &str); Object *newSymbol_(const Egg::string &str); + void computeSymbolHash_(HeapObject *symbol); HeapObject *newString_(const Egg::string &str); HeapObject *newArray_(uint32_t size); HeapObject *newByteArray_(const std::vector &bytes); From df1c8114cf29ec07df0dfa77fe39044246934d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Wed, 18 Mar 2026 01:18:09 -0300 Subject: [PATCH 12/15] Refactor CMakeLists.txt to create egg_runtime library and link dependencies for egg executable --- runtime/cpp/CMakeLists.txt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/runtime/cpp/CMakeLists.txt b/runtime/cpp/CMakeLists.txt index 965a10c7..5d059715 100644 --- a/runtime/cpp/CMakeLists.txt +++ b/runtime/cpp/CMakeLists.txt @@ -35,10 +35,16 @@ find_package(cxxopts REQUIRED) # ************************************************* file(GLOB ALL_SRC "*.cpp" "Evaluator/*.cpp" "Allocator/*.cpp" "Utils/*.cpp" "${PLATFORM}/*.cpp") +list(FILTER ALL_SRC EXCLUDE REGEX "Main\\.cpp$") -add_executable(${EXE} ${ALL_SRC}) -target_link_libraries(${EXE} libffi::libffi cxxopts::cxxopts) # link agains libffi +# Create runtime library (for use by bootstrapper and other tools) +add_library(egg_runtime STATIC ${ALL_SRC}) +target_link_libraries(egg_runtime PUBLIC libffi::libffi) +target_include_directories(egg_runtime PUBLIC . ${PLATFORM}) +# Create egg executable +add_executable(${EXE} Main.cpp) +target_link_libraries(${EXE} egg_runtime bootstrapper_lib egg_compiler cxxopts::cxxopts) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_libraries(${EXE} dl) @@ -49,6 +55,10 @@ set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) include_directories(. ${PLATFORM}) +# Add compiler and tests +add_subdirectory(Compiler) +add_subdirectory(Bootstrap) + include(CPack) From 866d444001d88f6becf0cbe0de152a308a67208f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Wed, 18 Mar 2026 01:22:40 -0300 Subject: [PATCH 13/15] Remove tests subdirectory from Bootstrap and Compiler CMakeLists --- runtime/cpp/Bootstrap/CMakeLists.txt | 2 -- runtime/cpp/Compiler/CMakeLists.txt | 3 --- 2 files changed, 5 deletions(-) diff --git a/runtime/cpp/Bootstrap/CMakeLists.txt b/runtime/cpp/Bootstrap/CMakeLists.txt index ec2af2e7..ba8eecd5 100644 --- a/runtime/cpp/Bootstrap/CMakeLists.txt +++ b/runtime/cpp/Bootstrap/CMakeLists.txt @@ -29,5 +29,3 @@ target_link_libraries(bootstrap_demo egg_compiler ) -# Add tests subdirectory -add_subdirectory(tests) diff --git a/runtime/cpp/Compiler/CMakeLists.txt b/runtime/cpp/Compiler/CMakeLists.txt index ec5bbffa..9541726f 100644 --- a/runtime/cpp/Compiler/CMakeLists.txt +++ b/runtime/cpp/Compiler/CMakeLists.txt @@ -10,6 +10,3 @@ file(GLOB COMPILER_SRC add_library(egg_compiler STATIC ${COMPILER_SRC}) target_include_directories(egg_compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -# Tests -enable_testing() -add_subdirectory(tests) From 1e588b15deca2ea53cb1f7616853d2fe82f2eb4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Wed, 18 Mar 2026 01:25:30 -0300 Subject: [PATCH 14/15] Remove bootstrap demonstration executable from CMakeLists --- runtime/cpp/Bootstrap/CMakeLists.txt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/runtime/cpp/Bootstrap/CMakeLists.txt b/runtime/cpp/Bootstrap/CMakeLists.txt index ba8eecd5..a5896f39 100644 --- a/runtime/cpp/Bootstrap/CMakeLists.txt +++ b/runtime/cpp/Bootstrap/CMakeLists.txt @@ -18,14 +18,3 @@ target_include_directories(bootstrapper_lib PUBLIC # Link bootstrapper with runtime target_link_libraries(bootstrapper_lib PUBLIC egg_runtime) -# Create bootstrap demonstration executable -add_executable(bootstrap_demo - bootstrap_demo.cpp -) - -# Link demo with bootstrapper library and compiler -target_link_libraries(bootstrap_demo - bootstrapper_lib - egg_compiler -) - From 15a3a821b0fad812b3a5e56e2e7e125c57bd1fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Wed, 18 Mar 2026 01:32:35 -0300 Subject: [PATCH 15/15] Add missing algorithm header to Bootstrapper.cpp --- runtime/cpp/Bootstrap/Bootstrapper.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runtime/cpp/Bootstrap/Bootstrapper.cpp b/runtime/cpp/Bootstrap/Bootstrapper.cpp index 1f8702a1..a993f820 100644 --- a/runtime/cpp/Bootstrap/Bootstrapper.cpp +++ b/runtime/cpp/Bootstrap/Bootstrapper.cpp @@ -15,6 +15,8 @@ #include "../Compiler/CompilationResult.h" #include "../KnownConstants.h" #include "../GCedRef.h" + +#include #include #include #include