/src/serenity/Userland/Libraries/LibJS/SyntheticModule.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022, David Tuin <davidot@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibJS/Runtime/AbstractOperations.h> |
8 | | #include <LibJS/Runtime/Completion.h> |
9 | | #include <LibJS/Runtime/GlobalEnvironment.h> |
10 | | #include <LibJS/Runtime/ModuleEnvironment.h> |
11 | | #include <LibJS/Runtime/PromiseCapability.h> |
12 | | #include <LibJS/Runtime/PromiseConstructor.h> |
13 | | #include <LibJS/Runtime/VM.h> |
14 | | #include <LibJS/SyntheticModule.h> |
15 | | |
16 | | namespace JS { |
17 | | |
18 | | JS_DEFINE_ALLOCATOR(SyntheticModule); |
19 | | |
20 | | // 1.2.1 CreateSyntheticModule ( exportNames, evaluationSteps, realm, hostDefined ), https://tc39.es/proposal-json-modules/#sec-createsyntheticmodule |
21 | | SyntheticModule::SyntheticModule(Vector<DeprecatedFlyString> export_names, SyntheticModule::EvaluationFunction evaluation_steps, Realm& realm, StringView filename) |
22 | 0 | : Module(realm, filename) |
23 | 0 | , m_export_names(move(export_names)) |
24 | 0 | , m_evaluation_steps(move(evaluation_steps)) |
25 | 0 | { |
26 | | // 1. Return Synthetic Module Record { [[Realm]]: realm, [[Environment]]: undefined, [[Namespace]]: undefined, [[HostDefined]]: hostDefined, [[ExportNames]]: exportNames, [[EvaluationSteps]]: evaluationSteps }. |
27 | 0 | } |
28 | | |
29 | | // 1.2.3.1 GetExportedNames( exportStarSet ), https://tc39.es/proposal-json-modules/#sec-smr-getexportednames |
30 | | ThrowCompletionOr<Vector<DeprecatedFlyString>> SyntheticModule::get_exported_names(VM&, Vector<Module*>) |
31 | 0 | { |
32 | | // 1. Return module.[[ExportNames]]. |
33 | 0 | return m_export_names; |
34 | 0 | } |
35 | | |
36 | | // 1.2.3.2 ResolveExport( exportName, resolveSet ), https://tc39.es/proposal-json-modules/#sec-smr-resolveexport |
37 | | ThrowCompletionOr<ResolvedBinding> SyntheticModule::resolve_export(VM&, DeprecatedFlyString const& export_name, Vector<ResolvedBinding>) |
38 | 0 | { |
39 | | // 1. If module.[[ExportNames]] does not contain exportName, return null. |
40 | 0 | if (!m_export_names.contains_slow(export_name)) |
41 | 0 | return ResolvedBinding::null(); |
42 | | |
43 | | // 2. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: exportName }. |
44 | 0 | return ResolvedBinding { ResolvedBinding::BindingName, this, export_name }; |
45 | 0 | } |
46 | | |
47 | | // 1.2.3.3 Link ( ), https://tc39.es/proposal-json-modules/#sec-smr-instantiate |
48 | | ThrowCompletionOr<void> SyntheticModule::link(VM& vm) |
49 | 0 | { |
50 | | // Note: Has some changes from PR: https://github.com/tc39/proposal-json-modules/pull/13. |
51 | | // Which includes renaming it from Instantiate ( ) to Link ( ). |
52 | | |
53 | | // 1. Let realm be module.[[Realm]]. |
54 | | // 2. Assert: realm is not undefined. |
55 | | // Note: This must be true because we use a reference. |
56 | | |
57 | | // 3. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). |
58 | 0 | auto environment = vm.heap().allocate_without_realm<ModuleEnvironment>(&realm().global_environment()); |
59 | | |
60 | | // 4. Set module.[[Environment]] to env. |
61 | 0 | set_environment(environment); |
62 | | |
63 | | // 5. For each exportName in module.[[ExportNames]], |
64 | 0 | for (auto& export_name : m_export_names) { |
65 | | // a. Perform ! envRec.CreateMutableBinding(exportName, false). |
66 | 0 | MUST(environment->create_mutable_binding(vm, export_name, false)); |
67 | | |
68 | | // b. Perform ! envRec.InitializeBinding(exportName, undefined, normal). |
69 | 0 | MUST(environment->initialize_binding(vm, export_name, js_undefined(), Environment::InitializeBindingHint::Normal)); |
70 | 0 | } |
71 | | |
72 | | // 6. Return unused. |
73 | 0 | return {}; |
74 | 0 | } |
75 | | |
76 | | // 1.2.3.4 Evaluate ( ), https://tc39.es/proposal-json-modules/#sec-smr-Evaluate |
77 | | ThrowCompletionOr<Promise*> SyntheticModule::evaluate(VM& vm) |
78 | 0 | { |
79 | | // Note: Has some changes from PR: https://github.com/tc39/proposal-json-modules/pull/13. |
80 | | // 1. Suspend the currently running execution context. |
81 | | // FIXME: We don't have suspend yet. |
82 | | |
83 | | // 2. Let moduleContext be a new ECMAScript code execution context. |
84 | 0 | auto module_context = ExecutionContext::create(); |
85 | | |
86 | | // 3. Set the Function of moduleContext to null. |
87 | | // Note: This is the default value. |
88 | | |
89 | | // 4. Set the Realm of moduleContext to module.[[Realm]]. |
90 | 0 | module_context->realm = &realm(); |
91 | | |
92 | | // 5. Set the ScriptOrModule of moduleContext to module. |
93 | 0 | module_context->script_or_module = NonnullGCPtr<Module>(*this); |
94 | | |
95 | | // 6. Set the VariableEnvironment of moduleContext to module.[[Environment]]. |
96 | 0 | module_context->variable_environment = environment(); |
97 | | |
98 | | // 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. |
99 | 0 | module_context->lexical_environment = environment(); |
100 | | |
101 | | // 8. Push moduleContext on to the execution context stack; moduleContext is now the running execution context. |
102 | 0 | TRY(vm.push_execution_context(*module_context, {})); |
103 | | |
104 | | // 9. Let result be the result of performing module.[[EvaluationSteps]](module). |
105 | 0 | auto result = m_evaluation_steps(*this); |
106 | | |
107 | | // 10. Suspend moduleContext and remove it from the execution context stack. |
108 | 0 | vm.pop_execution_context(); |
109 | | |
110 | | // 11. Resume the context that is now on the top of the execution context stack as the running execution context. |
111 | | |
112 | | // 12. Return Completion(result). |
113 | | // Note: Because we expect it to return a promise we convert this here. |
114 | 0 | auto promise = Promise::create(realm()); |
115 | 0 | if (result.is_error()) { |
116 | 0 | VERIFY(result.throw_completion().value().has_value()); |
117 | 0 | promise->reject(*result.throw_completion().value()); |
118 | 0 | } else { |
119 | | // Note: This value probably isn't visible to JS code? But undefined is fine anyway. |
120 | 0 | promise->fulfill(js_undefined()); |
121 | 0 | } |
122 | 0 | return promise.ptr(); |
123 | 0 | } |
124 | | |
125 | | // 1.2.2 SetSyntheticModuleExport ( module, exportName, exportValue ), https://tc39.es/proposal-json-modules/#sec-setsyntheticmoduleexport |
126 | | ThrowCompletionOr<void> SyntheticModule::set_synthetic_module_export(DeprecatedFlyString const& export_name, Value export_value) |
127 | 0 | { |
128 | 0 | auto& vm = this->realm().vm(); |
129 | | |
130 | | // Note: Has some changes from PR: https://github.com/tc39/proposal-json-modules/pull/13. |
131 | | // 1. Return ? module.[[Environment]].SetMutableBinding(name, value, true). |
132 | 0 | return environment()->set_mutable_binding(vm, export_name, export_value, true); |
133 | 0 | } |
134 | | |
135 | | // 1.3 CreateDefaultExportSyntheticModule ( defaultExport ), https://tc39.es/proposal-json-modules/#sec-create-default-export-synthetic-module |
136 | | NonnullGCPtr<SyntheticModule> SyntheticModule::create_default_export_synthetic_module(Value default_export, Realm& realm, StringView filename) |
137 | 0 | { |
138 | | // Note: Has some changes from PR: https://github.com/tc39/proposal-json-modules/pull/13. |
139 | | // 1. Let closure be the a Abstract Closure with parameters (module) that captures defaultExport and performs the following steps when called: |
140 | 0 | auto closure = [default_export = make_handle(default_export)](SyntheticModule& module) -> ThrowCompletionOr<void> { |
141 | | // a. Return ? module.SetSyntheticExport("default", defaultExport). |
142 | 0 | return module.set_synthetic_module_export("default", default_export.value()); |
143 | 0 | }; |
144 | | |
145 | | // 2. Return CreateSyntheticModule("default", closure, realm) |
146 | 0 | return realm.heap().allocate_without_realm<SyntheticModule>(Vector<DeprecatedFlyString> { "default" }, move(closure), realm, filename); |
147 | 0 | } |
148 | | |
149 | | // 1.4 ParseJSONModule ( source ), https://tc39.es/proposal-json-modules/#sec-parse-json-module |
150 | | ThrowCompletionOr<NonnullGCPtr<Module>> parse_json_module(StringView source_text, Realm& realm, StringView filename) |
151 | 0 | { |
152 | 0 | auto& vm = realm.vm(); |
153 | | |
154 | | // 1. Let jsonParse be realm's intrinsic object named "%JSON.parse%". |
155 | 0 | auto json_parse = realm.intrinsics().json_parse_function(); |
156 | | |
157 | | // 2. Let json be ? Call(jsonParse, undefined, « sourceText »). |
158 | 0 | auto json = TRY(call(vm, *json_parse, js_undefined(), PrimitiveString::create(realm.vm(), source_text))); |
159 | | |
160 | | // 3. Return CreateDefaultExportSyntheticModule(json, realm, hostDefined). |
161 | 0 | return SyntheticModule::create_default_export_synthetic_module(json, realm, filename); |
162 | 0 | } |
163 | | |
164 | | // 1.2.3.1 LoadRequestedModules ( ), https://tc39.es/proposal-json-modules/#sec-smr-LoadRequestedModules |
165 | | PromiseCapability& SyntheticModule::load_requested_modules(GCPtr<GraphLoadingState::HostDefined>) |
166 | 0 | { |
167 | | // 1. Return ! PromiseResolve(%Promise%, undefined). |
168 | 0 | auto& constructor = *vm().current_realm()->intrinsics().promise_constructor(); |
169 | 0 | auto promise_capability = MUST(new_promise_capability(vm(), &constructor)); |
170 | 0 | MUST(call(vm(), *promise_capability->resolve(), js_undefined(), js_undefined())); |
171 | 0 | return promise_capability; |
172 | 0 | } |
173 | | |
174 | | } |