/* --------------------------------------------------------- * * __________ D E L T A S C R I P T * * (_________() * * / === / - A fast, dynamic scripting language * * | == | - Version 4.13.11.0 * * / === / - Developed by Adam R. Nelson * * | = = | - 2011-2013 * * / === / - Distributed under GNU LGPL v3 * * (________() - http://github.com/ar-nelson/deltascript * * * * --------------------------------------------------------- */ package com.sector91.delta.script; import static com.sector91.delta.script.objects.DS_Tag.tag; import com.sector91.delta.script.instrs.DSInstr; import com.sector91.delta.script.objects.DS_Array; import com.sector91.delta.script.objects.DS_Blank; import com.sector91.delta.script.objects.DS_Boolean; import com.sector91.delta.script.objects.DS_Callable; import com.sector91.delta.script.objects.DS_List; import com.sector91.delta.script.objects.DS_Map; import com.sector91.delta.script.objects.DS_Object; import com.sector91.delta.script.objects.DS_Scalar; import com.sector91.delta.script.objects.DS_Scope; import com.sector91.delta.script.objects.DS_Set; import com.sector91.delta.script.objects.DS_String; import com.sector91.delta.script.objects.DS_Tag; import com.sector91.delta.script.objects.ScalarFactory; import com.sector91.delta.script.objects.VectorFactory; import com.sector91.delta.script.objects.reflect.DS_JavaClass; import com.sector91.delta.script.objects.reflect.DS_JavaObject; import com.sector91.delta.script.parser.DScriptBinaryReader; import com.sector91.delta.script.parser.DScriptParserException; import com.sector91.delta.script.parser.DScriptParser; import com.sector91.geom.Shape; import com.sector91.geom.Vector; import com.sector91.util.Sector91Version; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.charset.Charset; import java.util.List; import java.util.Map; import java.util.Set; /** * <p>Contains static utility methods used to compile and execute DeltaScript * code.</p> * * @author Adam R. Nelson * @version 4.13.11.0 */ public final class DeltaScript { // Static Methods // ---------------------------------------------------- /** The name of the scripting language. */ public static final String NAME = "DeltaScript"; /** Guess who! */ public static final String AUTHOR = "Adam R. Nelson"; /** The DeltaScript version that this class represents. */ public static final Sector91Version VERSION = new Sector91Version(4, 13, 11, 0); /** The website of DeltaScript's creator. */ public static final String WEBSITE = "http://github.com/ar-nelson/deltascript"; /** The file encoding of DeltaScript files. */ public static final Charset FILE_ENCODING = Charset.forName("UTF-8"); /** The extension of DeltaScript source files: {@code .ds} */ public static final String SRC_EXT = ".ds"; /** The extension of DeltaScript binary files: {@code .dsc} */ public static final String BIN_EXT = ".dsc"; /** The extension of DeltaScript module files: {@code .dsm} */ public static final String MODULE_EXT = ".dsm"; /** The name of the "sentinel" file that marks a folder as a DeltaScript * module: {@code module.ds} */ public static final String MODULE_FILENAME = "module.ds"; /** The name of the function that, if it exists, will be executed after * all of the subfiles of a module have been loaded: {@code main} */ public static final DS_Tag MODULE_MAIN_FUNC_NAME = tag("main"); /** The name of the callable that will be called in the event of an error.*/ public static final DS_Tag ERROR_FUNC_NAME = tag("error"); /** The DeltaScript Standard Library. */ public static final DS_JavaClass STDLIB = DS_JavaClass.fromClass( DScriptStandardLibrary.class); public static final int SCOPE_HASH_BUCKETS = 16; private DeltaScript() {} /** * <p>Compiles DeltaScript source code into a {@link CompiledDeltaScript} * object, which can be executed faster (using * {@link #exec(CompiledDeltaScript, DS_Scope)}) than interpreting raw * source code through {@link #eval(String, String, DS_Scope)}.</p> * * @param scriptText The DeltaScript source to compile. * @return A CompiledDeltaScript, which can be run with {@code exec}. * @throws DScriptErr If something goes wrong parsing the code. */ public static DSInstr compile(String scriptText) throws DScriptParserException {return compile(scriptText, new DScriptContext());} /** * <p>Compiles DeltaScript source code into a {@link CompiledDeltaScript} * object, which can be executed faster (using * {@link #exec(CompiledDeltaScript, DS_Scope)}) than interpreting raw * source code through {@link #eval(String, String, DS_Scope)}.</p> * * @param scriptText The DeltaScript source to compile. * @param context The context in which to compile the code. * @return A CompiledDeltaScript, which can be run with {@code exec}. * @throws DScriptErr If something goes wrong parsing the code. */ public static DSInstr compile(String scriptText, DScriptContext context) throws DScriptParserException {return compile(scriptText, "Unnamed Script", context);} /** * <p>Compiles DeltaScript source code into a {@link CompiledDeltaScript} * object, which can be executed faster (using {@link #exec( * CompiledDeltaScript, DS_Scope)}) than interpreting raw source code * through {@link #eval(String, String, DS_Scope)}.</p> * * @param scriptText The DeltaScript source to compile. * @param scriptName The name of the script, for debugging purposes. * @return A CompiledDeltaScript, which can be run with {@code exec}. * @throws DScriptErr If something goes wrong parsing the code. */ public static DSInstr compile(String scriptText, String scriptName) throws DScriptParserException {return compile(scriptText, scriptName, new DScriptContext());} /** * <p>Compiles DeltaScript source code into a {@link CompiledDeltaScript} * object, which can be executed faster (using {@link #exec( * CompiledDeltaScript, DS_Scope)}) than interpreting raw source code * through {@link #eval(String, String, DS_Scope)}.</p> * * @param scriptText The DeltaScript source to compile. * @param scriptName The name of the script, for debugging purposes. * @param context The context in which to compile the code. * @return A CompiledDeltaScript, which can be run with the DScriptEngine. * @throws DScriptErr If something goes wrong parsing the code. */ public static DSInstr compile(String scriptText, String scriptName, DScriptContext context) throws DScriptParserException {return new DScriptParser(context).compile(scriptText, scriptName);} /** * <p>Evaluates DeltaScript source code. The code is compiled into a {@link * CompiledDeltaScript} object, which is then executed in a new scope.</p> * * @param scriptText The DeltaScript source to execute. * @param scriptName The name of the script, for debugging purposes. * @return The return value of the last evaluated statement in the script. * @throws DScriptErr If any error occurs in parsing or executing the * script. */ public static DS_Object eval(String scriptText, String scriptName) throws DScriptErr, DScriptParserException { final DScriptContext context = new DScriptContext(); return eval(scriptText, scriptName, context.createScope(), context); } /** * <p>Evaluates DeltaScript source code. The code is compiled into a {@link * CompiledDeltaScript} object, which is then executed in the given scope. * </p> * * <p>Equivalent to the following code: {@code * DScriptEngine.exec(DScriptEngine.compile(scriptText, scriptName), scope); * }</p> * * @param scriptText The DeltaScript source to execute. * @param scriptName The name of the script, for debugging purposes. * @param scope The scope to execute the script in. * @return The return value of the last evaluated statement in the script. * @throws DScriptErr If any error occurs in parsing or executing the * script. */ public static DS_Object eval(String scriptText, String scriptName, DS_Scope scope) throws DScriptErr, DScriptParserException {return eval(scriptText, scriptName, scope, new DScriptContext());} /** * <p>Evaluates DeltaScript source code. The code is compiled into a {@link * CompiledDeltaScript} object, which is then executed in the given scope. * </p> * * <p>Equivalent to the following code: * <pre> * DScriptEngine.exec( * DScriptEngine.compile(scriptText, scriptName, context), * scope, context); * </pre></p> * * @param scriptText The DeltaScript source to execute. * @param scriptName The name of the script, for debugging purposes. * @param scope The scope to execute the script in. * @param context The context in which to compile and execute the script. * @return The return value of the last evaluated statement in the script. * @throws DScriptErr If any error occurs in parsing or executing the * script. */ public static DS_Object eval(String scriptText, String scriptName, DS_Scope scope, DScriptContext context) throws DScriptErr, DScriptParserException {return exec(compile(scriptText, scriptName, context), scope, context);} /** * <p>Executes compiled DeltaScript scripts in the given scope.</p> * * @param script The compiled DeltaScript script to execute. * @param scope The scope to execute the script in. * @return The return value of the last evaluated statement in the script. * @throws DScriptErr If any error occurs in executing the script. */ public static DS_Object exec(DSInstr instr, DS_Scope scope) throws DScriptErr {return exec(instr, scope, null);} /** * <p>Executes compiled DeltaScript scripts in a new scope in the given * context.</p> * * @param script The compiled DeltaScript script to execute. * @param context The {@link DScriptContext} to execute the script in. * @return The return value of the last evaluated statement in the script. * @throws DScriptErr If any error occurs in executing the script. */ public static DS_Object exec(DSInstr instr, DScriptContext context) throws DScriptErr {return exec(instr, context.createScope(), context);} /** * <p>Executes compiled DeltaScript scripts in the given scope and context. * </p> * * @param script The compiled DeltaScript script to execute. * @param scope The scope to execute the script in. * @param context The {@link DScriptContext} to execute the script in. * @return The return value of the last evaluated statement in the script. * @throws DScriptErr If any error occurs in executing the script. */ public static DS_Object exec(DSInstr instr, DS_Scope scope, DScriptContext context) throws DScriptErr { DScriptExecutor exc = new DScriptExecutor(instr,scope,false,context); return exc.execInstance(); } /** * <p>Mostly for internal use. Unless you know what you're doing, it's * recommended that you use {@link #exec(CompiledDeltaScript, DS_Scope)} * instead.</p> * * @param script The compiled DeltaScript script to execute. * @param scope The scope to execute the script in. * @param context The {@link DScriptContext} to execute the script in. * @return The return value of the last evaluated statement in the script. * @throws DScriptErr If any error occurs in executing the script. */ public static DS_Object execAsFunctionCall(DSInstr instr, DS_Scope scope, DScriptContext context) throws DScriptErr { DScriptExecutor exc = new DScriptExecutor(instr, scope, true, context); return exc.execInstance(); } /** * <p>Loads and executes a DeltaScript source/binary file or package in the * given context.</p> * * <p>If {@code context} is non-null and {@code f} is a relative path, * {@code f} will be resolved <em>relative to the current working directory * of the context</em>, not to the current working directory of the JVM. The * file will be executed in a new scope with a new context based off of * {@code context}, but with its current working directory set to the parent * directory of the script file.</p> * * @param f The {@link File} to load the script from. It must be either a * file with the extension {@link #SRC_EXT} or {@link #BIN_EXT}, * or a folder containing a file named {@link #PACKAGE_FILENAME}. * @param context The context to execute the script in. * @param returnScope If true, this method will return the scope that the * file was executed in, rather than the return value of the last * statement executed * @return Depending on {@code returnScope}, either the scope that the file * was executed in, or the return value of the last evaluated * statement in the script. * @throws IOException If an error occurs while loading/accessing the script * file, or if the file is not a DeltaScript file. * @throws DScriptErr If a runtime error occurs while executing the * script, or if the given context does not have permission to load * script files. * @throws DScriptParserException If an error occurs while parsing the * script file. * @since 3.12.3.0 * @see #execModule(File, DS_Scope) */ public static DS_Object execFile(File f, DScriptContext context, boolean returnScope) throws IOException, DScriptParserException { if (!f.isAbsolute() && context != null) { // Use the Current Working Directory of the DScriptContext, // *not* of the Java VM! f = new File( context.getCurrentWorkingDirectory(), f.getPath() ).getAbsoluteFile(); } //if (f.isDirectory()) // return execModule(f, scope); final DScriptContext newContext; if (context != null) newContext = context.clone(); else newContext = new DScriptContext(); newContext.setCurrentWorkingDirectory(f.getParentFile()); DSInstr script; final FileInputStream fileStream = new FileInputStream(f); try { if (f.getName().toLowerCase().endsWith(SRC_EXT)) { final BufferedReader br = new BufferedReader( new InputStreamReader(fileStream, FILE_ENCODING)); final StringBuffer data = new StringBuffer(); for (String ln=br.readLine(); ln != null; ln=br.readLine()) data.append(ln+"\n"); script = compile(data.toString(), f.getName(), newContext); } else if (f.getName().toLowerCase().endsWith(BIN_EXT)) script = new DScriptBinaryReader(fileStream).read( newContext, f.getName()); else throw new IOException("The file \"" + f.getName() + "\" is" + " not a DeltaScript file. DeltaScript filenames must end with" + " \"" + SRC_EXT + "\" or \"" + BIN_EXT + "\"."); } finally {fileStream.close();} final DS_Scope scope = newContext.createScope(); final DS_Object retval = exec(script, scope, newContext); return returnScope ? scope : retval; } /** * <p>Loads and executes a DeltaScript module folder in the given scope.</p> * * <p>A folder is considered a DeltaScript module if it contains a file * named {@link #MODULE_FILENAME}. This file will be executed first, in a * the given scope. After this, any files in the folder with the * {@link #SRC_EXT} or {@link #BIN_EXT} extensions will be loaded, in * lexicographic order, into subscopes of this scope with the same names * as the files, using {@link #execFile(File, DS_Scope)}. Finally, if the * package scope contains a function named {@link #PACKAGE_MAIN_FUNC_NAME}, * that function will be called.</p> * * <p>If {@code scope} has an associated {@link DScriptContext} and {@code * f} is a relative path, {@code f} will be resolved <em>relative to the * current working directory of the {@code DScriptContext}</em>, not to the * current working directory of the JVM. The given scope will be associated * with a new {@code DScriptContext} instance with its current working * directory set to the package folder.</p> * * @param f The {@link File} to load the script from. It must represent a * folder, and the folder must contain a file named * {@link #PACKAGE_FILENAME}. * @param scope The scope to execute the script in. * @return The given scope, after the execution has finished. */ public static DS_Scope execModule(File f, DS_Scope scope) throws DScriptParserException { throw new UnsupportedOperationException(); /*if (!f.isAbsolute() && scope.getEngineInstance() != null) { // Use the Current Working Directory of the DScriptEngine, // *not* of the Java VM! f = new File( scope.getEngineInstance().getContext() .getCurrentWorkingDirectory(), f.getPath() ).getAbsoluteFile(); } else f = f.getAbsoluteFile(); if (!f.isDirectory()) throw new DScriptException("The path \"" + f.getPath() + "\" is not" + " a DeltaScript package, because it does not represent" + " a directory. DeltaScript packages are directories" + " containing a file named " + PACKAGE_FILENAME + "."); // Execute the package root file. final File[] files = f.listFiles(); boolean foundPackageFile = false; for (File subfile : files) { if (subfile.isFile() && PACKAGE_FILENAME.equals(subfile.getName())) { execFile(subfile, scope); // This will associate the current // scope with a new DScriptEngine. foundPackageFile = true; break; } } if (!foundPackageFile) throw new DScriptException("The directory \"" + f.getPath() + "\" is" + " not a DeltaScript package, because it does not contain" + " a file named " + PACKAGE_FILENAME + "."); // Execute package files in subscopes. List<DS_Scope> scopes = new ArrayList<DS_Scope>(); List<String> scopeNames = new ArrayList<String>(); for (File subfile : files) { final String filename = subfile.getName(); if (filename.endsWith(SRC_EXT) || filename.endsWith(BIN_EXT)) { if (PACKAGE_FILENAME.equals(filename)) continue; // If there are both binary and source versions of a file, // go with the source version. final String nameWithoutExt = filename.substring(0, filename.lastIndexOf('.')); if (filename.endsWith(BIN_EXT)) { File dupFile = null; for (File potentialDup : files) { if (potentialDup.getName().equals( nameWithoutExt+SRC_EXT)) {dupFile = potentialDup; break;} } if (dupFile != null) continue; } // Execute the file in a named subscope, but don't add it to // the package scope yet! Files are loaded in an arbtrary // order, and should never depend on each other's existence // while loading. final DS_Scope fileScope = scope.createSubscope(); execFile(subfile, fileScope); scopes.add(fileScope); scopeNames.add(identifierize(nameWithoutExt)); } } // _Now_ add the scopes to the package scope. for (int i=0; i<scopes.size(); i++) scope.set(tag(scopeNames.get(i)), scopes.get(i)); System.out.println(scope); // If the package has a main function, call it now, after everything // has loaded. DS_Object mainFunc = scope.getLocal(PACKAGE_MAIN_FUNC_NAME); if (mainFunc instanceof DS_Callable) ((DS_Callable)mainFunc).call(scope); // Return the now-populated scope. return scope;*/ } /** * <p>Calls a named function, closure or other callable in the given scope * and returns its result.</p> * * @param name The identifier of the defined callable to call. * @param scope The scope in which to find the callable and execute it. * @param args The arguments (if any) to call the callable with. * @return The return value of the callable. * @throws DScriptErr If the given identifier is not defined or not * callable, or if another error occurs. */ public static DS_Object call(String name, DS_Scope scope, DS_Object... args) throws DScriptErr { DS_Object var = scope.dotGet(DS_Tag.tag(name)); if (var == null) throw new DScriptErr(name + " is undefined in this scope."); if (!(var instanceof DS_Callable)) throw new DScriptErr(name + " is not callable."); return ((DS_Callable)var).call(args); } /** * <p>Takes a Java object and <i>boxes</i> it as a {@link DS_Object}. It * tries built-in mappings of Java objects to DeltaScript objects first, * and, if that doesn't work, checks for user-defined mappings, which can * be defined with {@link #addBoxMapping(Class, Constructor)}. If no mapping * is found, this method defaults to boxing the object as a * {@link DS_JavaObject}.</p> * * <p>The inverse of this operation is calling {@link * DS_Object#unbox()}.</p> * * @param obj The object to box. * @return A {@link DS_Object} that either contains the given object or is * roughly equivalent to it. */ public static DS_Object box(Object obj) { if (obj instanceof DS_Object) return (DS_Object)obj; else if (obj == null) return DS_Blank.BLANK; else if (obj instanceof Boolean) return DS_Boolean.box((Boolean)obj); else if (obj instanceof List<?>) { List<?> src = (List<?>)obj; DS_List dst = new DS_List(); for (int i=0; i<src.size(); i++) dst.add(box(src.get(i))); return dst; } else if (obj instanceof Map<?,?>) { Map<?,?> src = (Map<?,?>)obj; DS_Map dst = new DS_Map(); for (Map.Entry<?,?> e : src.entrySet()) dst.put(box(e.getKey()), box(e.getValue())); return dst; } else if (obj instanceof Set<?>) { Set<?> src = (Set<?>)obj; DS_Set dst = new DS_Set(); for (Object item : src) dst.add(box(item)); return dst; } else if (obj instanceof Vector) return DScriptContext.getContextGeometry().convert((Vector)obj); else if (obj instanceof Shape) return DScriptContext.getContextGeometry().convert((Shape)obj); final Class<?> cls = obj.getClass(); if (cls == String.class) return new DS_String((String)obj); else if (obj instanceof Number) { final Number n = (Number)obj; if (cls == Long.class) return ScalarFactory.fromLong(n.longValue()); else if (cls == Float.class) return ScalarFactory.fromFloat(n.floatValue()); else if (cls == Integer.class || cls == Short.class || cls == Byte.class) return ScalarFactory.fromInt(n.intValue()); else if (cls == Double.class) return ScalarFactory.fromDouble(n.doubleValue()); else if (cls == BigInteger.class) return ScalarFactory.fromBigInteger((BigInteger)n); else if (cls == BigDecimal.class) return ScalarFactory.fromBigDecimal((BigDecimal)n); throw new UnsupportedOperationException(); } else if (cls.isArray()) { if (cls == float[].class) return VectorFactory.fromArray((float[])obj, NumberTypes.SHORT_FLOAT); else if (cls == int[].class) return VectorFactory.fromArray((int[])obj, NumberTypes.SHORT_INT); else if (cls == double[].class) return VectorFactory.fromArray((double[])obj, NumberTypes.LONG_FLOAT); else if (cls == long[].class) return VectorFactory.fromArray((long[])obj, NumberTypes.LONG_INT); else if (cls == boolean[].class) return VectorFactory.bitVector((boolean[])obj); // TODO: BigInteger, BigDecimal DS_Object[] arr = new DS_Object[Array.getLength(obj)]; for (int i=0; i<arr.length; i++) arr[i] = DeltaScript.box(Array.get(obj, i)); return new DS_Array(arr); } else if (DScriptContext.get() != null) { Map<Class<?>, Converter<?, ?>> conversions = DScriptContext.get().getConversions(); if (conversions.containsKey(cls)) { try { return ((Converter<Object, ?>)conversions.get( obj.getClass())).convert(obj); } catch (Exception ex) {throw new RuntimeException("Failed to box type " + obj.getClass().getName() + ": " + ex.getMessage(), ex);} } } return new DS_JavaObject<Object>(obj); } public static float floatValue(DS_Object var) throws DScriptErr { if (var instanceof DS_Scalar) return ((DS_Scalar)var).floatValue(); else throw new DScriptErr("Scalar expected.", DScriptErr.T_INVALID_TYPE, DScriptErr.T_NOT_NUMBER); } public static int intValue(DS_Object var) throws DScriptErr { if (var instanceof DS_Scalar && ((DS_Scalar)var).isIntegral()) return ((DS_Scalar)var).intValue(); else throw new DScriptErr("Integer expected.", DScriptErr.T_INVALID_TYPE,DScriptErr.T_NOT_INTEGER); } public static long longValue(DS_Object var) throws DScriptErr { if (var instanceof DS_Scalar && ((DS_Scalar)var).isIntegral()) return ((DS_Scalar)var).longValue(); else throw new DScriptErr("Integer expected.", DScriptErr.T_INVALID_TYPE,DScriptErr.T_NOT_INTEGER); } /** * <p>Converts {@code str} into a valid DeltaScript identifier. Any * non-alphanumeric characters in the string are replaced by underscores, * and an underscore is added to the beginning of strings that start with * numbers.</p> * * @param str The string to convert into a DeltaScript identifier. * @return A valid DeltaScript identifier, based on {@code str}. */ public static String identifierize(String str) { final String identifier = str.replaceAll("[^\\w]", "_"); if (identifier.matches("^\\d.*$")) return "_" + identifier; else return identifier; } }