/** * Copyright (c) 2012-2016 André Bargull * Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms. * * <https://github.com/anba/es6draft> */ package com.github.anba.es6draft.runtime.modules; import static com.github.anba.es6draft.runtime.ExecutionContext.newModuleDeclarationExecutionContext; import static com.github.anba.es6draft.runtime.ExecutionContext.newModuleExecutionContext; import static com.github.anba.es6draft.runtime.LexicalEnvironment.newModuleEnvironment; import static com.github.anba.es6draft.runtime.modules.ModuleSemantics.HostResolveImportedModule; import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED; import static com.github.anba.es6draft.semantics.StaticSemantics.ExportEntries; import static com.github.anba.es6draft.semantics.StaticSemantics.ImportEntries; import static com.github.anba.es6draft.semantics.StaticSemantics.ImportedLocalNames; import static com.github.anba.es6draft.semantics.StaticSemantics.ModuleRequests; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import com.github.anba.es6draft.Module; import com.github.anba.es6draft.compiler.CompilationException; import com.github.anba.es6draft.parser.ParserException; import com.github.anba.es6draft.runtime.ExecutionContext; import com.github.anba.es6draft.runtime.LexicalEnvironment; import com.github.anba.es6draft.runtime.ModuleEnvironmentRecord; import com.github.anba.es6draft.runtime.Realm; import com.github.anba.es6draft.runtime.internal.Messages; import com.github.anba.es6draft.runtime.internal.ScriptLoader; import com.github.anba.es6draft.runtime.types.ScriptObject; /** * 15.2.1.16 Source Text Module Records */ public final class SourceTextModuleRecord implements ModuleRecord, Cloneable { /** * [[SourceCodeId]] */ private final SourceIdentifier sourceCodeId; /** * [[Realm]] */ private Realm realm; /** * [[Environment]] */ private LexicalEnvironment<ModuleEnvironmentRecord> environment; /** * [[Namespace]] */ private ScriptObject namespace; /** * [[Evaluated]] */ private boolean evaluated; /** * [[ECMAScriptCode]] */ private Module scriptCode; /** * [[RequestedModules]] */ private final Set<String> requestedModules; /** * [[ImportEntries]] */ private final List<ImportEntry> importEntries; /** * [[LocalExportEntries]] */ private final List<ExportEntry> localExportEntries; /** * [[IndirectExportEntries]] */ private final List<ExportEntry> indirectExportEntries; /** * [[StarExportEntries]] */ private final List<ExportEntry> starExportEntries; /** * Extension:<br> * [[NameSpaceExportEntries]] */ private final List<ExportEntry> nameSpaceExportEntries; private boolean instantiated; private SourceTextModuleRecord(SourceIdentifier sourceCodeId, Set<String> requestedModules, List<ImportEntry> importEntries, List<ExportEntry> localExportEntries, List<ExportEntry> indirectExportEntries, List<ExportEntry> starExportEntries, List<ExportEntry> nameSpaceExportEntries) { assert sourceCodeId != null; this.sourceCodeId = sourceCodeId; this.requestedModules = unmodifiableOrEmpty(requestedModules); this.importEntries = unmodifiableOrEmpty(importEntries); this.localExportEntries = unmodifiableOrEmpty(localExportEntries); this.indirectExportEntries = unmodifiableOrEmpty(indirectExportEntries); this.starExportEntries = unmodifiableOrEmpty(starExportEntries); this.nameSpaceExportEntries = unmodifiableOrEmpty(nameSpaceExportEntries); } private SourceTextModuleRecord(SourceTextModuleRecord module) { this.sourceCodeId = module.sourceCodeId; this.scriptCode = module.scriptCode; this.requestedModules = module.requestedModules; this.importEntries = module.importEntries; this.localExportEntries = module.localExportEntries; this.indirectExportEntries = module.indirectExportEntries; this.starExportEntries = module.starExportEntries; this.nameSpaceExportEntries = module.nameSpaceExportEntries; } private static <T> Set<T> unmodifiableOrEmpty(Set<T> set) { if (set.isEmpty()) { return Collections.emptySet(); } return Collections.unmodifiableSet(set); } private static <T> List<T> unmodifiableOrEmpty(List<T> list) { if (list.isEmpty()) { return Collections.emptyList(); } return Collections.unmodifiableList(list); } @Override public SourceTextModuleRecord clone() { return new SourceTextModuleRecord(this); } @Override public String toString() { return String.format("[Module = %s]", sourceCodeId); } @Override public boolean equals(Object obj) { return obj == this; } @Override public int hashCode() { return sourceCodeId.hashCode(); } @Override public Realm getRealm() { return realm; } public void setRealm(Realm realm) { assert this.realm == null : "module already linked"; this.realm = Objects.requireNonNull(realm); } /** * [[SourceCodeId]] * * @return the module source code identifier */ @Override public SourceIdentifier getSourceCodeId() { return sourceCodeId; } /** * [[ECMAScriptCode]] * * @return the script code for this module */ public Module getScriptCode() { return scriptCode; } /** * [[RequestedModules]] * * @return the list of requested modules */ public Set<String> getRequestedModules() { return requestedModules; } /** * [[ImportEntries]] * * @return the list of {@code import} entries */ public List<ImportEntry> getImportEntries() { return importEntries; } /** * [[LocalExportEntries]] * * @return the list of local {@code export} entries */ public List<ExportEntry> getLocalExportEntries() { return localExportEntries; } /** * [[IndirectExportEntries]] * * @return the list of indirect {@code export} entries */ public List<ExportEntry> getIndirectExportEntries() { return indirectExportEntries; } /** * [[StarExportEntries]] * * @return the list of {@code export*} entries */ public List<ExportEntry> getStarExportEntries() { return starExportEntries; } /** * Extension:<br> * [[NameSpaceExportEntries]] * * @return the list of {@code export* as} entries */ public List<ExportEntry> getNameSpaceExportEntries() { return nameSpaceExportEntries; } @Override public LexicalEnvironment<ModuleEnvironmentRecord> getEnvironment() { return environment; } @Override public ScriptObject getNamespace() { return namespace; } @Override public void setNamespace(ScriptObject namespace) { assert this.namespace == null : "namespace already created"; this.namespace = Objects.requireNonNull(namespace); } @Override public boolean isEvaluated() { return evaluated; } @Override public boolean isInstantiated() { return instantiated; } /** * 15.2.1.16.1 Runtime Semantics: ParseModule ( sourceText ) * * @param scriptLoader * the script loader * @param sourceCodeId * the source code identifier * @param source * the module source code * @return the parsed module record * @throws IOException * if there was any I/O error * @throws ParserException * if the module source contains any syntax errors * @throws CompilationException * if the parsed module source cannot be compiled */ public static SourceTextModuleRecord ParseModule(ScriptLoader scriptLoader, SourceIdentifier sourceCodeId, ModuleSource source) throws IOException, ParserException, CompilationException { /* step 1 (not applicable) */ /* steps 2-3 */ com.github.anba.es6draft.ast.Module parsedBody = scriptLoader.parseModule( source.toSource(), source.sourceCode()); /* steps 4-12 */ return ParseModule(scriptLoader, sourceCodeId, parsedBody); } /** * 15.2.1.16.1 Runtime Semantics: ParseModule ( sourceText ) * * @param scriptLoader * the script loader * @param sourceCodeId * the source code identifier * @param parsedBody * the parsed module source code * @return the parsed module record * @throws CompilationException * if the parsed module source cannot be compiled */ public static SourceTextModuleRecord ParseModule(ScriptLoader scriptLoader, SourceIdentifier sourceCodeId, com.github.anba.es6draft.ast.Module parsedBody) throws CompilationException { /* steps 1-3 (not applicable) */ /* step 4 */ Set<String> requestedModules = ModuleRequests(parsedBody); /* step 5 */ List<ImportEntry> importEntries = ImportEntries(parsedBody); /* step 6 */ Map<String, ImportEntry> importedBoundNames = ImportedLocalNames(importEntries); /* step 7 */ ArrayList<ExportEntry> indirectExportEntries = new ArrayList<>(); /* step 8 */ ArrayList<ExportEntry> localExportEntries = new ArrayList<>(); /* step 9 */ ArrayList<ExportEntry> starExportEntries = new ArrayList<>(); /* step ? (Extension: Export From) */ ArrayList<ExportEntry> nameSpaceExportEntries = new ArrayList<>(); /* step 10 */ List<ExportEntry> exportEntries = ExportEntries(parsedBody); /* step 11 */ for (ExportEntry exportEntry : exportEntries) { if (exportEntry.getModuleRequest() == null) { ImportEntry importEntry = importedBoundNames.get(exportEntry.getLocalName()); if (importEntry == null || importEntry.isStarImport()) { localExportEntries.add(exportEntry); } else { indirectExportEntries.add(new ExportEntry(exportEntry.getSourcePosition(), importEntry.getModuleRequest(), importEntry.getImportName(), null, exportEntry.getExportName())); } } else if (exportEntry.isStarExport()) { starExportEntries.add(exportEntry); } else if (exportEntry.isNameSpaceExport()) { nameSpaceExportEntries.add(exportEntry); } else { indirectExportEntries.add(exportEntry); } } /* step 12 */ SourceTextModuleRecord m = new SourceTextModuleRecord(sourceCodeId, requestedModules, importEntries, localExportEntries, indirectExportEntries, starExportEntries, nameSpaceExportEntries); m.scriptCode = scriptLoader.load(parsedBody, m); return m; } /** * 15.2.1.16.2 GetExportedNames( exportStarSet ) Concrete Method */ @Override public Set<String> getExportedNames(Set<ModuleRecord> exportStarSet) throws IOException, MalformedNameException, ResolutionException { /* step 1 */ SourceTextModuleRecord module = this; /* steps 2-3 */ if (!exportStarSet.add(module)) { return Collections.emptySet(); } /* step 4 */ HashSet<String> exportedNames = new HashSet<>(); /* step 5 */ for (ExportEntry exportEntry : module.localExportEntries) { /* step 5.a (not applicable) */ /* step 5.b */ exportedNames.add(exportEntry.getExportName()); } /* step 6 */ for (ExportEntry exportEntry : module.indirectExportEntries) { /* step 6.a (not applicable) */ /* step 6.b */ exportedNames.add(exportEntry.getExportName()); } /* step 7 */ for (ExportEntry exportEntry : module.starExportEntries) { /* steps 7.a-b */ ModuleRecord requestedModule = HostResolveImportedModule(module, exportEntry.getModuleRequest()); /* step 7.c */ Set<String> starNames = requestedModule.getExportedNames(exportStarSet); /* step 7.d */ for (String n : starNames) { if (!"default".equals(n)) { exportedNames.add(n); } } } /* step ? (Extension: Export From) */ for (ExportEntry exportEntry : module.nameSpaceExportEntries) { exportedNames.add(exportEntry.getExportName()); } /* step 8 */ return exportedNames; } /** * 15.2.1.16.3 ResolveExport(exportName, resolveSet, exportStarSet) Concrete Method */ @Override public ModuleExport resolveExport(String exportName, Map<ModuleRecord, Set<String>> resolveSet, Set<ModuleRecord> exportStarSet) throws IOException, MalformedNameException, ResolutionException { /* step 1 */ SourceTextModuleRecord module = this; /* step 2 */ Set<String> resolvedExports = resolveSet.get(module); if (resolvedExports == null) { resolveSet.put(module, resolvedExports = new HashSet<>()); } else if (resolvedExports.contains(exportName)) { return null; } /* step 3 */ resolvedExports.add(exportName); /* step 4 */ for (ExportEntry exportEntry : module.localExportEntries) { if (exportName.equals(exportEntry.getExportName())) { /* step 4.a.i (not applicable) */ /* step 4.a.ii */ return new ModuleExport(module, exportEntry.getLocalName()); } } /* step 5 */ for (ExportEntry exportEntry : module.indirectExportEntries) { if (exportName.equals(exportEntry.getExportName())) { /* step 5.a.i (not applicable) */ /* steps 5.a.ii-iii */ ModuleRecord importedModule = HostResolveImportedModule(module, exportEntry.getModuleRequest()); /* steps 5.a.iv-v */ ModuleExport indirectResolution = importedModule.resolveExport( exportEntry.getImportName(), resolveSet, exportStarSet); /* step 5.a.vi */ if (indirectResolution != null) { return indirectResolution; } } } /* step ? (Extension: Export From) */ for (ExportEntry exportEntry : module.nameSpaceExportEntries) { if (exportName.equals(exportEntry.getExportName())) { ModuleRecord importedModule = HostResolveImportedModule(module, exportEntry.getModuleRequest()); return new ModuleExport(importedModule); } } /* step 6 */ if ("default".equals(exportName)) { /* step 6.a (not applicable) */ /* step 6.b */ throw new ResolutionException(Messages.Key.ModulesMissingDefaultExport, module.sourceCodeId.toString()); } /* steps 7-8 */ if (!exportStarSet.add(module)) { return null; } /* step 8 */ ModuleExport starResolution = null; /* step 9 */ for (ExportEntry exportEntry : module.starExportEntries) { /* steps 9.a-b */ ModuleRecord importedModule = HostResolveImportedModule(module, exportEntry.getModuleRequest()); /* steps 9.c-d */ ModuleExport resolution = importedModule.resolveExport(exportName, resolveSet, exportStarSet); /* step 9.e */ if (resolution == ModuleExport.AMBIGUOUS) { return ModuleExport.AMBIGUOUS; } /* step 9.f */ if (resolution != null) { if (starResolution == null) { starResolution = resolution; } else { if (resolution.getModule() != starResolution.getModule()) { return ModuleExport.AMBIGUOUS; } if (!Objects.equals(resolution.getBindingName(), starResolution.getBindingName())) { return ModuleExport.AMBIGUOUS; } } } } /* step 11 */ return starResolution; } /** * 15.2.1.16.4 ModuleDeclarationInstantiation( ) Concrete Method */ @Override public void instantiate() throws IOException, MalformedNameException, ResolutionException { /* step 1 */ SourceTextModuleRecord module = this; /* step 2 */ Realm realm = module.realm; /* step 3 */ assert realm != null : "module is not linked"; /* step 4 */ Module code = module.scriptCode; /* step 5 */ if (module.environment != null) { return; } /* step 6 */ LexicalEnvironment<ModuleEnvironmentRecord> env = newModuleEnvironment(realm.getGlobalEnv()); /* step 7 */ module.environment = env; /* step 8 */ for (String required : module.requestedModules) { /* step 8.a (note) */ /* steps 8.b-c */ ModuleRecord requiredModule = HostResolveImportedModule(module, required); /* steps 8.d-e */ requiredModule.instantiate(); } /* steps 9-17 */ ExecutionContext context = newModuleDeclarationExecutionContext(realm, code); code.getModuleBody().moduleDeclarationInstantiation(context, this, env); module.instantiated = true; } /** * 15.2.1.16.5 ModuleEvaluation() Concrete Method */ @Override public Object evaluate() throws IOException, MalformedNameException, ResolutionException { /* step 1 */ SourceTextModuleRecord module = this; /* step 2 */ // FIXME: spec issue - successful completion of ModuleDeclarationInstantiation incorrect? // assert module.instantiated; assert module.environment != null : "module is not instantiated"; /* step 3 */ Realm realm = module.realm; assert realm != null : "module is not linked"; /* step 4 */ if (module.evaluated) { return UNDEFINED; } /* step 5 */ module.evaluated = true; // ModuleDeclarationInstantiation did not complete successfully - stop evaluation. if (!this.instantiated) { return UNDEFINED; } /* step 6 */ for (String required : module.requestedModules) { /* steps 6.a-b */ ModuleRecord requiredModule = HostResolveImportedModule(module, required); /* steps 6.c-d */ requiredModule.evaluate(); } /* steps 7-12 */ ExecutionContext moduleContext = newModuleExecutionContext(realm, module); /* steps 13-14 */ ExecutionContext oldScriptContext = realm.getScriptContext(); try { realm.setScriptContext(moduleContext); /* step 15 */ Object result = module.scriptCode.evaluate(moduleContext); /* step 18 */ return result; } finally { /* steps 16-17 */ realm.setScriptContext(oldScriptContext); } } }