Skip to content

Commit

Permalink
Export named declaration (#636)
Browse files Browse the repository at this point in the history
  • Loading branch information
christianrondeau committed Jan 17, 2022
1 parent 6a87e76 commit 996377d
Show file tree
Hide file tree
Showing 19 changed files with 292 additions and 43 deletions.
42 changes: 42 additions & 0 deletions Jint.Tests/Runtime/ModuleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Xunit;

namespace Jint.Tests.Runtime
{
public class ModuleTests
{
private readonly Engine _engine;

public ModuleTests()
{
_engine = new Engine();
}

[Fact]
public void CanExportNamed()
{
_engine.DefineModule(@"export const value = 'exported value';", "my-module");
var ns = _engine.ImportModule("my-module");

Assert.Equal("exported value", ns.Get("value").AsString());
}

[Fact]
public void CanExportDefault()
{
_engine.DefineModule(@"export default 'exported value';", "my-module");
var ns = _engine.ImportModule("my-module");

Assert.Equal("exported value", ns.Get("default").AsString());
}

[Fact]
public void CanExportAll()
{
_engine.DefineModule(@"export const value = 'exported value';", "module1");
_engine.DefineModule(@"export * from 'module 1';", "module2");
var ns = _engine.ImportModule("module2");

Assert.Equal("exported value", ns.Get("value").AsString());
}
}
}
59 changes: 58 additions & 1 deletion Jint/Engine.Modules.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Esprima;
using Esprima.Ast;
using Jint.Native.Object;
using Jint.Runtime;
using Jint.Runtime.Modules;

namespace Jint
Expand All @@ -9,6 +14,14 @@ public partial class Engine

private readonly Dictionary<ModuleCacheKey, JsModule> _modules = new();

/// <summary>
/// https://tc39.es/ecma262/#sec-getactivescriptormodule
/// </summary>
internal IScriptOrModule GetActiveScriptOrModule()
{
return _executionContexts.GetActiveScriptOrModule();
}

public JsModule LoadModule(string specifier) => LoadModule(null, specifier);

internal JsModule LoadModule(string referencingModuleLocation, string specifier)
Expand All @@ -28,6 +41,50 @@ internal JsModule LoadModule(string referencingModuleLocation, string specifier)
return module;
}

public JsModule DefineModule(string source, string specifier)
{
var module = new JavaScriptParser(source).ParseModule();

return DefineModule(module, specifier);
}

public JsModule DefineModule(Module source, string specifier)
{
var key = new ModuleCacheKey(string.Empty, specifier);

var module = new JsModule(this, _host.CreateRealm(), source, null, false);

_modules[key] = module;

return module;
}

public ObjectInstance ImportModule(string specifier)
{
var key = new ModuleCacheKey(string.Empty, specifier);

if (!_modules.TryGetValue(key, out var module))
throw new ArgumentOutOfRangeException(nameof(specifier), "No module was found for this specified");

if (module.Status == ModuleStatus.Unlinked)
{
module.Link();
}

if (module.Status == ModuleStatus.Linked)
{
// TODO: Do something about that promise
var promise = module.Evaluate();
}

if (module.Status == ModuleStatus.Evaluated)
{
return JsModule.GetModuleNamespace(module);
}

throw new NotSupportedException($"The module is currently in status '{module.Status}'");
}

internal readonly record struct ModuleCacheKey(string ReferencingModuleLocation, string Specifier);
}
}
2 changes: 2 additions & 0 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ internal Options Options
PrivateEnvironmentRecord privateEnvironment)
{
var context = new ExecutionContext(
// TODO: Should this be null?
GetActiveScriptOrModule(),
lexicalEnvironment,
variableEnvironment,
privateEnvironment,
Expand Down
2 changes: 1 addition & 1 deletion Jint/HoistingScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ public void Visit(Node node, Node parent)
}
}

if (parent is null && variableDeclaration.Kind != VariableDeclarationKind.Var)
if (parent is null or Program && variableDeclaration.Kind != VariableDeclarationKind.Var)
{
_lexicalDeclarations ??= new List<VariableDeclaration>();
_lexicalDeclarations.Add(variableDeclaration);
Expand Down
2 changes: 2 additions & 0 deletions Jint/Native/Function/FunctionInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,8 @@ internal ExecutionContext PrepareForOrdinaryCall(JsValue newTarget)
var calleeRealm = _realm;

var calleeContext = new ExecutionContext(
// TODO: Should this be null?
_engine.GetActiveScriptOrModule(),
localEnv,
localEnv,
_privateEnvironment,
Expand Down
8 changes: 5 additions & 3 deletions Jint/Runtime/Environments/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,36 @@ namespace Jint.Runtime.Environments
internal readonly struct ExecutionContext
{
internal ExecutionContext(
IScriptOrModule? scriptOrModule,
EnvironmentRecord lexicalEnvironment,
EnvironmentRecord variableEnvironment,
PrivateEnvironmentRecord? privateEnvironment,
Realm realm,
FunctionInstance? function = null)
{
ScriptOrModule = scriptOrModule;
LexicalEnvironment = lexicalEnvironment;
VariableEnvironment = variableEnvironment;
PrivateEnvironment = privateEnvironment;
Realm = realm;
Function = function;
}

public readonly IScriptOrModule? ScriptOrModule;
public readonly EnvironmentRecord LexicalEnvironment;

public readonly EnvironmentRecord VariableEnvironment;
public readonly PrivateEnvironmentRecord? PrivateEnvironment;
public readonly Realm Realm;
public readonly FunctionInstance? Function;

public ExecutionContext UpdateLexicalEnvironment(EnvironmentRecord lexicalEnvironment)
{
return new ExecutionContext(lexicalEnvironment, VariableEnvironment, PrivateEnvironment, Realm, Function);
return new ExecutionContext(ScriptOrModule, lexicalEnvironment, VariableEnvironment, PrivateEnvironment, Realm, Function);
}

public ExecutionContext UpdateVariableEnvironment(EnvironmentRecord variableEnvironment)
{
return new ExecutionContext(LexicalEnvironment, variableEnvironment, PrivateEnvironment, Realm, Function);
return new ExecutionContext(ScriptOrModule, LexicalEnvironment, variableEnvironment, PrivateEnvironment, Realm, Function);
}

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion Jint/Runtime/Environments/FunctionEnvironmentRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,8 @@ private void HandleArrayPattern(EvaluationContext context, bool initiallyEmpty,
var oldEnv = _engine.ExecutionContext.LexicalEnvironment;
var paramVarEnv = JintEnvironment.NewDeclarativeEnvironment(_engine, oldEnv);

_engine.EnterExecutionContext(new ExecutionContext(paramVarEnv, paramVarEnv, null, _engine.Realm, null));
// TODO: Should this be null?
_engine.EnterExecutionContext(new ExecutionContext(_engine.GetActiveScriptOrModule(), paramVarEnv, paramVarEnv, null, _engine.Realm, null));
try
{
argument = jintExpression.GetValue(context).Value;
Expand Down
15 changes: 8 additions & 7 deletions Jint/Runtime/Environments/ModuleEnvironmentRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,27 @@ public void CreateImportBinding(string importName, JsModule module, string name)
_importBindings[importName] = new IndirectBinding(module, name);
}

// https://tc39.es/ecma262/#sec-module-environment-records-getbindingvalue-n-s
public override JsValue GetBindingValue(string name, bool strict)
{
if (_importBindings.TryGetValue(name, out var indirectBinding))
{
return base.GetBindingValue(name, strict);
return indirectBinding.Module._environment.GetBindingValue(indirectBinding.BindingName, true);
}

return indirectBinding.Module._environment.GetBindingValue(indirectBinding.BindingName, true);
return base.GetBindingValue(name, strict);
}

internal override bool TryGetBinding(in BindingName name, bool strict, out Binding binding, out JsValue value)
{
if (!_importBindings.TryGetValue(name.Key, out var indirectBinding))
if (_importBindings.TryGetValue(name.Key, out var indirectBinding))
{
return base.TryGetBinding(name, strict, out binding, out value);
value = indirectBinding.Module._environment.GetBindingValue(indirectBinding.BindingName, true);
binding = new(value, false, false, true);
return true;
}

value = indirectBinding.Module._environment.GetBindingValue(indirectBinding.BindingName, true);
binding = new(value, false, false, true);
return true;
return base.TryGetBinding(name, strict, out binding, out value);
}

public override bool HasThisBinding() => true;
Expand Down
16 changes: 16 additions & 0 deletions Jint/Runtime/ExecutionContextStack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,21 @@ public void ReplaceTopVariableEnvironment(EnvironmentRecord newEnv)

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref readonly ExecutionContext Pop() => ref _stack.Pop();

public IScriptOrModule? GetActiveScriptOrModule()
{
var array = _stack._array;
var size = _stack._size;
for (var i = size - 1; i > -1; --i)
{
var context = array[i];
if (context.ScriptOrModule is not null)
{
return context.ScriptOrModule;
}
}

return null;
}
}
}
1 change: 1 addition & 0 deletions Jint/Runtime/Host.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ protected virtual void InitializeHostDefinedRealm()
var realm = CreateRealm();

var newContext = new ExecutionContext(
scriptOrModule: Engine.GetActiveScriptOrModule(),
lexicalEnvironment: realm.GlobalEnv,
variableEnvironment: realm.GlobalEnv,
privateEnvironment: null,
Expand Down
10 changes: 10 additions & 0 deletions Jint/Runtime/IScriptOrModule.Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Jint.Runtime.Modules;

#nullable enable

namespace Jint.Runtime;

public static class ScriptOrModuleExtensions
{
public static JsModule? AsModule(this IScriptOrModule? scriptOrModule) => scriptOrModule as JsModule;
}
5 changes: 5 additions & 0 deletions Jint/Runtime/IScriptOrModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Jint.Runtime;

public interface IScriptOrModule
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#nullable enable

using Esprima.Ast;

namespace Jint.Runtime.Interpreter.Statements;

internal sealed class JintExportAllDeclarationStatement : JintExportDeclarationStatement<ExportAllDeclaration>
{
public JintExportAllDeclarationStatement(ExportAllDeclaration statement) : base(statement)
{
}

protected override void Initialize(EvaluationContext context)
{
InitializeDeclaration(context.Engine, _statement.Exported);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using Esprima.Ast;
using Jint.Native;
using Jint.Runtime.Interpreter.Expressions;

#nullable enable

namespace Jint.Runtime.Interpreter.Statements;

internal abstract class JintExportDeclarationStatement<T> : JintStatement<T> where T : ExportDeclaration
{
private JintExpression? _declarationExpression;
private JintStatement? _declarationStatement;

protected JintExportDeclarationStatement(T statement) : base(statement)
{
}

protected void InitializeDeclaration(Engine engine, StatementListItem? declaration)
{
switch (declaration)
{
case null:
return;
case Expression e:
_declarationExpression = JintExpression.Build(engine, e);
break;
case Statement s:
_declarationStatement = Build(s);
break;
default:
throw new NotSupportedException();
}
}

/// <summary>
/// https://tc39.es/ecma262/#sec-exports-runtime-semantics-evaluation
/// </summary>
protected override Completion ExecuteInternal(EvaluationContext context)
{
// TODO: This will be required for default exports
// var module = context.Engine.GetActiveScriptOrModule() as JsModule;
// if (module == null) throw new JavaScriptException("Export can only be used in a module");

if (_declarationStatement != null)
{
// TODO: Not tested
_declarationStatement.Execute(context);
return NormalCompletion(Undefined.Instance);
}

if (_declarationExpression != null)
{
// Named exports don't require anything more since the values are available in the lexical context
return _declarationExpression.GetValue(context);
}

return NormalCompletion(Undefined.Instance);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#nullable enable

using Esprima.Ast;

namespace Jint.Runtime.Interpreter.Statements;

internal sealed class JintExportDefaultDeclarationStatement : JintExportDeclarationStatement<ExportDefaultDeclaration>
{
public JintExportDefaultDeclarationStatement(ExportDefaultDeclaration statement) : base(statement)
{
}

protected override void Initialize(EvaluationContext context)
{
InitializeDeclaration(context.Engine, _statement.Declaration);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#nullable enable

using Esprima.Ast;

namespace Jint.Runtime.Interpreter.Statements;

internal sealed class JintExportNamedDeclarationStatement : JintExportDeclarationStatement<ExportNamedDeclaration>
{
public JintExportNamedDeclarationStatement(ExportNamedDeclaration statement) : base(statement)
{
}

protected override void Initialize(EvaluationContext context)
{
InitializeDeclaration(context.Engine, _statement.Declaration);
}
}

0 comments on commit 996377d

Please sign in to comment.