/** * 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.repl.global; import static com.github.anba.es6draft.repl.global.SharedFunctions.absolutePath; import static com.github.anba.es6draft.repl.global.SharedFunctions.loadScript; import static com.github.anba.es6draft.repl.global.SharedFunctions.readFile; import static com.github.anba.es6draft.repl.global.SharedFunctions.relativePathToScript; import static com.github.anba.es6draft.runtime.AbstractOperations.Get; import static com.github.anba.es6draft.runtime.AbstractOperations.ToBoolean; import static com.github.anba.es6draft.runtime.AbstractOperations.ToFlatString; import static com.github.anba.es6draft.runtime.AbstractOperations.ToInt32; import static com.github.anba.es6draft.runtime.modules.ModuleSemantics.GetModuleNamespace; import static com.github.anba.es6draft.runtime.modules.SourceTextModuleRecord.ParseModule; import static com.github.anba.es6draft.runtime.objects.binary.ArrayBufferConstructor.DetachArrayBuffer; import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Objects; import com.github.anba.es6draft.Executable; import com.github.anba.es6draft.Script; 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.Realm; import com.github.anba.es6draft.runtime.internal.DebugInfo; import com.github.anba.es6draft.runtime.internal.Errors; import com.github.anba.es6draft.runtime.internal.Messages; import com.github.anba.es6draft.runtime.internal.Properties.Function; import com.github.anba.es6draft.runtime.internal.ScriptLoader; import com.github.anba.es6draft.runtime.internal.Source; import com.github.anba.es6draft.runtime.modules.MalformedNameException; import com.github.anba.es6draft.runtime.modules.ModuleLoader; import com.github.anba.es6draft.runtime.modules.ModuleRecord; import com.github.anba.es6draft.runtime.modules.ModuleSource; import com.github.anba.es6draft.runtime.modules.ResolutionException; import com.github.anba.es6draft.runtime.modules.SourceIdentifier; import com.github.anba.es6draft.runtime.modules.SourceTextModuleRecord; import com.github.anba.es6draft.runtime.modules.loader.StringModuleSource; import com.github.anba.es6draft.runtime.objects.ErrorObject; import com.github.anba.es6draft.runtime.objects.GlobalObject; import com.github.anba.es6draft.runtime.objects.binary.ArrayBufferObject; import com.github.anba.es6draft.runtime.objects.collection.WeakMapObject; import com.github.anba.es6draft.runtime.objects.reflect.RealmObject; import com.github.anba.es6draft.runtime.types.Callable; import com.github.anba.es6draft.runtime.types.ScriptObject; import com.github.anba.es6draft.runtime.types.Type; import com.github.anba.es6draft.runtime.types.builtins.FunctionObject; /** * Built-in functions for the default shell. */ public final class ShellFunctions { private static String getResourceInfo(String resourceName, String defaultValue) { try (BufferedReader reader = new BufferedReader(new InputStreamReader( ShellFunctions.class.getResourceAsStream(resourceName), StandardCharsets.UTF_8))) { return reader.readLine(); } catch (IOException e) { return defaultValue; } } /** * shell-function: {@code parseModule(source)} * * @param cx * the execution context * @param source * the source string to compile * @return the status message */ @Function(name = "parseModule", arity = 1) public String parseModule(ExecutionContext cx, String source) { cx.getRealm().getScriptLoader().parseModule(new Source("<module>", 1), source); return "success"; } /** * shell-function: {@code parseScript(source)} * * @param cx * the execution context * @param source * the source string to compile * @return the status message */ @Function(name = "parseScript", arity = 1) public String parseScript(ExecutionContext cx, String source) { cx.getRealm().getScriptLoader().parseScript(new Source("<script>", 1), source); return "success"; } /** * shell-function: {@code compile(filename)} * * @param cx * the execution context * @param filename * the file to load * @return the status message */ @Function(name = "compile", arity = 1) public String compile(ExecutionContext cx, String filename) { try { Path file = absolutePath(cx, Paths.get(filename)); cx.getRealm().getScriptLoader().script(new Source(file, filename, 1), file); } catch (ParserException | CompilationException | IOException e) { return "error: " + e.getMessage(); } return "success"; } /** * shell-function: {@code loadRelativeToScript(filename)} * * @param cx * the execution context * @param caller * the caller execution context * @param filename * the file to load */ @Function(name = "loadRelativeToScript", arity = 1) public void loadRelativeToScript(ExecutionContext cx, ExecutionContext caller, String filename) { Path file = Paths.get(filename); loadScript(cx, file, relativePathToScript(cx, caller, file)); } /** * shell-function: {@code readRelativeToScript(filename)} * * @param cx * the execution context * @param caller * the caller execution context * @param filename * the file to load * @return the result value */ @Function(name = "readRelativeToScript", arity = 1) public Object readRelativeToScript(ExecutionContext cx, ExecutionContext caller, String filename) { Path file = Paths.get(filename); return readFile(cx, file, relativePathToScript(cx, caller, file)); } /** * shell-function: {@code loadModule(moduleName, [realmObject])} * * @param cx * the execution context * @param moduleName * the module name * @param realmObject * the optional realm object * @return the module namespace object * @throws MalformedNameException * if any imported module request cannot be normalized * @throws ResolutionException * if any export binding cannot be resolved */ @Function(name = "loadModule", arity = 1) public ScriptObject loadModule(ExecutionContext cx, String moduleName, Object realmObject) throws MalformedNameException, ResolutionException { Realm realm; if (!Type.isUndefined(realmObject)) { if (!(realmObject instanceof RealmObject)) { throw Errors.newTypeError(cx, Messages.Key.IncompatibleObject); } realm = ((RealmObject) realmObject).getRealm(); } else { realm = cx.getRealm(); } try { ModuleLoader moduleLoader = realm.getModuleLoader(); SourceIdentifier moduleId = moduleLoader.normalizeName(moduleName, null); ModuleRecord module = moduleLoader.resolve(moduleId, realm); module.instantiate(); module.evaluate(); return GetModuleNamespace(cx, module); } catch (IOException e) { throw Errors.newInternalError(cx, e, Messages.Key.ModulesIOException, e.getMessage()); } } /** * shell-function: {@code dump(object)} * * @param cx * the execution context * @param object * the object to inspect */ @Function(name = "dump", arity = 1) public void dump(ExecutionContext cx, Object object) { String id; if (!Type.isType(object)) { // foreign object or null id = Objects.toString(object, "<null>"); } else if (!Type.isObject(object)) { // primitive value id = String.format("%s (%s)", object.toString(), object.getClass().getSimpleName()); } else { // script objects id = String.format("%s@%x", object.getClass().getSimpleName(), System.identityHashCode(object)); } PrintWriter writer = cx.getRuntimeContext().getConsole().writer(); writer.println(id); } /** * shell-function: {@code dumpObject(object)} * * @param cx * the execution context * @param object * the object to inspect */ @Function(name = "dumpObject", arity = 1) public void dumpObject(ExecutionContext cx, ScriptObject object) { PrintWriter writer = cx.getRuntimeContext().getConsole().writer(); writer.println(object.toString()); } /** * shell-function: {@code dumpScope()} * * @param cx * the execution context * @param caller * the caller context */ @Function(name = "dumpScope", arity = 0) public void dumpScope(ExecutionContext cx, ExecutionContext caller) { if (caller.getLexicalEnvironment() != null) { PrintWriter writer = cx.getRuntimeContext().getConsole().writer(); writer.println(caller.getLexicalEnvironment().toString()); } } /** * shell-function: {@code dumpSymbolRegistry()} * * @param cx * the execution context */ @Function(name = "dumpSymbolRegistry", arity = 0) public void dumpSymbolRegistry(ExecutionContext cx) { PrintWriter writer = cx.getRuntimeContext().getConsole().writer(); writer.println(cx.getRealm().getSymbolRegistry().toString()); } /** * shell-function: {@code dumpTemplateMap()} * * @param cx * the execution context */ @Function(name = "dumpTemplateMap", arity = 0) public void dumpTemplateMap(ExecutionContext cx) { PrintWriter writer = cx.getRuntimeContext().getConsole().writer(); writer.println(cx.getRealm().getTemplateMap().toString()); } /** * shell-function: {@code gc()} */ @Function(name = "gc", arity = 0) public void gc() { System.gc(); } /** * shell-function: {@code error()} */ @Function(name = "error", arity = 0) public void error() { throw new AssertionError(); } /** * shell-function: {@code printStackTrace(object)} * * @param object * the error object */ @Function(name = "printStackTrace", arity = 1) public void printStackTrace(ErrorObject object) { object.getException().printStackTrace(); } /** * shell-function: {@code putstr(message)} * * @param cx * the execution context * @param message * the message to write */ @Function(name = "putstr", arity = 1) public void putstr(ExecutionContext cx, String message) { PrintWriter writer = cx.getRuntimeContext().getConsole().writer(); writer.print(message); writer.flush(); } /** * shell-function: {@code nextTick(function)} * * @param cx * the execution context * @param function * the callback function */ @Function(name = "nextTick", arity = 1) public void nextTick(ExecutionContext cx, Callable function) { cx.getRealm().enqueuePromiseTask(() -> function.call(cx, UNDEFINED)); } /** * shell-function: {@code detachArrayBuffer(arrayBuffer)} * * @param cx * the execution context * @param arrayBuffer * the array buffer object */ @Function(name = "detachArrayBuffer", arity = 1) public void detachArrayBuffer(ExecutionContext cx, ArrayBufferObject arrayBuffer) { DetachArrayBuffer(cx, arrayBuffer); } /** * shell-function: {@code weakMapSize(weakMap)} * * @param weakMap * the WeakMap object * @return the WeakMap's current size */ @Function(name = "weakMapSize", arity = 1) public int weakMapSize(WeakMapObject weakMap) { return weakMap.getWeakMapData().size(); } /** * shell-function: {@code version()} * * @return the version string */ @Function(name = "version", arity = 0) public String version() { return getResourceInfo("/version", "<unknown version>"); } /** * shell-function: {@code disassemble([function])} * * @param cx * the execution context * @param caller * the caller context * @param args * the arguments * @throws IOException * if there was any I/O error * @throws MalformedNameException * if the module name cannot be normalized */ @Function(name = "disassemble", arity = 1) public void disassemble(ExecutionContext cx, ExecutionContext caller, Object... args) throws IOException, MalformedNameException { DebugInfo debugInfo = null; if (args.length == 0) { FunctionObject currentFunction = caller.getCurrentFunction(); Executable currentExec = caller.getCurrentExecutable(); if (currentFunction != null && currentFunction.getExecutable() == currentExec) { debugInfo = currentFunction.getCode().debugInfo(); } else if (currentExec != null && currentExec.getSourceObject() != null) { debugInfo = currentExec.getSourceObject().debugInfo(); } } else if (args[0] instanceof FunctionObject) { debugInfo = ((FunctionObject) args[0]).getCode().debugInfo(); } else { String sourceCode = ToFlatString(cx, args[0]); boolean isModule = false; if (args.length > 1 && Type.isObject(args[1])) { isModule = ToBoolean(Get(cx, Type.objectValue(args[1]), "module")); } ScriptLoader scriptLoader = cx.getRealm().getScriptLoader(); if (isModule) { ModuleLoader moduleLoader = cx.getRealm().getModuleLoader(); SourceIdentifier identifier = moduleLoader.normalizeName("disassemble", null); ModuleSource src = new StringModuleSource(identifier, sourceCode); SourceTextModuleRecord module = ParseModule(scriptLoader, identifier, src); debugInfo = module.getScriptCode().getSourceObject().debugInfo(); } else { Source source = new Source("<disassemble>", 1); Script script = scriptLoader.compile(scriptLoader.parseScript(source, sourceCode), "#disassemble"); debugInfo = script.getSourceObject().debugInfo(); } } if (debugInfo != null) { PrintWriter writer = cx.getRuntimeContext().getConsole().writer(); for (DebugInfo.Method method : debugInfo.getMethods()) { writer.println(method.disassemble()); } } } /** * shell-function: {@code evalScript(sourceString, [options])} * * @param cx * the execution context * @param sourceString * the source string * @param options * the options object (optional) * @return the evaluation result */ @Function(name = "evalScript", arity = 1) public Object evalScript(ExecutionContext cx, String sourceString, Object options) { String name = ""; int line = 1; Realm realm = cx.getRealm(); if (Type.isObject(options)) { ScriptObject opts = Type.objectValue(options); Object fileName = Get(cx, opts, "fileName"); if (!Type.isUndefined(fileName)) { name = ToFlatString(cx, fileName); } Object lineNumber = Get(cx, opts, "lineNumber"); if (!Type.isUndefined(lineNumber)) { line = ToInt32(cx, lineNumber); } Object g = Get(cx, opts, "global"); if (!Type.isUndefined(g)) { if (!(g instanceof GlobalObject)) { throw Errors.newError(cx, "invalid global argument"); } realm = ((GlobalObject) g).getRealm(); } Object r = Get(cx, opts, "realm"); if (!Type.isUndefined(r)) { if (!(r instanceof RealmObject)) { throw Errors.newError(cx, "invalid realm argument"); } realm = ((RealmObject) r).getRealm(); } } Source source = new Source(name, line); Script script = realm.getScriptLoader().script(source, sourceString); return script.evaluate(realm); } /** * shell-function: {@code newGlobal()} * * @param cx * the execution context * @return a new global object instance */ @Function(name = "newGlobal", arity = 0) public ScriptObject newGlobal(ExecutionContext cx) { Realm realm; try { realm = cx.getRealm().getWorld().newInitializedRealm(); } catch (IOException | URISyntaxException e) { throw Errors.newError(cx, Objects.toString(e.getMessage(), "")); } return realm.getGlobalThis(); } }