From ebbe0119d9bd527daed8bbbe0a431f954761b901 Mon Sep 17 00:00:00 2001 From: VAN BOSSUYT Nicolas Date: Wed, 18 Dec 2024 11:13:24 +0100 Subject: [PATCH] wip --- index.js | 1 + src/libs/karm-base/string.h | 8 + src/libs/karm-io/fmt.h | 2 +- src/web/vaev-script/agent.h | 14 ++ src/web/vaev-script/cli/main.cpp | 29 --- src/web/vaev-script/completion.h | 77 ++++++++ src/web/vaev-script/gc.h | 48 +++++ src/web/vaev-script/main/main.cpp | 25 +++ .../vaev-script/{cli => main}/manifest.json | 2 +- src/web/vaev-script/object.h | 167 ++++++++++++++++++ src/web/vaev-script/ops.cpp | 61 +++++++ src/web/vaev-script/ops.h | 44 +++++ src/web/vaev-script/realm.h | 75 ++++++++ src/web/vaev-script/value.h | 90 ++++++++++ 14 files changed, 612 insertions(+), 31 deletions(-) create mode 100644 index.js create mode 100644 src/web/vaev-script/agent.h delete mode 100644 src/web/vaev-script/cli/main.cpp create mode 100644 src/web/vaev-script/completion.h create mode 100644 src/web/vaev-script/gc.h create mode 100644 src/web/vaev-script/main/main.cpp rename src/web/vaev-script/{cli => main}/manifest.json (88%) create mode 100644 src/web/vaev-script/object.h create mode 100644 src/web/vaev-script/ops.cpp create mode 100644 src/web/vaev-script/ops.h create mode 100644 src/web/vaev-script/realm.h create mode 100644 src/web/vaev-script/value.h diff --git a/index.js b/index.js new file mode 100644 index 00000000000..e9fe0090d63 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +console.log('Hello, world!'); diff --git a/src/libs/karm-base/string.h b/src/libs/karm-base/string.h index f2256c6157e..6ea25dc7b18 100644 --- a/src/libs/karm-base/string.h +++ b/src/libs/karm-base/string.h @@ -334,4 +334,12 @@ inline constexpr Karm::Str operator""s(char const *buf, usize len) { return {buf, len}; } +inline constexpr Karm::_Str operator""_s8(char const *buf, usize len) { + return {buf, len}; +} + +inline constexpr Karm::_Str operator""_s16(char16_t const *buf, usize len) { + return {reinterpret_cast(buf), len}; +} + #pragma clang diagnostic pop diff --git a/src/libs/karm-io/fmt.h b/src/libs/karm-io/fmt.h index ed64ceb9eea..7db8d44f030 100644 --- a/src/libs/karm-io/fmt.h +++ b/src/libs/karm-io/fmt.h @@ -635,7 +635,7 @@ struct Formatter> { } } - Res format(Io::TextWriter &writer, Res const &val) { + Res format(Io::TextWriter &writer, Res const &val) { if (val) return _fmtOk.format(writer, val.unwrap()); return _fmtErr.format(writer, val.none()); diff --git a/src/web/vaev-script/agent.h b/src/web/vaev-script/agent.h new file mode 100644 index 00000000000..bc18d50ad4c --- /dev/null +++ b/src/web/vaev-script/agent.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "gc.h" + +namespace Vaev::Script { + +// https://tc39.es/ecma262/#agent +struct Agent { + Gc::Gc &gc; +}; + +} // namespace Vaev::Script diff --git a/src/web/vaev-script/cli/main.cpp b/src/web/vaev-script/cli/main.cpp deleted file mode 100644 index fdfbb4b2a4e..00000000000 --- a/src/web/vaev-script/cli/main.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include -#include -#include -#include - -Async::Task<> entryPointAsync(Sys::Context &ctx) { - auto args = Sys::useArgs(ctx); - - if (args.len() != 2) { - Sys::errln("usage: vaev-js.cli \n"); - co_return Error::invalidInput(); - } - - auto verb = args[0]; - auto url = co_try$(Mime::parseUrlOrPath(args[1])); - if (verb == "dump-tokens") { - auto file = co_try$(Sys::File::open(url)); - auto buf = co_try$(Io::readAllUtf8(file)); - Io::SScan s{buf}; - Vaev::Script::Lexer lex{s}; - while (not lex.ended()) - Sys::println("{}", lex.next()); - co_return Ok(); - } else { - Sys::errln("unknown verb: {} (expected: dump-tokens)\n", verb); - co_return Error::invalidInput(); - } -} diff --git a/src/web/vaev-script/completion.h b/src/web/vaev-script/completion.h new file mode 100644 index 00000000000..04194625178 --- /dev/null +++ b/src/web/vaev-script/completion.h @@ -0,0 +1,77 @@ +#pragma once + +#include + +#include "value.h" + +namespace Vaev::Script { + +// https://tc39.es/ecma262/#sec-completion-record-specification-type +struct [[nodiscard]] Completion { + enum struct _Type { + NORMAL, + BREAK, + CONTINUE, + RETURN, + THROW, + }; + + using enum _Type; + + _Type type = _Type::NORMAL; + Value value = UNDEFINED; + String target = u""_s16; + + static Completion normal(Value value) { + return {NORMAL, value}; + } + + static Completion break_(String target) { + return {BREAK, UNDEFINED, target}; + } + + static Completion continue_(String target) { + return {CONTINUE, UNDEFINED, target}; + } + + static Completion return_(Value value) { + return {RETURN, value}; + } + + static Completion throw_(Value value) { + return {THROW, value}; + } + + bool operator==(_Type t) const { + return type == t; + } + + void repr(Io::Emit &e) const { + switch (type) { + case NORMAL: + e("normal({})", value); + break; + + case BREAK: + e("break({})", target); + break; + + case CONTINUE: + e("continue({})", target); + break; + + case RETURN: + e("return({})", value); + break; + + case THROW: + e("throw({})", value); + break; + } + } +}; + +template +using Except = Res; + +} // namespace Vaev::Script diff --git a/src/web/vaev-script/gc.h b/src/web/vaev-script/gc.h new file mode 100644 index 00000000000..3be6372a4f1 --- /dev/null +++ b/src/web/vaev-script/gc.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +namespace Karm::Gc { + +template +struct Ref { + T *_ptr = nullptr; + + Ref() = default; + + Ref(T *ptr) : _ptr{ptr} { + if (not _ptr) + panic("null pointer"); + } + + T const *operator->() const { + return _ptr; + } + + T *operator->() { + return _ptr; + } + + T const &operator*() const { + return *_ptr; + } + + T &operator*() { + return *_ptr; + } + + void repr(Io::Emit &e) const { + e("{}", *_ptr); + } +}; + +struct Gc { + template + Ref alloc(Args &&...args) { + return new T{std::forward(args)...}; + } +}; + +} // namespace Karm::Gc diff --git a/src/web/vaev-script/main/main.cpp b/src/web/vaev-script/main/main.cpp new file mode 100644 index 00000000000..d6481c246d7 --- /dev/null +++ b/src/web/vaev-script/main/main.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +using namespace Vaev; + +Async::Task<> entryPointAsync(Sys::Context &) { + Gc::Gc gc; + + auto agent = gc.alloc(gc); + auto realm = gc.alloc(agent); + + (void)realm->initializeHostDefinedRealm(agent); + + auto object1 = Script::Object::create(*agent); + object1->defineOwnProperty(Script::PropertyKey::from(u"foo"_s16), {.value = 42.}); + + auto object2 = Script::Object::create(*agent, {.prototype = object1}); + + auto res = object2->get(Script::PropertyKey::from(u"foo"_s16), object2); + + Sys::print("object2.foo = {}\n", res); + + co_return Ok(); +} diff --git a/src/web/vaev-script/cli/manifest.json b/src/web/vaev-script/main/manifest.json similarity index 88% rename from src/web/vaev-script/cli/manifest.json rename to src/web/vaev-script/main/manifest.json index 48c2449a433..dec3c39b478 100644 --- a/src/web/vaev-script/cli/manifest.json +++ b/src/web/vaev-script/main/manifest.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-script.cli", + "id": "vaev-script.main", "type": "exe", "description": "ECMAScript engine testing tool", "requires": [ diff --git a/src/web/vaev-script/object.h b/src/web/vaev-script/object.h new file mode 100644 index 00000000000..ea6da0a1778 --- /dev/null +++ b/src/web/vaev-script/object.h @@ -0,0 +1,167 @@ +#pragma once + +#include + +#include "agent.h" +#include "ops.h" +#include "value.h" + +namespace Vaev::Script { + +// https://tc39.es/ecma262/#property-key + +struct PropertyKey { + using _Store = Union; + + _Store store; + + static PropertyKey from(String str) { + return {str}; + } + + static PropertyKey from(Symbol sym) { + return {sym}; + } + + static PropertyKey from(u64 num) { + return {num}; + } +}; + +// https://tc39.es/ecma262/#sec-property-attributes +struct PropertyDescriptor { + Value value = UNDEFINED; + Boolean writable = false; + Value get = UNDEFINED; + Value set = UNDEFINED; + Boolean enumerable = false; + Boolean configurable = false; + + // https://tc39.es/ecma262/#sec-isaccessordescriptor + Boolean isAccessorDescriptor() const { + // 1. If Desc is undefined, return false. + + // 2. If Desc has a [[Get]] or [[Set]] field, return true. + if (get != UNDEFINED or set != UNDEFINED) + return true; + + // 3. Return false. + return false; + } + + // https://tc39.es/ecma262/#sec-isdatadescriptor + Boolean isDataDescriptor() const { + // 1. If Desc is undefined, return false. + + // 2. If Desc has a [[Value]] field, return true. + if (value != UNDEFINED) + return true; + + // 3. If Desc has a [[Writable]] field, return true. + if (writable) + return true; + + // 4. Return false. + return false; + } +}; + +// https://tc39.es/ecma262/#sec-object-type + +struct _ObjectCreateArgs { + Opt> prototype = NONE; +}; + +struct Object { + Agent &agent; + + static Gc::Ref create(Agent &agent, _ObjectCreateArgs args = {}); + + // https://tc39.es/ecma262/#table-essential-internal-methods + Opt> getPrototypeOf() const; + + Boolean setPrototypeOf(Gc::Ref proto); + + Boolean isExtensible() const; + + Boolean preventExtensions(); + + Union getOwnProperty(PropertyKey key) const; + + // https://tc39.es/ecma262/#sec-ordinarydefineownproperty + void ordinaryDefineOwnProperty() { + // 1. Let current be ? O.[[GetOwnProperty]](P). + // 2. Let extensible be ? IsExtensible(O). + // 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current). + } + + // https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc + Except defineOwnProperty(PropertyKey key, PropertyDescriptor desc) { + // 1. Return ? OrdinaryDefineOwnProperty(O, P, Desc). + return ordinaryDefineOwnProperty(key, desc); + } + + Boolean hasProperty(PropertyKey key) const; + + // https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver + Except get(PropertyKey key, Value receiver) const { + // 1. Return ? OrdinaryGet(O, P, Receiver). + return ordinaryGet(key, receiver); + } + + // https://tc39.es/ecma262/#sec-ordinaryget + Except ordinaryGet(PropertyKey key, Value receiver) const { + // 1. Let desc be ? O.[[GetOwnProperty]](P). + auto maybeDesc = getOwnProperty(key); + + // 2. If desc is undefined, then + if (maybeDesc == UNDEFINED) { + // a. Let parent be ? O.[[GetPrototypeOf]](). + auto parent = getPrototypeOf(); + + // b. If parent is null, return undefined. + if (parent == NONE) + return Ok(UNDEFINED); + + // c. Return ? parent.[[Get]](P, Receiver). + return (*parent)->get(key, receiver); + } + + auto &desc = maybeDesc.unwrap(); + + // 3. If IsDataDescriptor(desc) is true, return desc.[[Value]]. + if (desc.isDataDescriptor()) + return Ok(desc.value); + + // 4. Assert: IsAccessorDescriptor(desc) is true. + if (not desc.isAccessorDescriptor()) + panic("expected accessor descriptor"); + + // 5. Let getter be desc.[[Get]]. + auto getter = desc.get; + + // 6. If getter is undefined, return undefined. + if (getter == UNDEFINED) + return Ok(UNDEFINED); + + // 7. Return ? Call(getter, Receiver). + return Script::call(agent, getter, receiver); + } + + Boolean set(PropertyKey key, Value value, Value receiver); + + Boolean delete_(PropertyKey key) const; + + Vec ownPropertyKeys() const; + + // https://tc39.es/ecma262/#table-additional-essential-internal-methods-of-function-objects + Except call(Gc::Ref thisArg, Slice args) const; + + Value construct(Slice args, Gc::Ref newTarget) const; + + void repr(Io::Emit &e) const { + e("[object Object]"); + } +}; + +} // namespace Vaev::Script diff --git a/src/web/vaev-script/ops.cpp b/src/web/vaev-script/ops.cpp new file mode 100644 index 00000000000..498c6fcfd43 --- /dev/null +++ b/src/web/vaev-script/ops.cpp @@ -0,0 +1,61 @@ +#include "ops.h" + +#include "object.h" + +namespace Vaev::Script { + +// MARK: Abstract Operations --------------------------------------------------- +// https://tc39.es/ecma262/#sec-abstract-operations + +// MARK: Type Conversion ------------------------------------------------------- + +// MARK: Testing and Comparison Operations ------------------------------------- + +// https://tc39.es/ecma262/#sec-iscallable +bool isCallable(Value v) { + // 1. If argument is not an Object, return false. + if (not v.isObject()) + return false; + + // 2. If argument has a [[Call]] internal method, return true. + + // 3. Return false. + return false; +} + +// MARK: Operations on Objects ------------------------------------------------- + +// https://tc39.es/ecma262/#sec-call +Except call(Agent &agent, Value f, Value v, Slice args) { + // 1. If argumentsList is not present, set argumentsList to a new empty List. + // 2. If IsCallable(F) is false, throw a TypeError exception. + if (not isCallable(f)) + return throwException(createException(agent, ExceptionType::TYPE_ERROR)); + + // 3. Return ? F.[[Call]](V, argumentsList) + return f.asObject()->call(v.asObject(), args); +} + +// MARK: Operations on Iterator Objects ---------------------------------------- + +// MARK: Runtime Semantics ----------------------------------------------------- + +// https://tc39.es/ecma262/#sec-throw-an-exception + +Gc::Ref createException(Agent &agent, ExceptionType) { + // FIXME: Implement this properly. + auto exception = Object::create(agent); + exception->set( + PropertyKey::from(u"name"_s16), + String{u"Exception"_s16}, + exception + ); + return exception; +} + +Completion throwException(Value exception) { + // 1. Return ThrowCompletion(a newly created TypeError object). + return Completion::throw_(exception); +} + +} // namespace Vaev::Script diff --git a/src/web/vaev-script/ops.h b/src/web/vaev-script/ops.h new file mode 100644 index 00000000000..14f70612a36 --- /dev/null +++ b/src/web/vaev-script/ops.h @@ -0,0 +1,44 @@ +#pragma once + +#include "agent.h" +#include "completion.h" +#include "value.h" + +namespace Vaev::Script { + +// MARK: Abstract Operations =================================================== +// https://tc39.es/ecma262/#sec-abstract-operations + +// MARK: Type Conversion ------------------------------------------------------- + +// MARK: Testing and Comparison Operations ------------------------------------- + +// https://tc39.es/ecma262/#sec-iscallable +bool isCallable(Value v); + +// MARK: Operations on Objects ------------------------------------------------- + +// https://tc39.es/ecma262/#sec-call +Except call(Agent &agent, Value f, Value v, Slice args = {}); + +// MARK: Operations on Iterator Objects ---------------------------------------- + +// MARK: Runtime Semantics ===================================================== + +enum struct ExceptionType { + ERROR, + TYPE_ERROR, + RANGE_ERROR, + REFERENCE_ERROR, + SYNTAX_ERROR, + URI_ERROR, + + _LEN, +}; + +Gc::Ref createException(Agent &agent, ExceptionType type); + +// https://tc39.es/ecma262/#sec-throw-an-exception +Completion throwException(Value exception); + +} // namespace Vaev::Script diff --git a/src/web/vaev-script/realm.h b/src/web/vaev-script/realm.h new file mode 100644 index 00000000000..dae64e7b2c0 --- /dev/null +++ b/src/web/vaev-script/realm.h @@ -0,0 +1,75 @@ +#pragma once + +#include "agent.h" +#include "completion.h" +#include "value.h" + +namespace Vaev::Script { + +// https://tc39.es/ecma262/#realm +struct Realm { + Gc::Ref agentSignifier; + Value globalThis = UNDEFINED; + + // https://tc39.es/ecma262/#sec-createintrinsics + Completion createIntrinsics() { + // 1. Set realmRec.[[Intrinsics]] to a new Record. + + // 2. Set fields of realmRec.[[Intrinsics]] with the values listed in Table 6. The field names are the names listed in column one of the table. The value of each field is a new object value fully and recursively populated with property values as defined by the specification of each object in clauses 19 through 28. All object property values are newly created object values. All values that are built-in function objects are created by performing CreateBuiltinFunction(steps, length, name, slots, realmRec, prototype) where steps is the definition of that function provided by this specification, name is the initial value of the function's "name" property, length is the initial value of the function's "length" property, slots is a list of the names, if any, of the function's specified internal slots, and prototype is the specified value of the function's [[Prototype]] internal slot. The creation of the intrinsics and their properties must be ordered to avoid any dependencies upon objects that have not yet been created. + + // 3. Perform AddRestrictedFunctionProperties(realmRec.[[Intrinsics]].[[%Function.prototype%]], realmRec). + + // 4. Return unused. + return Completion::normal(UNDEFINED); + } + + // https://tc39.es/ecma262/#sec-initializehostdefinedrealm + Completion initializeHostDefinedRealm(Gc::Ref) { + // 1. Let realm be a new Realm Record. + + // 2. Perform CreateIntrinsics(realm). + + // 3. Set realm.[[AgentSignifier]] to AgentSignifier(). + + // 4. Set realm.[[GlobalObject]] to undefined. + + // 5. Set realm.[[GlobalEnv]] to undefined. + + // 6. Set realm.[[TemplateMap]] to a new empty List. + + // 7. Let newContext be a new execution context. + + // 8. Set the Function of newContext to null. + + // 9. Set the Realm of newContext to realm. + + // 10. Set the ScriptOrModule of newContext to null. + + // 11. Push newContext onto the execution context stack; newContext is now the running execution context. + + // 12. If the host requires use of an exotic object to serve as realm's global object, then + // a. Let global be such an object created in a host-defined manner. + + // 13. Else, + // a. Let global be OrdinaryObjectCreate(realm.[[Intrinsics]].[[%Object.prototype%]]). + + // 14. If the host requires that the this binding in realm's global scope return an object other than the global object, then + // a. Let thisValue be such an object created in a host-defined manner. + + // 15. Else, + // a. Let thisValue be global. + + // 16. Set realm.[[GlobalObject]] to global. + + // 17. Set realm.[[GlobalEnv]] to NewGlobalEnvironment(global, thisValue). + + // 18. Perform ? SetDefaultGlobalBindings(realm). + + // 19. Create any host-defined global object properties on global. + + // 20. Return unused. + return Completion::normal(UNDEFINED); + } +}; + +} // namespace Vaev::Script diff --git a/src/web/vaev-script/value.h b/src/web/vaev-script/value.h new file mode 100644 index 00000000000..a7bb5930a39 --- /dev/null +++ b/src/web/vaev-script/value.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include + +#include "gc.h" + +struct Agent; + +namespace Vaev::Script { + +// https://tc39.es/ecma262/#sec-ecmascript-language-types-undefined-type +struct Undefined { + bool operator==(Undefined const &) const = default; + auto operator<=>(Undefined const &) const = default; + + void repr(Io::Emit &e) const { + e("undefined"); + } +}; + +static constexpr Undefined UNDEFINED{}; + +// https://tc39.es/ecma262/#sec-ecmascript-language-types-null-type +struct Null { + bool operator==(Null const &) const = default; + auto operator<=>(Null const &) const = default; + + void repr(Io::Emit &e) const { + e("null"); + } +}; + +static constexpr Null NULL_{}; + +// https://tc39.es/ecma262/#sec-ecmascript-language-types-boolean-type +using Boolean = bool; + +// https://tc39.es/ecma262/#sec-ecmascript-language-types-string-type +using String = _String; + +// https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type +struct Symbol { + void repr(Io::Emit &e) const { + e("Symbol"); + } +}; + +// https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type +using Number = f64; + +struct Object; + +struct Value { + using _Store = Union< + Undefined, + Null, + Boolean, + String, + Symbol, + Number, + Gc::Ref>; + + _Store store = UNDEFINED; + + Value() = default; + + template T> + Value(T &&t) : store{std::forward(t)} {} + + template T> + bool operator==(T const &t) const { + return store == t; + } + + Boolean isObject() const { + return store.is>(); + } + + Gc::Ref asObject() { + return store.unwrap>(); + } + + void repr(Io::Emit &e) const { + e("{}", store); + } +}; + +} // namespace Vaev::Script