/*
* Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.rjeschke.weel;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicLong;
import com.github.rjeschke.weel.annotations.WeelClass;
import com.github.rjeschke.weel.annotations.WeelRawMethod;
import com.github.rjeschke.weel.annotations.WeelMethod;
import com.github.rjeschke.weel.jclass.WeelImage;
import com.github.rjeschke.weel.jclass.WeelIo;
import com.github.rjeschke.weel.jclass.WeelLock;
import com.github.rjeschke.weel.jclass.WeelReader;
import com.github.rjeschke.weel.jclass.WeelSemaphore;
import com.github.rjeschke.weel.jclass.WeelStack;
import com.github.rjeschke.weel.jclass.WeelStringBuilder;
import com.github.rjeschke.weel.jclass.WeelBlockingQueue;
import com.github.rjeschke.weel.jclass.WeelSyncVar;
import com.github.rjeschke.weel.jclass.WeelThread;
import com.github.rjeschke.weel.jclass.WeelWriter;
/**
* Main Weel class.
* <p>
* Example usage:
* </p>
*
* <pre>
* <code>final Weel weel = new Weel();
* weel.compile("println('Hello world!')");
* weel.runStatic();</code>
* </pre>
*
* @author René Jeschke <rene_jeschke@yahoo.de>
*/
public final class Weel
{
/** Default size of the operand stack. */
public final static int DEFAULT_VALUE_STACK_SIZE = 8192;
/** Default size of the function frame stack. */
public final static int DEFAULT_FRAME_STACK_SIZE = 4096;
/** Default size of the closure function stack. */
public final static int DEFAULT_CLOSURE_STACK_SIZE = 256;
/** Global variables. */
final ArrayList<Value> globals = new ArrayList<Value>();
/** Private variables. */
final ArrayList<Value> privates = new ArrayList<Value>();
/** Function list. */
final ArrayList<WeelFunction> functions = new ArrayList<WeelFunction>();
/** Name to global variable index mapping. */
final HashMap<String, Integer> mapGlobals = new HashMap<String, Integer>();
/** Name to function index mapping. */
final HashMap<String, Integer> mapFunctions = new HashMap<String, Integer>();
/** Name to exact function index mapping. */
final HashMap<String, Integer> mapFunctionsExact = new HashMap<String, Integer>();
/** Weel class loader. */
final WeelLoader classLoader = new WeelLoader();
/** Compiled script classes. */
final ArrayList<String> scriptClasses = new ArrayList<String>();
/** Type bound support functions. */
final TypeFunctions[] typeFunctions = new TypeFunctions[6];
/** Counter for wrapper classes. */
final static AtomicLong wrapperCounter = new AtomicLong();
/** Counter for script classes. */
final static AtomicLong scriptCounter = new AtomicLong();
/** Debug mode flag. */
boolean debugMode = false;
/** Debug mode flag. */
boolean dumpCode = false;
/** Default size of the operand stack. */
final int valueStackSize;
/** Default size of the function frame stack. */
final int frameStackSize;
/** Default size of the closure function stack. */
final int closureStackSize;
private final static Class<?>[] STDLIB =
{ WeelLibMath.class, WeelLibString.class, WeelLibCon.class,
WeelLibMap.class, WeelLibOop.class, WeelLibSys.class,
WeelUnit.class };
private final static Class<?>[] JCLASSES =
{ WeelStringBuilder.class, WeelImage.class, WeelThread.class,
WeelLock.class, WeelSemaphore.class, WeelBlockingQueue.class,
WeelSyncVar.class, WeelIo.class, WeelReader.class, WeelWriter.class,
WeelStack.class};
/** ThreadLocal variable for Weel Runtimes associated with this Weel class. */
private final ThreadLocal<WeelRuntime> runtime = new ThreadLocal<WeelRuntime>()
{
/** @see java.lang.ThreadLocal#initialValue() */
@Override
protected WeelRuntime initialValue()
{
return new WeelRuntime(Weel.this);
}
};
/**
* Constructor.
*/
public Weel()
{
this(DEFAULT_VALUE_STACK_SIZE, DEFAULT_FRAME_STACK_SIZE,
DEFAULT_CLOSURE_STACK_SIZE);
}
/**
* Constructor.
*
* @param stackSize
* Size of the operand stack in slots.
* @param fStackSize
* Size of the frame stack in slots.
* @param cStackSize
* Size of the closure function stack in slots.
*/
public Weel(final int stackSize, final int fStackSize, final int cStackSize)
{
this.valueStackSize = stackSize;
this.frameStackSize = fStackSize;
this.closureStackSize = cStackSize;
// Import standard library
for(final Class<?> c : STDLIB)
{
this.importFunctions(c);
}
// Import classes
for(final Class<?> c : JCLASSES)
{
this.importFunctions(c);
}
// Initialize type functions
for(int i = 0; i < this.typeFunctions.length; i++)
{
this.typeFunctions[i] = new TypeFunctions();
}
}
/**
* Enables or disables debug mode. When set to <code>true</code> asserts get
* compiled, otherwise they get skipped.
*
* @param enable
* On or off?
*/
public void setDebugMode(final boolean enable)
{
this.debugMode = enable;
}
/**
* Enables or disables dumping of generated intermediate byte code.
*
* @param enable
* On or off?
*/
public void enableCodeDump(final boolean enable)
{
this.dumpCode = enable;
}
/**
* Invokes the Weel function with the given name and arguments.
*
* @param functionName
* The function name.
* @param args
* The arguments.
* @return A Value if the functions returns a value, <code>null</code>
* otherwise.
* @throws WeelException
* if the function does not exist.
*/
public Value invoke(final String functionName, Object... args)
{
final WeelFunction function = this.findFunction(functionName,
args.length);
if(function == null)
{
throw new WeelException("Unknown function '" + functionName + "'("
+ args.length + ")");
}
return this.getRuntime().invoke(function, args);
}
/**
* Invokes the Weel function with the given name and arguments.
*
* @param function
* The function.
* @param args
* The arguments.
* @return A Value if the functions returns a value, <code>null</code>
* otherwise.
* @throws WeelException
* if the function argument count does not match the supplied
* argument count.
*/
public Value invoke(final WeelFunction function, Object... args)
{
return this.getRuntime().invoke(function, args);
}
/**
* Compiles the given input String.
*
* @param input
* The input String.
*/
public void compile(final String input)
{
final Compiler compiler = new Compiler(this);
compiler.compile(input, null);
}
/**
* Compiles the given input String.
*
* @param input
* The input String.
* @param filename
* The filename used in error messages.
*/
public void compile(final String input, final String filename)
{
final Compiler compiler = new Compiler(this);
compiler.compile(input, filename);
}
/**
* Compiles the given input stream.
*
* @param input
* The input stream.
*/
public void compile(final InputStream input)
{
final Compiler compiler = new Compiler(this);
compiler.compile(input, null);
}
/**
* Compiles the given input stream.
*
* @param input
* The input stream.
* @param filename
* The filename used in error messages.
*/
public void compile(final InputStream input, final String filename)
{
final Compiler compiler = new Compiler(this);
compiler.compile(input, filename);
}
/**
* Compiles a file given as a Java resource.
*
* @param resource
* The resource (e.g. <code>my.scripts.Script</code>)
*/
public void compileResource(final String resource)
{
final Compiler compiler = new Compiler(this);
final InputStream in = Weel.class.getResourceAsStream("/"
+ resource.replace('.', '/') + ".weel");
compiler.compile(in, resource);
try
{
in.close();
}
catch(IOException e)
{
throw new WeelException(e);
}
}
/**
* Compiles the given input String (which must be an anonymous function).
*
* @param input
* The input String.
* @return The compiled function.
*/
public WeelFunction compileFunction(final String input)
{
final Compiler compiler = new Compiler(this);
return compiler.compileFunction(input);
}
/**
* Runs the static part of all compiled scripts.
*/
public void runStatic()
{
final WeelRuntime runtime = this.getRuntime();
final String[] classes = this.scriptClasses
.toArray(new String[this.scriptClasses.size()]);
for(final String name : classes)
{
try
{
final Class<?> clazz = this.classLoader.findClass(name);
clazz.getMethod("STATIC", WeelRuntime.class).invoke(null,
runtime);
}
catch(Exception e)
{
if(e instanceof WeelException)
throw (WeelException) e;
if(e.getCause() != null
&& e.getCause() instanceof WeelException)
throw (WeelException) e.getCause();
throw new WeelException(e);
}
}
}
/**
* Runs the main function/sub (if exists).
*
* @return The return value of 'main' or null.
*/
public Value runMain()
{
return this.runMain((String[]) null);
}
/**
* Runs the main function/sub (if exists).
*
* @param args
* Program arguments.
* @return The return value of 'main' or null.
*/
public Value runMain(final String... args)
{
final WeelFunction main = this.findFunction("main");
final ValueMap vargs = new ValueMap();
if(args != null)
{
for(final String s : args)
{
vargs.append(new Value(s));
}
}
if(main != null)
{
if(main.arguments > 1)
{
throw new WeelException("main() should only take one argument");
}
try
{
return main.arguments == 1 ? this.invoke(main, vargs) : this
.invoke(main);
}
catch(Exception e)
{
if(e instanceof WeelException)
throw (WeelException) e;
if(e.getCause() != null
&& e.getCause() instanceof WeelException)
throw (WeelException) e.getCause();
throw new WeelException(e);
}
}
return null;
}
/**
* Gets a Runtime object local to the current Thread.
*
* @return A Runtime.
* @see com.github.rjeschke.weel.WeelRuntime
* @see java.lang.ThreadLocal
*/
public WeelRuntime getRuntime()
{
return this.runtime.get();
}
/**
* Gets a temporary Runtime object for optimization purposes.
*
* @return A Runtime.
* @see com.github.rjeschke.weel.WeelRuntime
*/
WeelRuntime getTempRuntime()
{
return new WeelRuntime(this);
}
/**
* Gets a global variable.
*
* @param name
* The name.
* @return The Value.
* @throws WeelException
* If no global variable with the given name exists.
*/
public Value getGlobal(final String name)
{
final Integer index = this.mapGlobals.get(name.toLowerCase());
if(index == null)
throw new WeelException("Unknown global variable '" + name + "'");
return this.globals.get(index).clone();
}
/**
* Sets a global variable.
*
* @param name
* The name.
* @param value
* The value.
* @throws WeelException
* If no global variable with the given name exists.
*/
public void setGlobal(final String name, final Value value)
{
final Integer index = this.mapGlobals.get(name.toLowerCase());
if(index == null)
throw new WeelException("Unknown global variable '" + name + "'");
value.copyTo(this.globals.get(index));
}
/**
* Creates a new global variable.
*
* @param name
* The name.
* @throws WeelException
* If a global variable with the given name already exists.
*/
public void createGlobal(final String name)
{
if(this.mapGlobals.containsKey(name.toLowerCase()))
throw new WeelException("Duplicate global variable '" + name + "'");
this.mapGlobals.put(name.toLowerCase(), this.globals.size());
this.globals.add(new Value());
}
/**
* Adds a global variable to this Weel.
*
* @param name
* The name.
* @return The index.
*/
int addGlobal(final String name)
{
final int index = this.globals.size();
this.mapGlobals.put(name.toLowerCase(), index);
this.globals.add(new Value());
return index;
}
/**
* Checks if a global variable with the given name exists.
*
* @param name
* The name.
* @return <code>true</code> if the global variable exists.
*/
public boolean hasGlobal(final String name)
{
return this.mapGlobals.containsKey(name.toLowerCase());
}
/**
* Finds the given function.
*
* @param name
* The function's name.
* @return The WeelFunction or <code>null</code> if none was found.
*/
public WeelFunction findFunction(final String name)
{
final Integer index = this.mapFunctions.get(name.toLowerCase());
return index != null ? this.functions.get(index) : null;
}
/**
* Finds the given function.
*
* @param name
* The function's name.
* @param args
* The number of arguments.
* @return The WeelFunction or <code>null</code> if none was found.
*/
public WeelFunction findFunction(final String name, final int args)
{
final Integer index = this.mapFunctionsExact.get(name.toLowerCase()
+ "#" + args);
return index != null ? this.functions.get(index) : null;
}
/**
* Adds the given function to this Weel's function list.
*
* @param iname
* The internal name.
* @param func
* The function.
*/
void addFunction(final String iname, final WeelFunction func)
{
this.addFunction(iname, func, true);
}
/**
* Adds the given function to this Weel's function list.
*
* @param iname
* The internal name.
* @param func
* The function.
* @param addToHash
* Add it to the function name hash map?
* @return The index.
*/
int addFunction(final String iname, final WeelFunction func,
final boolean addToHash)
{
final int index = this.functions.size();
func.index = index;
this.functions.add(func);
if(addToHash)
{
this.mapFunctions.put(func.name, index);
this.mapFunctionsExact.put(iname, index);
}
return index;
}
/**
* Imports a class with static Weel functions.
*
* @param clazz
* The class to import.
*/
public void importFunctions(Class<?> clazz)
{
final Method[] methods = clazz.getDeclaredMethods();
final WeelClass wclass = clazz.getAnnotation(WeelClass.class);
ValueMap map;
final String prefix;
MethodWrapper mw = null;
if(wclass != null)
{
map = new ValueMap();
final String clazzName = wclass.name().length() > 0 ? wclass.name()
.toLowerCase() : clazz.getSimpleName().toLowerCase();
prefix = clazzName + (wclass.usesOop() ? "$$" : "$");
if(wclass.isPrivate() && clazzName.indexOf('.') == -1)
{
//
try
{
final Field me = clazz.getDeclaredField("ME");
me.set(null, map);
}
catch(Exception e)
{
throw new WeelException("Can't access 'ME'", e);
}
}
else
{
if(clazzName.indexOf('.') != -1)
{
final String[] toks = clazzName.split("[.]");
final int g = this.hasGlobal(toks[0]) ? this.mapGlobals
.get(toks[0]) : this.addGlobal(toks[0]);
ValueMap cur;
if(this.globals.get(g).type != ValueType.MAP)
{
this.globals.set(g, new Value(cur = new ValueMap()));
}
else
{
cur = this.globals.get(g).getMap();
}
for(int i = 1; i < toks.length; i++)
{
ValueMap next;
if(!cur.hasKey(toks[i]))
{
cur.set(toks[i], new Value(next = new ValueMap()));
}
else
{
next = cur.get(toks[i]).getMap();
}
cur = next;
}
map = cur;
}
else
{
if(this.hasGlobal(clazzName))
{
throw new WeelException(
"Duplicate global variable for Weel clazz: "
+ clazzName);
}
final int g = this.addGlobal(clazzName);
this.globals.set(g, new Value(map));
}
}
}
else
{
map = null;
prefix = "";
}
for(int i = 0; i < methods.length; i++)
{
final Method m = methods[i];
final WeelRawMethod raw = m.getAnnotation(WeelRawMethod.class);
final WeelMethod nice = m.getAnnotation(WeelMethod.class);
if((m.getModifiers() & Modifier.STATIC) == 0)
{
throw new WeelException("Weel only supports static functions: "
+ m);
}
final WeelFunction func = new WeelFunction();
if(raw != null)
{
if(m.getParameterTypes().length != 1
|| m.getReturnType() != void.class
|| m.getParameterTypes()[0] != WeelRuntime.class)
throw new WeelException("Illegal raw Weel function: "
+ m.toGenericString());
final String fname = (raw.name().length() > 0 ? raw.name() : m
.getName()).toLowerCase();
func.name = prefix + fname;
func.arguments = raw.args();
func.returnsValue = raw.returnsValue();
func.clazz = clazz.getCanonicalName();
func.javaName = m.getName();
final String iname = func.name + "#" + func.arguments;
if(this.mapFunctionsExact.containsKey(iname))
throw new WeelException("Duplicate function: " + func.name
+ "(" + func.arguments + ") ["
+ m.toGenericString() + "]");
this.addFunction(iname, func);
if(map != null)
{
map.set(fname, new Value(func));
}
}
else if(nice != null)
{
if(mw == null)
{
mw = new MethodWrapper("Wrap$" + clazz.getSimpleName()
+ "$" + wrapperCounter.getAndIncrement());
}
final String fname = (nice.name().length() > 0 ? nice.name()
: m.getName()).toLowerCase();
func.name = prefix + fname;
func.arguments = m.getParameterTypes().length;
func.returnsValue = m.getReturnType() != void.class;
func.clazz = mw.getClassName();
func.javaName = mw.wrap(m, func);
final String iname = func.name + "#" + func.arguments;
if(this.mapFunctionsExact.containsKey(iname))
throw new WeelException("Duplicate function: " + func.name
+ "(" + func.arguments + ") ["
+ m.toGenericString() + "]");
this.addFunction(iname, func);
if(map != null)
{
map.set(fname, new Value(func));
}
}
}
if(mw != null && mw.classWriter.hasMethods())
{
this.classLoader.addClass(mw.classWriter);
}
}
/**
* Initializes all invokers.
*/
void initAllInvokers()
{
for(final WeelFunction func : this.functions)
{
if(func.invoker == null)
func.invoker = WeelInvokerFactory.create();
func.initialize(this);
}
}
/**
* Registers a private variable.
*
* @return The index.
*/
int registerPrivate()
{
final int i = this.privates.size();
this.privates.add(new Value());
return i;
}
}