/*
* 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.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.LinkedList;
import com.github.rjeschke.weel.Value;
import com.github.rjeschke.weel.Weel;
import com.github.rjeschke.weel.WeelException;
import com.github.rjeschke.weel.WeelFunction;
import com.github.rjeschke.weel.Variable.Type;
/**
* Weel compiler.
*
* @author René Jeschke <rene_jeschke@yahoo.de>
*/
final class Compiler
{
/** The current tokenizer. */
private Tokenizer tokenizer;
/** The Weel. */
private final Weel weel;
/** Nested scopes. */
private final LinkedList<Scope> scopes = new LinkedList<Scope>();
/** The current CodeBlock. */
private WeelCode block;
/** The current Scope. */
private Scope scope;
/** The class writer. */
private JvmClassWriter classWriter;
/** Flag indicating that we're used. */
private boolean used = false;
/** Flag indicating that we're doing runtime compilation. */
private boolean runtimeCompile = false;
/** Last compiled function (for runtime compilation). */
private WeelFunction lastFunction = null;
/** Anonymous function counter. */
private int anonCounter = 0;
/** Name to exact (private) function index mapping. */
private final HashMap<String, Integer> mapFunctionsExact = new HashMap<String, Integer>();
/** Name to (private) function index mapping. */
private final HashMap<String, Integer> mapFunctions = new HashMap<String, Integer>();
/**
* Constructor.
*
* @param weel
* The Weel.
*/
public Compiler(final Weel weel)
{
this.weel = weel;
}
/**
* Compiles the given input String.
*
* @param input
* The input String.
*/
public void compile(final String input, final String filename)
{
this.tokenizer = new Tokenizer(new StringReader(input), filename);
this.compile();
}
/**
* Compiles the given input stream.
*
* @param input
* The input stream.
*/
public void compile(final InputStream input, final String filename)
{
try
{
this.tokenizer = new Tokenizer(new BufferedReader(
new InputStreamReader(input, "UTF-8")), filename);
}
catch (UnsupportedEncodingException e)
{
throw new WeelException(e);
}
this.compile();
}
/**
* Main compile method.
*/
private void compile()
{
if (this.used)
{
throw new WeelException("Trying to reuse a compiler instance.");
}
this.initialize();
this.tokenizer.next();
while (this.tokenizer.token != Token.EOF)
this.compileToken();
if (this.scope.type != ScopeType.STATIC)
{
throw new WeelException("Open block: " + this.scope.type.toString());
}
this.scope.block.closeBlock(this.weel.debugMode, this.weel.dumpCode);
this.blockToBytecode(this.block);
this.weel.classLoader.addClass(this.classWriter);
this.weel.initAllInvokers();
this.used = true;
}
/**
* Compile method for runtime compilation.
*/
WeelFunction compileFunction(final String input)
{
this.tokenizer = new Tokenizer(new StringReader(input), null);
if (this.used)
{
throw new WeelException("Trying to reuse a compiler instance.");
}
this.runtimeCompile = true;
this.initialize();
this.tokenizer.next();
switch (this.tokenizer.token)
{
case ANON_OPEN:
this.openFunction(true, true, false);
break;
case RESERVED:
switch (this.tokenizer.reserved)
{
case FUNC:
case SUB:
this.openFunction(true, false, false);
break;
default:
this.syntaxError();
break;
}
break;
default:
this.syntaxError();
break;
}
while (this.tokenizer.token != Token.EOF)
this.compileToken();
if (this.scope.type != ScopeType.STATIC)
{
throw new WeelException("Open block: " + this.scope.type.toString());
}
if (this.lastFunction == null)
{
this.syntaxError();
}
this.scope.block.closeBlock(this.weel.debugMode, this.weel.dumpCode);
this.blockToBytecode(this.block);
final WeelLoader loader = this.lastFunction.loader = new WeelLoader(
this.weel.classLoader);
loader.addClass(this.classWriter);
this.lastFunction.invoker = WeelInvokerFactory.create();
this.lastFunction.invoker.initialize(this.weel, this.lastFunction);
this.used = true;
return this.lastFunction;
}
/**
* Initializes this compiler.
*/
private void initialize()
{
this.classWriter = new JvmClassWriter(
"com.github.rjeschke.weel.scripts.Script"
+ Weel.scriptCounter.getAndIncrement());
this.weel.scriptClasses.add(this.classWriter.className);
final Scope s = new Scope(this.weel, ScopeType.STATIC, this);
s.block = new WeelCode(this.weel);
this.addScope(s);
}
/**
* Adds a scope.
*
* @param s
* The scope.
*/
private void addScope(final Scope s)
{
this.scopes.add(s);
this.scope = s;
this.block = s.block;
}
/**
* Removes a scope.
*/
private void removeScope()
{
this.scopes.removeLast().unregisterLocals();
final Scope s = this.scopes.isEmpty() ? null : this.scopes.getLast();
this.scope = s;
this.block = s != null ? s.block : null;
}
/**
* Opens a functions with checks.
*
* @param anonymous
* Is it anonymous?
* @param isAlternate
* Is it alternate syntax?
* @param isPrivate
* Is it private?
*/
private void doOpenFunction(final boolean anonymous,
final boolean isAlternate, final boolean isPrivate)
{
if (this.scope.findFunctionScope() != null)
{
throw new WeelException(this.tokenizer
.error("Can't declare a sub/func inside another sub/func"));
}
if (this.runtimeCompile)
{
throw new WeelException(
this.tokenizer
.error("Can't declare a global or private sub/func during runtime compilation."));
}
this.openFunction(anonymous, isAlternate, isPrivate);
}
/**
* Compiles a token.
*/
private void compileToken()
{
switch (this.tokenizer.token)
{
case NAME:
this.parseVarsAndFuncs(false);
this.skipSemi();
break;
case CURLY_BRACE_OPEN:
case STRING:
case BRACE_OPEN:
case NUMBER:
this.tryStaticSupport();
this.skipSemi();
break;
case CURLY_BRACE_CLOSE:
{
if ((this.scope.type == ScopeType.FUNC || this.scope.type == ScopeType.SUB)
&& this.block.isAlternateSyntax)
{
this.closeFunction();
}
else
this.syntaxError();
break;
}
case ANON_OPEN:
this.doOpenFunction(false, true, false);
break;
case RESERVED:
switch (this.tokenizer.reserved)
{
case NULL:
case TRUE:
case FALSE:
this.tryStaticSupport();
this.skipSemi();
break;
case THIS:
this.parseVarsAndFuncs(false);
this.skipSemi();
break;
case LOCAL:
this.parseLocal();
this.skipSemi();
break;
case PRIVATE:
this.tokenizer.next();
if (this.tokenizer.token == Token.ANON_OPEN)
{
this.doOpenFunction(false, true, true);
}
else if (this.tokenizer.token == Token.RESERVED
&& (this.tokenizer.reserved == ReservedWord.SUB || this.tokenizer.reserved == ReservedWord.FUNC))
{
this.doOpenFunction(false, false, true);
}
else
{
this.parsePrivate();
this.skipSemi();
}
break;
case OUTER:
this.parseOuter();
this.skipSemi();
break;
case GLOBAL:
this.parseGlobal();
this.skipSemi();
break;
case IF:
this.openIf();
break;
case ELSEIF:
this.addElseIf();
break;
case ELSE:
this.addElse();
break;
case FOR:
this.openFor();
break;
case FOREACH:
this.openForEach();
break;
case BREAK:
this.addBreak();
this.skipSemi();
break;
case CONTINUE:
this.addContinue();
this.skipSemi();
break;
case DO:
this.openDo();
break;
case UNTIL:
this.closeDoUntil();
this.skipSemi();
break;
case WHILE:
this.openWhile();
this.skipSemi();
break;
case SWITCH:
this.openSwitch();
break;
case CASE:
this.addCase();
break;
case DEFAULT:
this.addDefault();
break;
case FUNC:
case SUB:
this.doOpenFunction(false, false, false);
break;
case EXIT:
this.addExit();
this.skipSemi();
break;
case RETURN:
this.addReturn();
this.skipSemi();
break;
case END:
this.closeScope();
break;
default:
this.syntaxError();
break;
}
break;
case EOF:
break;
default:
this.syntaxError();
break;
}
}
/**
* Checks if the expression is a static support function call, e.g.
* <code>"Hello world!"::length()</code>
*/
private void tryStaticSupport()
{
this.parseExpression(false);
if (this.tokenizer.token == Token.DOUBLE_COLON)
{
this.parseVarsAndFuncs(false);
}
else
{
this.syntaxError();
}
}
/**
* Checks if the current expression continues with a token that indicates
* that it needs a return value. Only used in variables and function parsing
* for non-first expressions.
*
* @return <code>true</code> if so.
*/
private boolean wouldNeedReturnValue()
{
switch (this.tokenizer.token)
{
case BRACE_OPEN:
case BRACKET_OPEN:
case DOT:
case ARROW:
case DOUBLE_COLON:
// as this is only used for non-first expressions
// we will need a return value for all kinds of
// assigns
case ASSIGN:
case ASSIGN_ADD:
case ASSIGN_DIV:
case ASSIGN_AND:
case ASSIGN_MODULO:
case ASSIGN_MUL:
case ASSIGN_OR:
case ASSIGN_SUB:
case ASSIGN_XOR:
case ASSIGN_STRCAT:
case ASSIGN_MAPCAT:
case ASSIGN_SHL:
case ASSIGN_SHR:
case ASSIGN_USHR:
return true;
default:
return false;
}
}
/**
* Parses an assign or a function call
*
* @param getContext
* Flag to indicate that we need a return value.
*/
private void parseVarsAndFuncs(final boolean getContext)
{
boolean first = true, stackCall = false, end = false, oop = false, append = false;
Variable var;
if (this.tokenizer.token == Token.RESERVED
&& this.tokenizer.reserved == ReservedWord.THIS)
{
final Scope s = this.scope.findFunctionScope();
if (s == null || !s.isOop)
{
throw new WeelException(this.tokenizer
.error("Illegal use of 'this'"));
}
var = new Variable();
var.type = Type.LOCAL;
var.index = 0;
}
else if (this.tokenizer.token == Token.DOUBLE_COLON)
{
var = new Variable();
}
else
{
var = this.scope
.findVariable(new Variable(), this.tokenizer.string);
}
ExpressionType expr = var.type != Type.NONE ? ExpressionType.VARIABLE
: ExpressionType.NONE;
if (this.tokenizer.token != Token.DOUBLE_COLON)
this.tokenizer.next();
while (!end)
{
switch (this.tokenizer.token)
{
case DOUBLE_COLON:
{
int paramc = 0;
this.tokenizer.next();
this.checkToken(Token.NAME);
final String name = this.tokenizer.string;
if (expr == ExpressionType.ARRAY)
{
this.block.add(new InstrGetMap());
}
else if (first && expr == ExpressionType.VARIABLE)
{
this.needGetVariable(var);
this.writeGetVariable(var);
}
else if (first && var.function != null)
{
this.writeGetVariable(var);
}
// FIXME ... is there something missing here?
this.tokenizer.next();
this.checkToken(Token.BRACE_OPEN);
this.tokenizer.next();
while (this.tokenizer.token != Token.BRACE_CLOSE)
{
paramc++;
this.parseExpression();
if (this.tokenizer.token == Token.BRACE_CLOSE)
{
break;
}
if (this.tokenizer.token == Token.COMMA)
{
this.tokenizer.next();
continue;
}
this.syntaxError();
}
this.checkToken(Token.BRACE_CLOSE);
this.tokenizer.next();
this.block.add(new InstrSpecialCall(name, paramc, getContext
|| this.wouldNeedReturnValue()));
oop = first = false;
expr = ExpressionType.FUNCTION;
break;
}
case BRACE_OPEN:
{
int paramc = 0;
final boolean isAssert;
this.tokenizer.next();
if (first)
{
isAssert = var.name.equals("assert");
if (!isAssert && !var.isFunction())
{
this.needGetVariable(var, true);
this.writeGetVariable(var);
stackCall = true;
}
}
else
{
if (expr == ExpressionType.ARRAY)
{
if (oop)
{
this.block.add(new InstrGetMapOop());
paramc++;
}
else
{
this.block.add(new InstrGetMap());
}
stackCall = true;
}
isAssert = false;
}
if (isAssert)
{
this.parseAssert();
return;
}
while (this.tokenizer.token != Token.BRACE_CLOSE)
{
paramc++;
this.parseExpression();
if (this.tokenizer.token == Token.BRACE_CLOSE)
{
break;
}
if (this.tokenizer.token == Token.COMMA)
{
this.tokenizer.next();
continue;
}
this.syntaxError();
}
this.checkToken(Token.BRACE_CLOSE);
this.tokenizer.next();
if (first && !stackCall)
{
WeelFunction func = this.findFunction(var.name, paramc);
if (func == null)
{
throw new WeelException(this.tokenizer
.error("Unknown function/sub " + var.name + "("
+ paramc + ")"));
}
this.block.add(new InstrCall(func));
if (getContext || this.wouldNeedReturnValue())
{
if (!func.returnsValue)
{
throw new WeelException(this.tokenizer.error("Sub "
+ func.getName() + "("
+ func.getNumArguments()
+ ") doesn't return a value"));
}
}
else
{
if (func.returnsValue)
{
this.block.add(new InstrPop(1));
}
}
}
else
{
this.block.add(new InstrStackCall(paramc, getContext
| this.wouldNeedReturnValue()));
}
oop = first = false;
expr = ExpressionType.FUNCTION;
break;
}
case ARROW:
if (first)
{
this.needGetVariable(var);
this.writeGetVariable(var);
}
else
{
if (expr == ExpressionType.ARRAY)
this.block.add(new InstrGetMap());
}
this.tokenizer.next();
if (this.tokenizer.token == Token.BRACKET_OPEN)
{
this.tokenizer.next();
this.parseExpression();
this.checkToken(Token.BRACKET_CLOSE);
}
else
{
this.checkToken(Token.NAME);
this.block.add(new InstrLoad(this.tokenizer.string));
}
this.block.add(new InstrKey());
this.tokenizer.next();
oop = true;
first = false;
expr = ExpressionType.ARRAY;
break;
case DOT:
if (first)
{
this.needGetVariable(var);
this.writeGetVariable(var);
}
else
{
if (expr == ExpressionType.ARRAY)
this.block.add(new InstrGetMap());
}
this.tokenizer.next();
this.checkToken(Token.NAME);
this.block.add(new InstrLoad(this.tokenizer.string));
this.block.add(new InstrKey());
this.tokenizer.next();
oop = first = false;
expr = ExpressionType.ARRAY;
break;
case BRACKET_OPEN:
if (first)
{
this.needGetVariable(var);
this.writeGetVariable(var);
}
else
{
if (expr == ExpressionType.ARRAY)
this.block.add(new InstrGetMap());
}
this.tokenizer.next();
if (!getContext && this.tokenizer.token == Token.BRACKET_CLOSE)
{
this.tokenizer.next();
if (this.tokenizer.token != Token.ASSIGN)
this.syntaxError();
append = true;
}
else
{
this.parseExpression();
this.block.add(new InstrKey());
this.checkToken(Token.BRACKET_CLOSE);
this.tokenizer.next();
}
oop = first = false;
expr = ExpressionType.ARRAY;
break;
case ASSIGN_ADD:
case ASSIGN_DIV:
case ASSIGN_AND:
case ASSIGN_MODULO:
case ASSIGN_MUL:
case ASSIGN_OR:
case ASSIGN_SUB:
case ASSIGN_XOR:
case ASSIGN_STRCAT:
case ASSIGN_MAPCAT:
case ASSIGN_SHL:
case ASSIGN_SHR:
case ASSIGN_USHR:
if (first)
{
this.needGetVariable(var);
expr = ExpressionType.VARIABLE;
}
end = true;
break;
case ASSIGN:
if (first)
{
this.needSetVariable(var);
expr = ExpressionType.VARIABLE;
}
end = true;
break;
default:
if (!getContext && first && var.type == Type.NONE)
{
this.syntaxError();
}
else if (getContext && first)
{
if (!var.isFunction())
this.needGetVariable(var);
expr = ExpressionType.VARIABLE;
}
end = true;
break;
}
}
switch (this.tokenizer.token)
{
case ASSIGN_ADD:
case ASSIGN_DIV:
case ASSIGN_AND:
case ASSIGN_MODULO:
case ASSIGN_MUL:
case ASSIGN_OR:
case ASSIGN_SUB:
case ASSIGN_XOR:
case ASSIGN_STRCAT:
case ASSIGN_MAPCAT:
case ASSIGN_SHL:
case ASSIGN_SHR:
case ASSIGN_USHR:
{
final Token op = this.tokenizer.token;
if (first && expr != ExpressionType.ARRAY && var.type == Type.NONE)
this.syntaxError();
if (expr == ExpressionType.ARRAY)
{
this.block.add(new InstrSdup2());
this.block.add(new InstrGetMap());
}
else
{
this.writeGetVariable(var);
}
this.tokenizer.next();
this.parseExpression();
switch (op)
{
case ASSIGN_ADD:
this.block.add(new InstrAlu2(Alu2InstrType.add));
break;
case ASSIGN_DIV:
this.block.add(new InstrAlu2(Alu2InstrType.div));
break;
case ASSIGN_AND:
this.block.add(new InstrAlu2(Alu2InstrType.and));
break;
case ASSIGN_MODULO:
this.block.add(new InstrAlu2(Alu2InstrType.mod));
break;
case ASSIGN_MUL:
this.block.add(new InstrAlu2(Alu2InstrType.mul));
break;
case ASSIGN_OR:
this.block.add(new InstrAlu2(Alu2InstrType.or));
break;
case ASSIGN_SUB:
this.block.add(new InstrAlu2(Alu2InstrType.sub));
break;
case ASSIGN_XOR:
this.block.add(new InstrAlu2(Alu2InstrType.xor));
break;
case ASSIGN_STRCAT:
this.block.add(new InstrAlu2(Alu2InstrType.strcat));
break;
case ASSIGN_MAPCAT:
this.block.add(new InstrAlu2(Alu2InstrType.mapcat2));
break;
case ASSIGN_SHL:
this.block.add(new InstrAlu2(Alu2InstrType.shl));
break;
case ASSIGN_SHR:
this.block.add(new InstrAlu2(Alu2InstrType.shr));
break;
case ASSIGN_USHR:
this.block.add(new InstrAlu2(Alu2InstrType.ushr));
break;
default:
break;
}
if (expr == ExpressionType.ARRAY)
{
if (getContext)
this.block.add(new InstrSdups());
this.block.add(new InstrSetMap());
}
else
{
if (getContext)
this.block.add(new InstrSdup());
this.writeSetVariable(var);
}
break;
}
case ASSIGN:
this.tokenizer.next();
this.parseExpression();
if (expr == ExpressionType.ARRAY)
{
if (getContext)
this.block.add(new InstrSdups());
if (append)
this.block.add(new InstrAppendMap());
else
this.block.add(new InstrSetMap());
}
else if (expr == ExpressionType.VARIABLE)
{
if (getContext)
this.block.add(new InstrSdup());
this.writeSetVariable(var);
}
else
{
this.syntaxError();
}
break;
default:
if (first && getContext
&& (var.type != Type.NONE || var.function != null))
{
this.writeGetVariable(var);
}
else if (getContext && expr == ExpressionType.ARRAY)
{
this.block.add(new InstrGetMap());
}
else if (expr != ExpressionType.FUNCTION)
{
if (getContext && first)
{
throw new WeelException(this.tokenizer.error(
"Unknown variable '%s'", var.name));
}
this.syntaxError();
}
break;
}
}
/**
* Parses an operand.
*/
private void parseOperand()
{
switch (this.tokenizer.token)
{
case NUMBER:
this.block.add(new InstrLoad(this.tokenizer.number));
this.tokenizer.next();
break;
case STRING:
this.block.add(new InstrLoad(this.tokenizer.string));
this.tokenizer.next();
break;
case RESERVED:
switch (this.tokenizer.reserved)
{
case TRUE:
this.block.add(new InstrLoad(-1));
this.tokenizer.next();
break;
case FALSE:
this.block.add(new InstrLoad(0));
this.tokenizer.next();
break;
case NULL:
this.block.add(new InstrLoad());
this.tokenizer.next();
break;
case THIS:
this.parseVarsAndFuncs(true);
break;
default:
this.syntaxError();
break;
}
break;
case NAME:
this.parseVarsAndFuncs(true);
break;
case CURLY_BRACE_OPEN:
{
this.block.add(new InstrCreateMap());
this.tokenizer.next();
while (this.tokenizer.token != Token.CURLY_BRACE_CLOSE)
{
this.block.add(new InstrSdup());
switch (this.tokenizer.token)
{
case DOT:
this.tokenizer.next();
this.checkToken(Token.NAME);
this.block.add(new InstrLoad(this.tokenizer.string));
this.block.add(new InstrKey());
this.tokenizer.next();
this.checkToken(Token.ASSIGN);
this.tokenizer.next();
this.parseExpression();
this.block.add(new InstrSetMap());
break;
case BRACKET_OPEN:
this.tokenizer.next();
this.parseExpression();
this.block.add(new InstrKey());
this.checkToken(Token.BRACKET_CLOSE);
this.tokenizer.next();
this.checkToken(Token.ASSIGN);
this.tokenizer.next();
this.parseExpression();
this.block.add(new InstrSetMap());
break;
case NAME:
{
final Token prev = this.tokenizer.token;
final String name = this.tokenizer.string;
if (this.tokenizer.next() == Token.ASSIGN)
{
this.block.add(new InstrLoad(name));
this.block.add(new InstrKey());
this.tokenizer.next();
this.parseExpression();
this.block.add(new InstrSetMap());
}
else
{
// FIXME do I need this? is this correct?
switch (this.tokenizer.token)
{
case NAME:
case RESERVED:
case NUMBER:
case STRING:
this.syntaxError();
break;
default:
break;
}
this.tokenizer.ungetToken(prev);
this.parseExpression();
this.block.add(new InstrAppendMap());
}
break;
}
default:
this.parseExpression();
this.block.add(new InstrAppendMap());
break;
}
if (this.tokenizer.token == Token.CURLY_BRACE_CLOSE)
break;
if (this.tokenizer.token == Token.COMMA)
{
this.tokenizer.next();
continue;
}
this.syntaxError();
}
this.tokenizer.next();
break;
}
default:
this.syntaxError();
}
}
/**
* Parses an expression.
*/
private void parseExpression()
{
this.parseExpression(true);
}
/**
* Parses an expression.
*
* @param allowDoubleColon
* Flag indicating if we allow the parsing of <code>::</code>
*/
private void parseExpression(final boolean allowDoubleColon)
{
if (this.tokenizer.token == Token.RESERVED
&& (this.tokenizer.reserved == ReservedWord.FUNC || this.tokenizer.reserved == ReservedWord.SUB)
|| this.tokenizer.token == Token.ANON_OPEN)
{
// Parse anonymous function
final Scope start = this.scope;
this.openFunction(true, this.tokenizer.token == Token.ANON_OPEN,
false);
while (this.scope != start && this.tokenizer.token != Token.EOF)
{
this.compileToken();
}
}
else
{
this.parseExpression(-1);
if (this.tokenizer.token == Token.TERNARY)
{
final int first = this.block.registerLabel();
final int second = this.block.registerLabel();
this.block.add(new InstrPopBool());
this.block.add(new InstrIfEq(first));
this.tokenizer.next();
this.parseExpression();
this.checkToken(Token.COLON);
this.tokenizer.next();
this.block.add(new InstrGoto(second));
this.block.add(new InstrLabel(first));
this.parseExpression();
this.block.add(new InstrLabel(second));
}
else if (this.tokenizer.token == Token.DOUBLE_COLON
&& allowDoubleColon)
{
this.parseVarsAndFuncs(true);
}
}
}
/**
* Parses an expression.
*
* @param prio
* The priority.
* @return The last operator token.
*/
private Token parseExpression(final int prio)
{
this.checkExpr();
if (this.tokenizer.isUnary(this.tokenizer.token))
{
switch (this.tokenizer.token)
{
case BRACE_OPEN:
this.tokenizer.next();
this.parseExpression(-1);
this.checkToken(Token.BRACE_CLOSE);
this.tokenizer.next();
break;
case SUB:
this.tokenizer.next();
if (this.tokenizer.token == Token.NUMBER)
{
this.block.add(new InstrLoad(-this.tokenizer.number));
this.tokenizer.next();
}
else
{
this.parseExpression(Tokenizer.UOPR_PRIORITY);
this.block.add(new InstrNeg());
}
break;
default:
{
final Token tok = this.tokenizer.token;
this.tokenizer.next();
this.parseExpression(Tokenizer.UOPR_PRIORITY);
switch (tok)
{
case LOGICAL_NOT:
this.block.add(new InstrLnot());
break;
case BINARY_NOT:
this.block.add(new InstrNot());
break;
default:
break;
}
break;
}
}
}
else
{
this.parseOperand();
}
Token bop = this.tokenizer.token;
while (this.tokenizer.isBinary(bop)
&& this.tokenizer.getBinaryPriority(bop) > prio)
{
this.tokenizer.next();
int taddr = 0;
// Short circuit logical and/or
if (bop == Token.LOGICAL_AND)
{
this.block.add(new InstrTestPopf());
taddr = this.block.registerLabel();
this.block.add(new InstrIfNe(taddr));
}
else if (bop == Token.LOGICAL_OR)
{
this.block.add(new InstrTestPopt());
taddr = this.block.registerLabel();
this.block.add(new InstrIfNe(taddr));
}
final Token nbop = this.parseExpression(this.tokenizer
.getBinaryPriority(bop));
switch (bop)
{
case ADD:
this.block.add(new InstrAlu2(Alu2InstrType.add));
break;
case SUB:
this.block.add(new InstrAlu2(Alu2InstrType.sub));
break;
case MUL:
this.block.add(new InstrAlu2(Alu2InstrType.mul));
break;
case POW:
this.block.add(new InstrAlu2(Alu2InstrType.pow));
break;
case DIV:
this.block.add(new InstrAlu2(Alu2InstrType.div));
break;
case LOGICAL_AND:
case LOGICAL_OR:
this.block.add(new InstrLabel(taddr));
break;
case EQUAL:
this.block.add(new InstrAlu2(Alu2InstrType.cmpEq));
break;
case NOT_EQUAL:
this.block.add(new InstrAlu2(Alu2InstrType.cmpNe));
break;
case GREATER:
this.block.add(new InstrAlu2(Alu2InstrType.cmpGt));
break;
case GREATER_EQUAL:
this.block.add(new InstrAlu2(Alu2InstrType.cmpGe));
break;
case LESS:
this.block.add(new InstrAlu2(Alu2InstrType.cmpLt));
break;
case LESS_EQUAL:
this.block.add(new InstrAlu2(Alu2InstrType.cmpLe));
break;
case STRING_CONCAT:
this.block.add(new InstrAlu2(Alu2InstrType.strcat));
break;
case MAP_CONCAT:
this.block.add(new InstrAlu2(Alu2InstrType.mapcat));
break;
case MODULO:
this.block.add(new InstrAlu2(Alu2InstrType.mod));
break;
case BINARY_AND:
this.block.add(new InstrAlu2(Alu2InstrType.and));
break;
case BINARY_OR:
this.block.add(new InstrAlu2(Alu2InstrType.or));
break;
case BINARY_XOR:
this.block.add(new InstrAlu2(Alu2InstrType.xor));
break;
case SHL:
this.block.add(new InstrAlu2(Alu2InstrType.shl));
break;
case SHR:
this.block.add(new InstrAlu2(Alu2InstrType.shr));
break;
case USHR:
this.block.add(new InstrAlu2(Alu2InstrType.ushr));
break;
default:
break;
}
bop = nbop;
}
return bop;
}
/**
* Closes a scope.
*/
private void closeScope()
{
switch (this.scope.type)
{
case IF:
this.closeIf();
break;
case FOR:
this.closeFor();
break;
case FOREACH:
this.closeForEach();
break;
case DO:
this.closeDo();
break;
case WHILE:
this.closeWhile();
break;
case SUB:
case FUNC:
this.closeFunction();
break;
case SWITCH:
this.closeSwitch();
break;
default:
throw new WeelException(this.tokenizer.error("'end' without block"));
}
}
/**
* Adds a 'break' to a scope.
*/
private void addBreak()
{
final Scope s = this.scope.getBreakScope();
if (s == null)
{
throw new WeelException(this.tokenizer
.error("'break' without suitable scope"));
}
this.block.add(new InstrGoto(s.addBreak()));
this.tokenizer.next();
}
/**
* Adds a 'continue' to a scope.
*/
private void addContinue()
{
final Scope s = this.scope.getContinueScope();
if (s == null)
{
throw new WeelException(this.tokenizer
.error("'continue' without suitable scope"));
}
this.block.add(new InstrGoto(s.addContinue()));
this.tokenizer.next();
}
/**
* Adds a case to a switch.
*/
private void addCase()
{
int cont = 0;
if (this.scope.type != ScopeType.SWITCH)
{
throw new WeelException(this.tokenizer
.error("'switch' without 'case'"));
}
if (this.scope.hasDefault)
{
throw new WeelException(this.tokenizer
.error("'switch' after 'default'"));
}
if (this.scope.hasCase)
{
cont = this.block.registerLabel();
this.block.add(new InstrGoto(cont));
this.block.add(new InstrLabel(this.scope.continueLabel));
this.scope.continueLabel = -1;
}
this.block.add(new InstrSdup());
this.tokenizer.next();
this.parseExpression();
this.checkToken(Token.COLON);
this.tokenizer.next();
this.block.add(new InstrCmpEqual());
this.block.add(new InstrIfEq(this.scope.addContinue()));
if (this.scope.hasCase)
{
this.block.add(new InstrLabel(cont));
}
this.scope.hasCase = true;
}
/**
* Adds a default to a switch.
*/
private void addDefault()
{
if (this.scope.type != ScopeType.SWITCH)
{
throw new WeelException(this.tokenizer
.error("'default' without 'case'"));
}
if (this.scope.hasDefault)
{
throw new WeelException(this.tokenizer.error("Duplicate 'default'"));
}
if (this.scope.hasCase)
{
this.block.add(new InstrLabel(this.scope.continueLabel));
this.scope.continueLabel = -1;
}
this.tokenizer.next();
this.checkToken(Token.COLON);
this.tokenizer.next();
this.scope.hasDefault = true;
}
/**
* Opens a SWITCH scope.
*/
private void openSwitch()
{
this.addScope(new Scope(this.scope, ScopeType.SWITCH));
this.tokenizer.next();
this.parseExpression();
this.checkReserved(ReservedWord.DO);
this.tokenizer.next();
}
/**
* Closes a SWITCH scope.
*/
private void closeSwitch()
{
if (this.scope.continueLabel != -1)
{
this.block.add(new InstrLabel(this.scope.continueLabel));
}
if (this.scope.breakLabel != -1)
{
this.block.add(new InstrLabel(this.scope.breakLabel));
}
this.block.add(new InstrPop(1));
this.removeScope();
this.tokenizer.next();
}
/**
* Opens a FOR scope.
*/
private void openFor()
{
this.addScope(new Scope(this.scope, ScopeType.FOR));
this.tokenizer.next();
this.checkToken(Token.NAME);
final Variable var = this.scope.findVariable(new Variable(),
this.tokenizer.string);
if (var.type != Type.LOCAL)
{
var.index = this.scope.addLocal(var.name);
var.type = Type.LOCAL;
}
this.tokenizer.next();
this.checkToken(Token.ASSIGN);
this.tokenizer.next();
this.parseExpression();
this.block.add(new InstrVarStore(VarInstrType.LOCAL, var.index));
this.checkToken(Token.COMMA);
this.tokenizer.next();
this.parseExpression();
if (this.tokenizer.token == Token.COMMA)
{
this.tokenizer.next();
this.parseExpression();
}
else
{
this.block.add(new InstrLoad(1));
}
this.checkReserved(ReservedWord.DO);
this.tokenizer.next();
this.block.add(new InstrBeginFor(var.index));
this.block.add(new InstrIfEq(this.scope.addBreak()));
this.scope.start = this.block.registerLabel();
this.block.add(new InstrLabel(this.scope.start));
this.scope.localIndex = var.index;
}
/**
* Closes a FOR scope.
*/
private void closeFor()
{
if (this.scope.continueLabel != -1)
{
this.block.add(new InstrLabel(this.scope.continueLabel));
}
this.block.add(new InstrEndFor(this.scope.localIndex));
this.block.add(new InstrIfNe(this.scope.start));
if (this.scope.breakLabel != -1)
{
this.block.add(new InstrLabel(this.scope.breakLabel));
}
this.block.add(new InstrPop(2));
this.removeScope();
this.tokenizer.next();
}
/**
* Opens a FOREACH scope.
*/
private void openForEach()
{
this.addScope(new Scope(this.scope, ScopeType.FOREACH));
this.tokenizer.next();
this.checkToken(Token.NAME);
Variable key = null, val = this.scope.findVariable(new Variable(),
this.tokenizer.string);
this.needSetVariable(val);
this.tokenizer.next();
if (this.tokenizer.token == Token.COMMA)
{
this.tokenizer.next();
this.checkToken(Token.NAME);
key = val;
val = this.scope
.findVariable(new Variable(), this.tokenizer.string);
this.needSetVariable(val);
this.tokenizer.next();
}
this.checkReserved(ReservedWord.IN);
this.tokenizer.next();
if (this.tokenizer.token == Token.RESERVED
&& this.tokenizer.reserved == ReservedWord.THIS)
{
final Scope s = this.scope.findFunctionScope();
if (s == null || !s.isOop)
{
throw new WeelException(this.tokenizer
.error("Illegal use of 'this'"));
}
this.block.add(new InstrVarLoad(VarInstrType.LOCAL, 0));
}
else
{
this.checkToken(Token.NAME);
final Variable map = this.scope.findVariable(new Variable(),
this.tokenizer.string);
this.needGetVariable(map);
this.writeGetVariable(map);
}
this.tokenizer.next();
this.checkReserved(ReservedWord.DO);
this.tokenizer.next();
this.block.add(new InstrPrepareForEach());
this.block.add(new InstrLabel(this.scope.addContinue()));
this.block.add(new InstrDoForEach());
this.block.add(new InstrIfEq(this.scope.addBreak()));
this.writeSetVariable(val);
if (key != null)
this.writeSetVariable(key);
else
this.block.add(new InstrPop(1));
}
/**
* Closes a FOREACH scope.
*/
private void closeForEach()
{
this.block.add(new InstrGoto(this.scope.addContinue()));
if (this.scope.breakLabel != -1)
{
this.block.add(new InstrLabel(this.scope.breakLabel));
}
this.block.add(new InstrPop(1));
this.removeScope();
this.tokenizer.next();
}
/**
* Opens a DO scope.
*/
private void openDo()
{
this.addScope(new Scope(this.scope, ScopeType.DO));
this.tokenizer.next();
this.scope.start = this.block.registerLabel();
this.block.add(new InstrLabel(this.scope.start));
}
/**
* Closes a DO scope.
*/
private void closeDo()
{
if (this.scope.breakLabel != -1)
{
this.block.add(new InstrLabel(this.scope.breakLabel));
}
if (this.scope.continueLabel != -1)
throw new WeelException(this.tokenizer
.error("Misplaced 'continue'"));
this.removeScope();
this.tokenizer.next();
}
/**
* Closes a DO scope.
*/
private void closeDoUntil()
{
if (this.scope.continueLabel != -1)
{
this.block.add(new InstrLabel(this.scope.continueLabel));
}
this.tokenizer.next();
this.parseExpression();
this.block.add(new InstrPopBool());
this.block.add(new InstrIfEq(this.scope.start));
if (this.scope.breakLabel != -1)
{
this.block.add(new InstrLabel(this.scope.breakLabel));
}
this.removeScope();
}
/**
* Opens a WHILE scope.
*/
private void openWhile()
{
this.addScope(new Scope(this.scope, ScopeType.WHILE));
this.tokenizer.next();
this.block.add(new InstrLabel(this.scope.addContinue()));
this.parseExpression();
this.checkReserved(ReservedWord.DO);
this.tokenizer.next();
this.block.add(new InstrPopBool());
this.block.add(new InstrIfEq(this.scope.addBreak()));
}
/**
* Closes a WHILE scope.
*/
private void closeWhile()
{
this.block.add(new InstrGoto(this.scope.continueLabel));
this.block.add(new InstrLabel(this.scope.breakLabel));
this.removeScope();
this.tokenizer.next();
}
/**
* Opens an IF scope.
*/
private void openIf()
{
this.tokenizer.next();
this.parseExpression();
this.checkReserved(ReservedWord.THEN);
this.tokenizer.next();
this.addScope(new Scope(this.scope, ScopeType.IF));
this.block.add(new InstrPopBool());
this.block.add(new InstrIfEq(this.scope.addContinue()));
}
/**
* Adds an 'ELSEIF' to an IF scope.
*/
private void addElseIf()
{
if (this.scope == null || this.scope.type != ScopeType.IF)
throw new WeelException(this.tokenizer
.error("'elseif' without 'if'"));
if (this.scope.hasElse)
throw new WeelException(this.tokenizer
.error("'elseif' after 'else'"));
this.block.add(new InstrGoto(this.scope.addBreak()));
this.block.add(new InstrLabel(this.scope.continueLabel));
this.scope.continueLabel = -1;
this.tokenizer.next();
this.parseExpression();
this.checkReserved(ReservedWord.THEN);
this.tokenizer.next();
this.block.add(new InstrPopBool());
this.block.add(new InstrIfEq(this.scope.addContinue()));
}
/**
* Adds an 'ELSE' to an IF scope.
*/
private void addElse()
{
if (this.scope == null || this.scope.type != ScopeType.IF)
throw new WeelException(this.tokenizer.error("'else' without 'if'"));
if (this.scope.hasElse)
throw new WeelException(this.tokenizer.error("Duplicate 'else'"));
this.tokenizer.next();
this.scope.hasElse = true;
this.block.add(new InstrGoto(this.scope.addBreak()));
this.block.add(new InstrLabel(this.scope.continueLabel));
this.scope.continueLabel = -1;
}
/**
* Closes an IF scope.
*/
private void closeIf()
{
if (this.scope.continueLabel != -1)
{
this.block.add(new InstrLabel(this.scope.continueLabel));
}
if (this.scope.breakLabel != -1)
{
this.block.add(new InstrLabel(this.scope.breakLabel));
}
this.tokenizer.next();
this.removeScope();
}
/**
* Opens a function.
*
* @param anonymous
* Are we anonymous?
*/
private void openFunction(final boolean anonymous, final boolean alternate,
final boolean isPrivate)
{
if (anonymous)
this.addScope(new Scope(this.scope, ScopeType.BORDER));
this.addScope(new Scope(this.scope,
this.tokenizer.reserved == ReservedWord.SUB ? ScopeType.SUB
: ScopeType.FUNC));
this.block = this.scope.block = new WeelCode(this.weel);
this.block.source = this.tokenizer.error("");
this.block.isAnonymousFunction = anonymous;
final WeelFunction func = this.block.function = new WeelFunction();
func.returnsValue = this.tokenizer.token == Token.RESERVED
&& this.tokenizer.reserved == ReservedWord.FUNC;
this.tokenizer.next();
if (!anonymous)
{
this.checkToken(Token.NAME);
func.name = this.tokenizer.string;
this.tokenizer.next();
if (this.tokenizer.token == Token.DOT
|| this.tokenizer.token == Token.COLON)
{
this.scope.isOop = this.tokenizer.token == Token.COLON;
// We use the scope parent here ... why?
// Because we want to support 'local classes' ... and the parent
// scope should always be valid here (static scope)
this.scope.oopVariable = this.scope.parent.findVariable(
new Variable(), func.name);
this.needGetVariable(this.scope.oopVariable);
this.tokenizer.next();
this.checkToken(Token.NAME);
func.name += (this.scope.isOop ? "$$" : "$")
+ (this.scope.oopIndex = this.tokenizer.string);
this.tokenizer.next();
}
}
else
{
if (this.tokenizer.token == Token.COLON)
{
this.scope.isOop = true;
this.tokenizer.next();
}
func.name = "ANON";
}
int paramc = 0;
if (this.scope.isOop)
{
this.scope.addLocal("THIS");
paramc++;
}
if ((alternate && this.tokenizer.token == Token.BRACE_OPEN)
|| !alternate)
{
this.checkToken(Token.BRACE_OPEN);
this.tokenizer.next();
while (this.tokenizer.token != Token.BRACE_CLOSE)
{
paramc++;
this.checkToken(Token.NAME);
this.scope.addLocal(this.tokenizer.string);
this.tokenizer.next();
if (this.tokenizer.token == Token.COMMA)
{
this.tokenizer.next();
continue;
}
if (this.tokenizer.token != Token.BRACE_CLOSE)
{
this.syntaxError();
}
}
this.tokenizer.next();
}
func.arguments = paramc;
if (this.findFunction(func.name, func.arguments) != null)
{
throw new WeelException(this.tokenizer.error("Duplicate function: "
+ func));
}
if (!this.runtimeCompile)
{
final int fi = this.weel.addFunction(func.name + "#"
+ func.arguments, func, !(anonymous || isPrivate));
if (isPrivate)
{
this.mapFunctions.put(func.name, fi);
this.mapFunctionsExact
.put(func.name + "#" + func.arguments, fi);
}
}
this.block.isAlternateSyntax = alternate;
}
/**
* Closes a function.
*/
private void closeFunction()
{
final WeelFunction func = this.lastFunction = this.block.function;
final boolean anonymous = this.block.isAnonymousFunction;
final Variable oopVar = this.scope.oopVariable;
final String oopIndex = this.scope.oopIndex;
if (this.block.isAlternateSyntax && this.block.hasReturn)
{
func.returnsValue = true;
}
if (this.scope.breakLabel != -1)
{
this.block.add(new InstrLabel(this.scope.breakLabel));
}
if (!this.block.cvarIndex.isEmpty())
{
func.environment = new Value[this.block.cvarIndex.size()];
func.envLocals = new int[this.block.cvarIndex.size()];
for (int i = 0; i < func.environment.length; i++)
{
func.envLocals[i] = this.block.cvarIndex.get(i);
}
}
this.block.closeBlock(this.weel.debugMode, this.weel.dumpCode);
this.blockToBytecode(this.block);
this.removeScope();
if (anonymous)
{
this.removeScope();
if (!this.runtimeCompile)
{
if (func.envLocals != null)
this.block.add(new InstrCreateClosure(func.index));
else
this.block.add(new InstrLoadFunc(func.index));
}
}
this.tokenizer.next();
if (oopVar != null)
{
this.writeGetVariable(oopVar);
this.block.add(new InstrLoad(oopIndex));
this.block.add(new InstrKey());
this.block.add(new InstrLoadFunc(func.index));
this.block.add(new InstrSetMap());
}
}
/**
* Adds 'exit' to a sub.
*/
private void addExit()
{
final Scope s = this.scope.findFunctionScope();
if (s == null || s.type == ScopeType.FUNC || this.block.hasReturn)
{
throw new WeelException(this.tokenizer
.error("'exit' without 'sub'"));
}
this.block.hasExit = true;
this.writeExitPops();
this.block.add(new InstrGoto(s.addBreak()));
this.tokenizer.next();
}
/**
* Adds 'return' to a sub.
*/
private void addReturn()
{
final Scope s = this.scope.findFunctionScope();
if (s == null || s.type == ScopeType.SUB || this.block.hasExit)
{
throw new WeelException(this.tokenizer
.error("'return' without 'func'"));
}
this.block.hasReturn = true;
this.writeExitPops();
this.tokenizer.next();
this.parseExpression();
this.block.add(new InstrGoto(s.addBreak()));
}
/**
* Parses the 'local' keyword
*/
private void parseLocal()
{
this.tokenizer.next();
this.checkToken(Token.NAME);
while (this.tokenizer.token != Token.EOF)
{
this.checkToken(Token.NAME);
final String name = this.tokenizer.string;
if (this.scope.locals.containsKey(name)
|| this.block.cvars.containsKey(name))
{
throw new WeelException(this.tokenizer.error(
"Duplicate explicit local variable '%s'", name));
}
final int index = this.scope.addLocal(name);
this.tokenizer.next();
if (this.tokenizer.token == Token.ASSIGN)
{
this.tokenizer.next();
this.parseExpression();
this.block.add(new InstrVarStore(VarInstrType.LOCAL, index));
}
if (this.tokenizer.token == Token.COMMA)
{
this.tokenizer.next();
continue;
}
break;
}
}
/**
* Parses the 'outer' keyword
*/
private void parseOuter()
{
final Scope outer = this.scope.getBorderScope();
if (outer == null)
{
throw new WeelException(this.tokenizer
.error("'outer' without anonymous function"));
}
this.tokenizer.next();
this.checkToken(Token.NAME);
while (this.tokenizer.token != Token.EOF)
{
this.checkToken(Token.NAME);
final String name = this.tokenizer.string;
int lidx = outer.findLocal(name);
if (this.scope.findCvar(name) != -1 || lidx != -1)
{
throw new WeelException(this.tokenizer.error(
"Duplicate explicit cvar '%s'", name));
}
lidx = outer.addLocal(name);
Variable var = new Variable();
var.type = Type.NONE;
var.name = name;
this.scope.maybeCreateCvar(var);
this.tokenizer.next();
this.checkToken(Token.ASSIGN);
this.tokenizer.next();
final Scope old = this.scope;
// We have to switch scopes to compile the
// initialize expression into the border scope.
this.scope = outer;
this.block = outer.block;
this.parseExpression();
this.block.add(new InstrVarStore(VarInstrType.LOCAL, lidx));
this.scope = old;
this.block = old.block;
if (this.tokenizer.token == Token.COMMA)
{
this.tokenizer.next();
continue;
}
break;
}
}
/**
* Parses the 'global' keyword
*/
private void parseGlobal()
{
this.tokenizer.next();
if (this.runtimeCompile)
{
throw new WeelException(this.tokenizer
.error("Can't use 'global' in runtime compilation."));
}
this.checkToken(Token.NAME);
while (this.tokenizer.token != Token.EOF)
{
this.checkToken(Token.NAME);
final String name = this.tokenizer.string;
if (this.weel.mapGlobals.containsKey(name))
{
throw new WeelException(this.tokenizer.error(
"Duplicate global variable '%s'", name));
}
final int index = this.weel.addGlobal(name);
this.tokenizer.next();
if (this.tokenizer.token == Token.ASSIGN)
{
this.tokenizer.next();
this.parseExpression();
this.block.add(new InstrVarStore(VarInstrType.GLOBAL, index));
}
if (this.tokenizer.token == Token.COMMA)
{
this.tokenizer.next();
continue;
}
break;
}
}
/**
* Parses the 'private' keyword
*/
private void parsePrivate()
{
this.checkToken(Token.NAME);
while (this.tokenizer.token != Token.EOF)
{
this.checkToken(Token.NAME);
final String name = this.tokenizer.string;
if (this.scope.findPrivate(name) != -1)
{
throw new WeelException(this.tokenizer.error(
"Duplicate private variable '%s'", name));
}
final int index = this.scope.addPrivate(name);
this.tokenizer.next();
if (this.tokenizer.token == Token.ASSIGN)
{
this.tokenizer.next();
this.parseExpression();
this.block.add(new InstrVarStore(VarInstrType.PRIVATE, index));
}
if (this.tokenizer.token == Token.COMMA)
{
this.tokenizer.next();
continue;
}
break;
}
}
/**
* Checks if there's an expression.
*
* @throws WeelException
* if there's no expression.
*/
private void checkExpr()
{
if (!this.tokenizer.isExpression())
throw new WeelException(this.tokenizer.error("Expression expected"));
}
/**
* Checks if there's the specified ReservedWord.
*
* @param rw
* The ReservedWord.
* @throws WeelException
* if there's no ReservedWord.
*/
private void checkReserved(final ReservedWord rw)
{
if (this.tokenizer.token != Token.RESERVED
|| this.tokenizer.reserved != rw)
throw new WeelException(this.tokenizer.error("'%s' expected", rw
.toString().toLowerCase()));
}
/**
* Check if the specified token is there.
*
* @param tok
* The token.
* @throws WeelException
* if there's no token.
*/
private void checkToken(final Token tok)
{
if (this.tokenizer.token != tok)
this.errorExp(tok);
}
/**
* Skips a semicolon if present.
*/
private void skipSemi()
{
if (this.tokenizer.token == Token.SEMICOLON)
this.tokenizer.next();
}
/**
* Throws a syntax error.
*/
private void syntaxError()
{
throw new WeelException(this.tokenizer.error("Syntax error ("
+ this.tokenizer.token.toString().toLowerCase() + ")"));
}
/**
* Token error message.
*
* @param tok
* The token.
*/
private void errorExp(final Token tok)
{
String msg = "";
switch (tok)
{
case BRACE_OPEN:
msg += "'('";
break;
case BRACE_CLOSE:
msg += "')'";
break;
case BRACKET_CLOSE:
msg += "']'";
break;
case CURLY_BRACE_CLOSE:
msg += "'}'";
break;
case COMMA:
msg += "','";
break;
case COLON:
msg += "':'";
break;
case ASSIGN:
msg += "'='";
break;
case NAME:
msg += "Name";
break;
case NUMBER:
msg += "Number";
break;
case STRING:
msg += "String";
break;
default:
break;
}
throw new WeelException(this.tokenizer.error(msg + " expected"));
}
/**
* Write a load variable instruction.
*
* @param var
* The variable.
*/
private void writeGetVariable(final Variable var)
{
switch (var.type)
{
case LOCAL:
this.block.add(new InstrVarLoad(VarInstrType.LOCAL, var.index));
break;
case GLOBAL:
this.block.add(new InstrVarLoad(VarInstrType.GLOBAL, var.index));
break;
case PRIVATE:
this.block.add(new InstrVarLoad(VarInstrType.PRIVATE, var.index));
break;
case CVAR:
this.block.add(new InstrVarLoad(VarInstrType.CVAR, var.index));
break;
default:
if (var.function != null)
this.block.add(new InstrLoadFunc(var.function.index));
else
this.syntaxError();
break;
}
}
/**
* Makes sure we get a variable to read from.
*
* @param var
* The Variable.
*/
private void needGetVariable(final Variable var)
{
this.needGetVariable(var, false);
}
/**
* Makes sure we get a variable to read from.
*
* @param var
* The Variable.
* @param maybeFunction
* Flag indicating that the may be a function. (Only affects
* error generation.)
*/
private void needGetVariable(final Variable var, final boolean maybeFunction)
{
if (var.type != Type.NONE)
return;
if (this.block.isAnonymousFunction)
{
this.scope.maybeCreateCvar(var);
}
if (var.type == Type.NONE)
{
throw new WeelException(this.tokenizer.error(
maybeFunction ? "Unknown function '%s'"
: "Unknown variable '%s'", var.name));
}
}
/**
* Makes sure we get a variable to write to.
*
* @param var
* The Variable.
*/
private void needSetVariable(final Variable var)
{
if (var.type != Type.NONE)
return;
if (this.block.isAnonymousFunction)
{
this.scope.maybeCreateCvar(var);
}
if (var.type == Type.NONE)
{
var.type = Type.LOCAL;
var.index = this.scope.addLocal(var.name);
}
}
/**
* Write a store variable instruction.
*
* @param var
* The variable.
*/
private void writeSetVariable(final Variable var)
{
switch (var.type)
{
case LOCAL:
this.block.add(new InstrVarStore(VarInstrType.LOCAL, var.index));
break;
case GLOBAL:
this.block.add(new InstrVarStore(VarInstrType.GLOBAL, var.index));
break;
case PRIVATE:
this.block.add(new InstrVarStore(VarInstrType.PRIVATE, var.index));
break;
case CVAR:
this.block.add(new InstrVarStore(VarInstrType.CVAR, var.index));
break;
default:
this.syntaxError();
}
}
/**
* Calculates the number of pops needed to be placed before an 'exit' or a
* 'return'. Writes a 'pop(n)' instruction if number of pops > 0.
*/
private void writeExitPops()
{
int pops = 0;
Scope s = this.scope;
while (s != null
&& (s.type != ScopeType.SUB && s.type != ScopeType.FUNC))
{
switch (s.type)
{
case FOR:
pops += 2;
break;
case FOREACH:
case SWITCH:
pops++;
break;
default:
break;
}
s = s.parent;
}
if (pops > 0)
{
this.block.add(new InstrPop(pops));
}
}
/**
* Parses a Weel assert.
*/
private void parseAssert()
{
this.block.add(new InstrBegAssert());
this.parseExpression();
String message = null;
if (this.tokenizer.token == Token.COMMA)
{
this.tokenizer.next();
this.checkToken(Token.STRING);
message = this.tokenizer.string;
this.tokenizer.next();
}
this.checkToken(Token.BRACE_CLOSE);
this.tokenizer.next();
this.block.add(new InstrAssert((message != null) ? this.tokenizer
.error("Assert failed (%s)", message) : this.tokenizer
.error("Assert failed")));
this.block.add(new InstrEndAssert());
}
private void blockToBytecode(final WeelCode b)
{
JvmMethodWriter mw;
if (b.function == null)
{
if (b.instrs.size() == 0)
{
return;
}
mw = this.classWriter.createMethod("STATIC",
"(Lcom/github/rjeschke/weel/WeelRuntime;)V");
}
else
{
b.function.clazz = this.classWriter.className;
b.function.javaName = b.isAnonymousFunction ? "$anon$"
+ this.anonCounter++ : b.function.name + "$"
+ b.function.arguments;
mw = this.classWriter.createMethod(b.function.javaName,
"(Lcom/github/rjeschke/weel/WeelRuntime;)V");
}
for (int i = 0; i < b.instrs.size(); i++)
{
b.instrs.get(i).write(mw);
}
mw.addOp(JvmOp.RETURN);
mw.resolveLabels();
}
private WeelFunction findFunction(final String name, final int paramc)
{
final Integer idx = this.mapFunctionsExact.get(name.toLowerCase() + "#"
+ paramc);
if (idx != null)
{
return this.weel.functions.get(idx);
}
return this.weel.findFunction(name, paramc);
}
WeelFunction findFunction(final String name)
{
final Integer idx = this.mapFunctions.get(name.toLowerCase());
if (idx != null)
{
return this.weel.functions.get(idx);
}
return this.weel.findFunction(name);
}
}