/*
* $Id: SwitchStatement.java,v 1.27 2002/09/16 08:05:06 jkl Exp $
*
* Copyright (c) 2002 Njet Communications Ltd. All Rights Reserved.
*
* Use is subject to license terms, as defined in
* Anvil Sofware License, Version 1.1. See LICENSE
* file, or http://njet.org/license-1.1.txt
*/
package anvil.script.statements;
import anvil.core.Any;
import anvil.ErrorListener;
import anvil.Location;
import anvil.codec.Code;
import anvil.codec.ConstantPool;
import anvil.codec.Source;
import anvil.codec.Target;
import anvil.codec.Switch;
import anvil.parser.Tag;
import anvil.ErrorListener;
import anvil.script.compiler.ByteCompiler;
import anvil.script.Context;
import anvil.script.expression.Node;
import anvil.script.expression.Expression;
import anvil.script.expression.VariableNode;
import anvil.script.parser.TemplateParser;
import anvil.script.Grammar;
import anvil.java.util.BindingEnumeration;
import anvil.java.util.Hashlist;
import anvil.java.util.Holder;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.ArrayList;
/**
* class SwitchStatement
*
* @author: Jani Lehtim�ki
*/
public class SwitchStatement extends ScopedStatement implements Labeled
{
public static final Any DEFAULT_MARKER = new Any()
{
public int hashCode()
{
return System.identityHashCode(this);
}
public boolean equals(Object obj)
{
return this == obj;
}
};
public static int ON_SWITCH = 0;
public static int ON_CASE = 1;
public static int ON_DEFAULT = 2;
private Expression _expression = null;
private VariableNode _switch;
private ArrayList _cases = new ArrayList();
private Hashlist _jumptable = new Hashlist();
private Case _currentCase = null;
private Case _defaultCase = null;
private int _state = ON_SWITCH;
private String _label = null;
private Source _startscope;
private Source _endscope;
private boolean _allconstant = true;
public SwitchStatement(Statement parent, Location location)
{
super(parent, location);
}
public SwitchStatement(Statement parent, Location location, Expression expression, String label)
{
super(parent, location);
_expression = expression;
_label = label;
}
public int typeOf()
{
return Statement.ST_SWITCH;
}
public String name()
{
return "switch";
}
public String getLabel()
{
return _label;
}
public Case getCase(Any key)
{
if (_allconstant) {
return (Case)_jumptable.get(key);
}
return null;
}
public Case getDefault()
{
return _defaultCase;
}
public void parse(TemplateParser parser, Tag tag)
{
String s = tag.getValue("expr");
if (s == null) {
s = tag.getValue("by");
}
_expression = Grammar.parseExpression(s, getLocation(), parser);
_label = parseLabel(parser, tag);
}
public BlockStatement getBlockStatement()
{
if (_currentCase != null) {
return _currentCase.getBlock();
} else {
return null;
}
}
public Statement getChildStatement()
{
if (_currentCase != null) {
return _currentCase.getBlock();
} else {
return null;
}
}
public Case onCase(ErrorListener listener, Expression expr)
{
_state = ON_CASE;
_currentCase = new Case(expr, new ImplicitBlockStatement(this, getLocation()));
_cases.add(_currentCase);
return _currentCase;
}
private Case onCase(TemplateParser parser, Tag tag)
{
String s = tag.getValue("value");
if (s == null) {
s = tag.getValue("of");
}
Expression value = Grammar.parseExpression(s, parser.getLocation(), parser);
return onCase(parser, value);
}
public Case onDefault(ErrorListener listener, Location location)
{
if (_defaultCase != null) {
listener.error(location, "Default already declared");
}
_state = ON_DEFAULT;
_defaultCase = new Case(null, new ImplicitBlockStatement(this, location));
_currentCase = _defaultCase;
_cases.add(_defaultCase);
return _currentCase;
}
public boolean onTag(TemplateParser parser, int type, Tag tag)
{
switch(type) {
case Statement.ST_ENDSWITCH:
parser.pop();
break;
case Statement.ST_ENDCASE:
if (_state == ON_CASE) {
_state = ON_SWITCH;
} else {
return false;
}
break;
case Statement.ST_ENDDEFAULT:
if (_state == ON_DEFAULT) {
_state = ON_SWITCH;
} else {
return false;
}
break;
case ST_CASE:
onCase(parser, tag);
break;
case ST_DEFAULT:
onDefault(parser, parser.getLocation());
break;
case ST_TAG:
if (_state == ON_SWITCH) {
return true;
}
default:
if (_state != ON_SWITCH) {
return super.onTag(parser, type, tag);
} else {
return false;
}
}
return true;
}
public void onCharacters(TemplateParser parser, String cdata)
{
if (_state != ON_SWITCH) {
super.onCharacters(parser, cdata);
}
}
public void check(ErrorListener context)
{
_expression.check(context);
Iterator iter = _cases.iterator();
while(iter.hasNext()) {
((Case)iter.next()).checkExpression(context);
}
iter = _cases.iterator();
while(iter.hasNext()) {
Case caze = (Case)iter.next();
if (caze.isDefault()) {
_jumptable.put(DEFAULT_MARKER, caze);
} else {
Any value = caze.getConstant();
if (value != null) {
if (_jumptable.containsKey(value)) {
context.error(caze.getLocation(), "Case value duplicated");
} else {
_jumptable.put(value, caze);
}
} else {
_allconstant = false;
_jumptable.clear();
break;
}
}
}
iter = _cases.iterator();
while(iter.hasNext()) {
((Case)iter.next()).check(context);
}
if (!_allconstant) {
FunctionStatement function = getFunctionStatement();
if (function.isGenerator()) {
_switch = new VariableNode(function.declare("switch$"+hashCode()));
}
}
}
public Jumps eliminate(ErrorListener context)
{
Jumps jumps = new Jumps();
Iterator iter = _cases.iterator();
while(iter.hasNext()) {
jumps.merge(((Case)iter.next()).eliminate(context));
}
if (_defaultCase != null) {
jumps.setBlocked(!jumps.hasBreak() && !jumps.hasContinue());
} else {
jumps.setBlocked(false);
}
return jumps.shift();
}
public boolean isBlocked()
{
if (_defaultCase == null) {
return false;
}
Iterator iter = _cases.iterator();
while(iter.hasNext()) {
if (!((Case)iter.next()).getBlock().isBlocked()) {
return false;
}
}
return true;
}
public Source getStartOfScope()
{
return _startscope;
}
public Source getEndOfScope()
{
return _endscope;
}
public void compile(ByteCompiler context)
{
Code code = context.getCode();
ConstantPool pool = code.getPool();
Target start = code.getTarget();
_startscope = code.getSource();
_endscope = code.getSource();
if (_allconstant) {
boolean allNumeric = true;
Enumeration enum = _jumptable.keys();
while(enum.hasMoreElements()) {
Any key = (Any)enum.nextElement();
if (key != DEFAULT_MARKER) {
if (!key.isInt()) {
allNumeric = false;
break;
}
}
}
if (_expression.needLineNumbers()) {
context.location(_expression.getLocation());
}
Switch select;
if (allNumeric) {
_expression.compile(context, Expression.GET);
code.invokevirtual(pool.addMethodRef(context.TYPE_ANY, "toInt", "()I"));
select = code.select();
enum = _jumptable.keys();
while(enum.hasMoreElements()) {
Any key = (Any)enum.nextElement();
if (key != DEFAULT_MARKER) {
select.addCase(key.toInt());
}
}
select.end();
enum = _jumptable.keysAndElements();
while(enum.hasMoreElements()) {
Case caze = (Case)enum.nextElement();
if (caze.isDefault()) {
select.bindDefault();
} else {
select.bindCase(caze.getConstant().toInt());
}
caze.compile(context);
}
} else {
int slot = context.addSwitch(_jumptable.keys());
code.getstatic(pool.addFieldRef(context.TYPE_MODULE, "_switch", "[Lanvil/java/util/Hashlist;"));
code.iconst(slot);
code.aaload();
_expression.compile(context, Expression.GET);
code.invokestatic(pool.addMethodRef("anvil/script/compiler/CompiledModule",
"switchCase", "(Lanvil/java/util/Hashlist;Lanvil/core/Any;)I"));
select = code.select();
int size = _jumptable.size();
if (_defaultCase != null) {
size--;
}
for(int key=0; key<size; key++) {
select.addCase(key);
}
select.end();
int key = 0;
enum = _jumptable.elements();
while(enum.hasMoreElements()) {
Case caze = (Case)enum.nextElement();
if (caze.isDefault()) {
select.bindDefault();
} else {
select.bindCase(key++);
}
caze.compile(context);
}
}
if (_defaultCase == null) {
select.bindDefault();
}
} else {
FunctionStatement function = getFunctionStatement();
boolean in_generator = function.isGenerator();
int l_key = 0;
if (in_generator) {
_switch.compile(context, new Node() {
public void compile(ByteCompiler context, int operation) {
_expression.compile(context, Expression.GET);
}
});
code.pop();
} else {
_expression.compile(context, Expression.GET);
l_key = code.addLocal();
code.astore(l_key);
}
int equalsmethod = pool.addMethodRef(context.TYPE_OBJECT, "equals", "(Ljava/lang/Object;)Z");
Iterator iter = _cases.iterator();
while(iter.hasNext()) {
Case caze = (Case)iter.next();
if (!caze.isDefault()) {
Expression expression = caze.getExpression();
if (in_generator) {
_switch.compile(context, Expression.GET);
} else {
code.aload(l_key);
}
if (expression.isConstant()) {
context.constant(expression.eval(), false);
} else {
expression.compile(context, Expression.GET);
}
code.invokevirtual(equalsmethod);
caze.if_ne(code);
}
}
Source isdefault = null;
if (_defaultCase != null) {
_defaultCase.go_to(code);
} else {
code.go_to(_endscope);
}
iter = _cases.iterator();
while(iter.hasNext()) {
Case caze = (Case)iter.next();
caze.compile(context);
}
if (!in_generator) {
code.endLocal(l_key);
}
}
_endscope.bind();
_startscope.bind(start);
}
}