/*
* 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.util.HashMap;
import com.github.rjeschke.weel.Weel;
import com.github.rjeschke.weel.Variable.Type;
/**
* A Weel scope.
*
* @author René Jeschke <rene_jeschke@yahoo.de>
*/
class Scope
{
/** The weel. */
final Weel weel;
/** The scope type. */
final ScopeType type;
/** The parent. */
final Scope parent;
/** The root. */
final Scope root;
/** The root. */
final Compiler compiler;
/** The CodeBlock. */
WeelCode block;
/** Local name to index mapping. */
final HashMap<String, Integer> locals = new HashMap<String, Integer>();
/** Private name to index mapping. */
final HashMap<String, Integer> privates = new HashMap<String, Integer>();
/** Various flags. */
boolean hasElse, hasDefault, hasCase;
/** Starting label for loops. */
int start;
/** Local variable index for FOR loops. */
int localIndex;
/** OOP variable for functions. */
Variable oopVariable;
/** OOP array index. */
String oopIndex;
/** Is this a oop function? */
boolean isOop;
/** Is this a private function. */
boolean isPrivate;
/** Break label. */
int breakLabel = -1;
/** Continue label. */
int continueLabel = -1;
/**
* Constructor.
*
* @param weel
* The Weel.
* @param type
* The type.
*/
public Scope(final Weel weel, final ScopeType type, final Compiler compiler)
{
this.type = type;
this.weel = weel;
this.parent = null;
this.root = this;
this.compiler = compiler;
}
/**
* COnstructor.
*
* @param parent
* The scope parent.
* @param type
* The type.
*/
public Scope(final Scope parent, final ScopeType type)
{
this.type = type;
this.parent = parent;
this.block = parent.block;
this.weel = parent.weel;
this.root = parent.root;
this.compiler = parent.compiler;
}
/**
* Finds a surrounding code block.
*
* @return The code block scope or <code>null</code>
*/
Scope findOuterCodeScope()
{
Scope current = this;
while (current != null)
{
if (current.block != this.block)
return current;
current = current.parent;
}
return null;
}
/**
* Finds a surrounding function scope.
*
* @return The function scope or <code>null</code>
*/
Scope findFunctionScope()
{
Scope current = this;
while (current != null)
{
if (current.type == ScopeType.FUNC || current.type == ScopeType.SUB)
return current;
current = current.parent;
}
return null;
}
/**
* Finds a border scope.
*
* @return The border scope or <code>null</code>
*/
Scope getBorderScope()
{
Scope current = this;
while (current != null)
{
if (current.type == ScopeType.BORDER)
return current;
current = current.parent;
}
return null;
}
/**
* Gets a scope which accepts a 'break'.
*
* @return The scope or <code>null</code>
*/
Scope getBreakScope()
{
Scope current = this;
while (current != null)
{
switch (current.type)
{
case FUNC:
case SUB:
return null;
case FOR:
case FOREACH:
case SWITCH:
case DO:
case WHILE:
return current;
default:
break;
}
current = current.parent;
}
return null;
}
/**
* Gets a scope which accepts a 'continue'.
*
* @return The scope or <code>null</code>
*/
Scope getContinueScope()
{
Scope current = this;
while (current != null)
{
switch (current.type)
{
case FUNC:
case SUB:
return null;
case FOR:
case FOREACH:
case DO:
case WHILE:
return current;
default:
break;
}
current = current.parent;
}
return null;
}
/**
* Adds a break.
*/
int addBreak()
{
if(this.breakLabel == -1)
{
this.breakLabel = this.block.registerLabel();
}
return this.breakLabel;
}
/**
* Adds a continue.
*/
int addContinue()
{
if(this.continueLabel == -1)
{
this.continueLabel = this.block.registerLabel();
}
return this.continueLabel;
}
/**
* Finds a local variable.
*
* @param name
* The name.
* @return The index or <code>-1</code>
*/
int findLocal(final String name)
{
if (this.locals.containsKey(name))
{
return this.locals.get(name);
}
if (this.parent != null
&& (this.type != ScopeType.SUB && this.type != ScopeType.FUNC))
{
return this.parent.findLocal(name);
}
return -1;
}
/**
* Finds a private variable.
*
* @param name
* The name.
* @return The index or <code>-1</code>
*/
int findPrivate(final String name)
{
final Integer index = this.root.privates.get(name);
return index == null ? -1 : index;
}
/**
* Finds a local variable, searches up to static scope.
*
* @param name
* The name.
* @return The index or <code>-1</code>
*/
int findLocalFull(final String name)
{
if (this.locals.containsKey(name))
{
return this.locals.get(name);
}
if (this.parent != null)
{
return this.parent.findLocalFull(name);
}
return -1;
}
/**
* Finds a closure variable.
*
* @param name
* The name.
* @return The index or <code>-1</code>
*/
int findCvar(final String name)
{
if (this.block.cvars.containsKey(name))
{
return this.block.cvars.get(name);
}
if (this.parent != null
&& (this.type != ScopeType.SUB && this.type != ScopeType.FUNC))
{
return this.parent.findCvar(name);
}
return -1;
}
/**
* Finds a closure variable, searches up to top nested anonymous function.
*
* @param name
* The name.
* @return The index or <code>-1</code>
*/
int findCvarFull(final String name)
{
if (this.block.cvars.containsKey(name))
{
return this.block.cvars.get(name);
}
if (this.parent != null && this.parent.block.isAnonymousFunction)
{
return this.parent.findCvarFull(name);
}
return -1;
}
/**
* Adds a private variable to this scope.
*
* @param name
* The name.
* @return The index.
*/
int addPrivate(String name)
{
int ret = this.weel.registerPrivate();
this.root.privates.put(name, ret);
return ret;
}
/**
* Adds a local variable to this scope.
*
* @param name
* The name.
* @return The index.
*/
int addLocal(String name)
{
int ret = this.block.registerLocal();
this.locals.put(name, ret);
return ret;
}
/**
* Unregisters all locals assigned with this scope.
*/
void unregisterLocals()
{
for (final int idx : this.locals.values())
{
this.block.unregisterLocal(idx);
}
}
/**
* Initialize Variable.
*
* @param var
* The Variable (out).
* @param name
* The name.
* @return The supplied Variable.
*/
Variable findVariable(final Variable var, final String name)
{
final Integer glob = this.weel.mapGlobals.get(name);
final Integer priv = this.root.privates.get(name);
var.name = name;
var.type = Type.NONE;
final int loc = this.findLocal(name);
if (loc != -1)
{
var.index = loc;
var.type = Type.LOCAL;
}
else if (this.block.isAnonymousFunction)
{
final int cv = this.findCvar(name);
if (cv != -1)
{
var.index = cv;
var.type = Type.CVAR;
}
}
if (var.type == Type.NONE)
{
if(priv != null)
{
var.index = priv;
var.type = Type.PRIVATE;
}
else if(glob != null)
{
var.index = glob;
var.type = Type.GLOBAL;
}
}
var.function = this.compiler.findFunction(name);
return var;
}
/**
* Finds a variable, performs a full search. Used in closures.
*
* @param var
* The Variable (out).
* @param name
* The name.
* @return The supplied Variable.
*/
private Variable findVariableFull(final Variable var, final String name)
{
final Integer glob = this.weel.mapGlobals.get(name);
final Integer priv = this.root.privates.get(name);
var.name = name;
var.type = Type.NONE;
if (this.block.isAnonymousFunction)
{
final int cv = this.findCvarFull(name);
if (cv != -1)
{
var.index = cv;
var.type = Type.CVAR;
}
}
if(var.type == Type.NONE)
{
final int loc = this.findLocalFull(name);
if (loc != -1)
{
var.index = loc;
var.type = Type.LOCAL;
}
}
if (var.type == Type.NONE)
{
if(priv != null)
{
var.index = priv;
var.type = Type.PRIVATE;
}
else if(glob != null)
{
var.index = glob;
var.type = Type.GLOBAL;
}
}
var.function = this.compiler.findFunction(name);
return var;
}
/**
* Checks if we can create a CVAR and if so, creates it.
*
* @param var
* The variable (out).
*/
void maybeCreateCvar(final Variable var)
{
final Scope s = this.getBorderScope();
if (s == null)
return;
final Variable v = s.findVariableFull(new Variable(), var.name);
if (v.type == Type.LOCAL || v.type == Type.CVAR)
{
var.index = this.block.getCvar(v);
if(var.index != -1)
{
this.block.cvars.put(var.name, var.index);
var.type = Type.CVAR;
}
else
{
var.type = Type.NONE;
}
}
}
}