/*
* $Id: LinkNode.java,v 1.39 2002/09/16 08:05:05 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.expression;
import anvil.Location;
import anvil.core.Any;
import anvil.core.LangModule;
import anvil.core.Modules;
import anvil.core.reflect.Reflection;
import anvil.codec.Code;
import anvil.script.compiler.ByteCompiler;
import anvil.ErrorListener;
import anvil.script.CompilableFunction;
import anvil.script.Context;
import anvil.script.Import;
import anvil.script.Imported;
import anvil.script.Type;
import anvil.script.InterfaceType;
import anvil.script.VariableType;
import anvil.script.LocalVariableType;
import anvil.script.ConstantVariableType;
import anvil.script.StaticVariableType;
import anvil.script.MethodType;
import anvil.script.MemberVariableType;
import anvil.script.Name;
import anvil.script.NamespaceType;
import anvil.script.ClassType;
import anvil.script.Scope;
import anvil.script.ReferenceResolver;
import anvil.script.ReflectedJava;
import anvil.script.NativeJava;
import anvil.script.Grammar;
import anvil.script.Synthetic;
import anvil.script.statements.Statement;
import anvil.script.statements.FunctionStatement;
import anvil.script.statements.ClassStatement;
import anvil.script.statements.LocalVariableStatement;
import anvil.script.statements.ModuleStatement;
import anvil.script.parser.ParserBaseConstants;
import anvil.server.Zone;
import java.io.IOException;
import java.util.ArrayList;
/**
* class LinkNode
*
* @author: Jani Lehtim�ki
*/
public class LinkNode extends Node implements ReferenceResolver
{
public static final int GET = 0;
public static final int ASSIGN = 1;
public static final int DECLARE = 2;
public static final int NEW = 3;
public static final int SUPER = 4;
protected ModuleStatement _script;
protected Statement _statement;
protected Location _location;
protected Name _name;
protected int _index = 0;
protected Parent _args;
protected int _role;
public LinkNode(Statement stmt, Location location, Name name, Parent args, int role)
{
super();
_script = stmt.getModuleStatement();
_statement = stmt;
_location = location;
_name = name;
_args = args;
_role = role;
}
public LinkNode(Statement stmt, Location location, Name name)
{
super();
_script = stmt.getModuleStatement();
_statement = stmt;
_location = location;
_name = name;
_args = null;
_role = GET;
}
public void setRole(int role)
{
_role = role;
}
public int typeOf()
{
return Node.EXPR_LINK;
}
public boolean isConstant()
{
return false;
}
public Node optimize()
{
if (_args != null) {
_args.optimize();
}
return this;
}
public String toString()
{
return toString(true);
}
public String toString(boolean withArgs)
{
StringBuffer buffer = new StringBuffer(32);
_name.toString(0, buffer);
if (withArgs && _args != null) {
buffer.append(_args.toString());
}
return buffer.toString();
}
protected Parent consumeArgs()
{
Parent p = _args;
_args = null;
return p;
}
public boolean hasArgs()
{
return (_args != null);
}
protected String consumeSymbol()
{
if (_index < _name.size()) {
return _name.get(_index++);
} else {
return null;
}
}
protected String peekSymbol()
{
if (_index < _name.size()) {
return _name.get(_index);
} else {
return null;
}
}
protected int peekKind()
{
if (_index < _name.size()) {
return _name.getKind(_index);
} else {
return 0;
}
}
protected int symbolsLeft()
{
return _name.size() - _index;
}
protected boolean hasMoreSymbols()
{
return _index < _name.size();
}
protected void checkArguments(ErrorListener context, CompilableFunction function)
{
Parent args = _args;
boolean hassplices = args.hasSplices();
boolean hasnamed = args.hasNamedParameters();
int n = args.childs();
if (hasnamed) {
if (hassplices) {
context.error(_location, "Splices and named parameters cannot be used together");
} else {
ArrayList fixed = new ArrayList();
int min = function.getMinimumParameterCount();
int max = function.getParameterCount();
boolean[] consumed = new boolean[n];
int p = 0;
for(int i=0; i<max; i++) {
int argtype = function.getParameterType(i);
String argname = function.getParameterName(i);
Any argdefault = function.getParameterDefault(i);
switch(argtype) {
case CompilableFunction.PARAMETER_CONTEXT:
break;
case CompilableFunction.PARAMETER_ARRAY:
case CompilableFunction.PARAMETER_ANYLIST:
case CompilableFunction.PARAMETER_LIST:
case CompilableFunction.PARAMETER_REST:
break;
default:
{
Node node = null;
int index = args.findNamedIndex(argname);
if (index == -1) {
while(p<n) {
if (!consumed[p]) {
Node child = args.getChild(p);
if (child.typeOf() != EXPR_NAMED) {
node = child;
consumed[p++] = true;
break;
}
}
p++;
}
} else {
if (!consumed[index]) {
consumed[index] = true;
node = ((NamedNode)args.getChild(index)).getChild();
} else {
context.error(_location, "Named parameter '"+argname+"' appears more than once");
}
}
if (node != null) {
fixed.add(node);
} else {
if (fixed.size()<min) {
context.error(_location, "Couldn't match for parameter #"+i+" '"+argname+"'");
} else {
fixed.add(new ConstantNode(argdefault));
}
}
}
break;
}
}
for(int i=0; i<n; i++) {
if (!consumed[i]) {
Node node = args.getChild(i);
if (node.typeOf() == EXPR_NAMED) {
NamedNode named = (NamedNode)node;
context.error(_location, "Supplied named parameter #"+(i+1)+" with name '"+named.getName()+"' was not matched");
node = named.getChild();
}
fixed.add(node);
}
}
_args = new ExpressionList((Node[])fixed.toArray(new Node[fixed.size()]));
}
} else {
if (!hassplices) {
if (n < function.getMinimumParameterCount()) {
context.error(_location, "Too few parameters in call to '" + function + "'");
}
}
}
}
protected Type lookupLibrary(ErrorListener context)
{
Zone zone = _script.getAddress().getZone();
Modules modules = zone.getModules();
Type type = modules.lookupDeclaration(_name.get(_index-1));
if (type != null) {
return type;
}
StringBuffer libname = new StringBuffer(32);
int i = _index;
int length = _name.size();
int foundIndex = -1;
Scope foundScope = null;
libname.append(_name.get(i-1));
while(i <= length) {
String name = libname.toString();
Scope scope = zone.findJava(name);
if (scope != null) {
foundIndex = i;
foundScope = scope;
}
if (i >= length) {
break;
}
libname.append('.');
libname.append(_name.get(i));
i++;
}
if (foundIndex != -1) {
_index = foundIndex;
return foundScope;
}
return null;
}
protected Node explicitThisConstruct(ErrorListener listener, ClassType classtype)
{
ClassType context = _statement.getClassStatement();
if (!hasMoreSymbols()) {
return new ThisNode(context, classtype);
}
Type type = classtype.lookupDeclaration(peekSymbol());
if (type == null) {
listener.error(_location, "Undefined entity '" + _name + "'");
return null;
}
switch(type.getType()) {
case Type.METHOD:
{
MethodType method = (MethodType)type;
consumeSymbol();
if (hasMoreSymbols()) {
//methodname
return new DelegateNode(new ThisNode(context, classtype), method);
} else {
if (hasArgs()) {
//methodname(...)
checkArguments(listener, method);
return new StaticInvokeNode(classtype, context, method, consumeArgs());
} else {
//methodname
return new DelegateNode(new ThisNode(context, classtype), method);
}
}
}
case Type.MEMBER_VARIABLE:
{
MemberVariableType member = (MemberVariableType)type;
consumeSymbol();
return new MemberVariableNode(classtype, context, member);
}
case Type.CONSTANT_VARIABLE:
{
String symbol = consumeSymbol();
if (_role != GET) {
listener.error(_location, "Attempting to assign to constant '"+symbol+"'");
}
return new ConstantVariableNode((ConstantVariableType)type);
}
case Type.STATIC_VARIABLE:
{
consumeSymbol();
return new StaticVariableNode((StaticVariableType)type);
}
case Type.IMPORT:
{
listener.error(_location, "Imported entities are not available from explicit 'this' construct");
return null;
}
default:
{
return new ThisNode(context, classtype);
}
}
}
protected Node classConstruct(ErrorListener listener, Scope scope)
{
if (!hasMoreSymbols()) {
return new TypeNode(scope);
}
if (peekKind() == ParserBaseConstants.THIS) {
if (scope.getType() == Type.CLASS) {
consumeSymbol();
return explicitThisConstruct(listener, (ClassType)scope);
} else {
listener.error(_location, "Left of 'this' in '"+_name+"' does not point into class");
}
}
String symbol = peekSymbol();
Type type = scope.lookupDeclaration(symbol);
if (type == null) {
return new TypeNode(scope);
}
switch(type.getType()) {
case Type.CLASS:
case Type.INTERFACE:
{
consumeSymbol();
if (hasMoreSymbols()) {
return classConstruct(listener, (Scope)type);
} else {
return new TypeNode(type);
}
}
case Type.MEMBER_VARIABLE:
case Type.METHOD:
case Type.INTERFACE_METHOD:
case Type.FUNCTION:
case Type.CONSTRUCTOR:
{
consumeSymbol();
return new TypeNode(type);
}
case Type.STATIC_VARIABLE:
{
consumeSymbol();
return new StaticVariableNode((StaticVariableType)type);
}
case Type.CONSTANT_VARIABLE:
{
consumeSymbol();
if (_role != GET) {
listener.error(_location, "Attempting to assign to constant '"+symbol+"'");
}
return new ConstantVariableNode((ConstantVariableType)type);
}
case Type.IMPORT:
{
listener.error(_location, "Imported entities are not available from explicit class construct");
return null;
}
default:
{
return new TypeNode(scope);
}
}
}
protected Type followImports(ErrorListener listener, Type type)
{
while(type.getType() == Type.IMPORT) {
Imported imported = (Imported)type;
if (imported.getModule() != _script) {
listener.error(_location, "Attempting to refer to '"+imported+"' in another module");
return null;
}
type = imported.getPointedType();
}
return type;
}
protected Node onFunction(ErrorListener listener, Type type)
{
if (type instanceof FunctionStatement) {
FunctionStatement function = (FunctionStatement)type;
if (function.getContext() != null) {
if (hasArgs()) {
return new InlinedCallNode(_statement.getFunctionStatement(), function, consumeArgs());
} else {
return new InlinedFunctionNode(_statement.getFunctionStatement(), function);
}
}
}
if (hasMoreSymbols()) {
return new TypeNode((CompilableFunction)type);
//functioname[.field...]
} else {
if (hasArgs()) {
//functioname(...)
CompilableFunction function = (CompilableFunction)type;
checkArguments(listener, function);
return new CallNode(function, consumeArgs());
} else {
//functioname
return new TypeNode((CompilableFunction)type);
}
}
}
protected Node construct(ErrorListener listener, Scope scope)
{
String symbol = peekSymbol();
Type type;
if (scope != null) {
consumeSymbol();
type = scope.lookupDeclaration(symbol);
if (type == null) {
listener.error(_location, "Entity '" + _name + "' is undeclared");
return null;
}
} else {
switch(_role) {
case DECLARE:
{
consumeSymbol();
FunctionStatement function = _statement.getFunctionStatement();
type = function.lookupDeclaration(symbol);
if (type == null) {
type = function.declare(symbol);
}
}
break;
case ASSIGN:
{
consumeSymbol();
type = _statement.lookupAnyDeclaration(symbol);
if (type == null) {
consumeSymbol();
FunctionStatement function = _statement.getFunctionStatement();
if (function != null) {
type = function.declare(symbol);
} else {
listener.error(_location, "Entity '" + _name + "' is undeclared");
return null;
}
}
}
break;
default:
{
type = _statement.lookupAnyDeclaration(symbol);
if (type == null) {
type = LangModule.__module__.lookupDeclaration(symbol);
if (type == null) {
consumeSymbol();
type = lookupLibrary(listener);
if (type == null) {
listener.error(_location, "Entity '" + _name + "' is undeclared");
return null;
}
symbol = type.getName();
} else {
type = LangModule.__module__;
symbol = type.getName();
}
} else {
consumeSymbol();
}
}
break;
}
}
type = followImports(listener, type);
if (type == null) {
return null;
}
if (type instanceof ReflectedJava) {
if (type instanceof Reflection) {
return new JavaClassNode(type.getName());
} else {
return new JavaClassNode(type.getParent().getName());
}
}
switch(type.getType()) {
case Type.MODULE:
case Type.NAMESPACE:
{
if (!hasMoreSymbols()) {
return new TypeNode(type);
}
return construct(listener, (Scope)type);
}
case Type.INTERFACE:
case Type.CLASS:
{
return classConstruct(listener, (Scope)type);
}
case Type.GLOBAL_NAMESPACE:
{
return GlobalNamespaceNode.INSTANCE;
}
case Type.SYSTEM_NAMESPACE:
{
return new NamespaceNode((NamespaceType)type);
}
case Type.FUNCTION:
{
return onFunction(listener, type);
}
case Type.INTERFACE_METHOD:
{
return new TypeNode(type);
}
case Type.METHOD:
{
MethodType method = (MethodType)type;
ClassStatement context = _statement.getClassStatement();
if (type instanceof FunctionStatement) {
FunctionStatement function = (FunctionStatement)type;
if (function.getContext() != null) {
if (hasArgs()) {
return new InlinedCallNode(_statement.getFunctionStatement(), function, consumeArgs());
} else {
return new InlinedFunctionNode(_statement.getFunctionStatement(), function);
}
}
}
if (hasMoreSymbols()) {
return new TypeNode(type);
} else {
Grammar.checkInstanceAmbiguity(listener, _location, context, method);
if (hasArgs()) {
//methodname(...)
checkArguments(listener, method);
return new StaticInvokeNode(method.getClassType(), context, method, consumeArgs());
} else {
//methodname
return new DelegateNode(new ThisNode(context, method.getClassType()), method);
}
}
}
case Type.CONSTRUCTOR:
{
if (!hasMoreSymbols()) {
if (hasArgs()) {
listener.error(_location, "Trying to call constructor '"+type+"'");
return null;
}
}
return new TypeNode(type);
}
case Type.CONSTANT_VARIABLE:
{
if (_role != GET) {
listener.error(_location, "Attempting to assign to constant '"+symbol+"'");
}
return new ConstantVariableNode((ConstantVariableType)type);
}
case Type.STATIC_VARIABLE:
{
return new StaticVariableNode((StaticVariableType)type);
}
case Type.MEMBER_VARIABLE:
{
ClassStatement context = _statement.getClassStatement();
MemberVariableType member = (MemberVariableType)type;
Grammar.checkInstanceAmbiguity(listener, _location, context, member);
return new MemberVariableNode(context, member);
}
case Type.FUNCTION_PARAMETER:
case Type.LOCAL_VARIABLE:
{
FunctionStatement context = _statement.getFunctionStatement();
if (type.getParent() != context) {
return new EscapedVariableNode(symbol, (LocalVariableStatement)type, context);
} else {
return new VariableNode((LocalVariableStatement)type);
}
}
}
return null;
}
protected Node superConstruct(ErrorListener context)
{
consumeSymbol();
ClassType classtype = _statement.getClassStatement();
boolean in_method = (_statement.getFunctionStatement() != null);
if (classtype == null) {
context.error(_location, "Cannot use 'super' outside the scope of class");
return null;
}
if (classtype.getBase() == null) {
context.error(_location, "Cannot use 'super' in classes without base class");
return null;
}
if ((symbolsLeft() == 0) && hasArgs() && in_method) {
if (_role != SUPER) {
context.error(_location, "Superclass constructor invoke must appear as first statement in the body of constructor");
return null;
}
ClassType base = classtype.getBaseClass();
CompilableFunction constructor = base.getConstructor();
if (constructor == null) {
context.error(_location, "Base class '"+base+"' does not have constructor");
return null;
}
checkArguments(context, constructor);
return new ConstructorInvokeNode(classtype, consumeArgs());
}
if (symbolsLeft() < 1) {
context.error(_location, "Invalid use of 'super'.");
return null;
}
String symbol = consumeSymbol();
Type type = classtype.lookupInheritedDeclaration(symbol);
if (type == null) {
context.error(_location, "Undefined entity '"+_name+"'");
return null;
}
switch(type.getType()) {
case Type.INTERFACE_METHOD:
{
context.error(_location, "Illegal access to interface method with 'super'");
return null;
}
case Type.METHOD:
if (!hasArgs()) {
return new TypeNode(type);
}
if (_statement.isStaticRegion()) {
context.error(_location, "Attempting to invoke '"+type+"' from static region");
}
MethodType ctor = (MethodType)type;
checkArguments(context, ctor);
return new SuperInvokeNode(ctor, consumeArgs());
case Type.FUNCTION:
if (!hasArgs()) {
return new TypeNode(type);
}
CompilableFunction function = (CompilableFunction)type;
checkArguments(context, function);
return new CallNode(function, consumeArgs());
case Type.CONSTANT_VARIABLE:
return new ConstantVariableNode((ConstantVariableType)type);
case Type.STATIC_VARIABLE:
return new StaticVariableNode((StaticVariableType)type);
case Type.MEMBER_VARIABLE:
if (_statement.isStaticRegion()) {
context.error(_location, "Attempting to refer to member variable '"+type+"' from static region");
}
if (!in_method) {
break;
}
return new MemberVariableNode(null, (MemberVariableType)type);
case Type.IMPORT:
context.error(_location, "Imported entities may not be referred with 'super'");
return null;
default:
break;
}
context.error(_location, "Invalid use of 'super'");
return null;
}
protected Node newConstruct(ErrorListener listener, ClassType target)
{
if (!hasMoreSymbols()) {
listener.error(_location, "Syntax error: class name expected after 'new'");
return null;
}
Type type;
String symbol;
if (peekKind() == ParserBaseConstants.MODULE) {
consumeSymbol();
type = _script;
} else {
symbol = consumeSymbol();
type = _statement.lookupAnyDeclaration(symbol);
if (type == null) {
type = LangModule.__module__.lookupDeclaration(symbol);
if (type == null) {
type = lookupLibrary(listener);
}
}
}
while(true) {
if (type == null) {
listener.error(_location, "Entity '" + _name.toString(1) + "' is undeclared");
return null;
}
type = followImports(listener, type);
if (type == null) {
return null;
}
if (type instanceof ReflectedJava) {
if (type instanceof Reflection) {
return new JavaClassNode(type.getName());
} else {
return new JavaClassNode(type.getParent().getName());
}
}
switch(type.getType()) {
case Type.MODULE:
case Type.NAMESPACE:
{
if (hasMoreSymbols()) {
symbol = consumeSymbol();
type = ((Scope)type).lookupDeclaration(symbol);
break;
}
listener.error(_location, "Entity '" + _name.toString(1) + "' is not a class");
return null;
}
case Type.CLASS:
{
ClassType clazz = (ClassType)type;
if (hasMoreSymbols()) {
symbol = consumeSymbol();
type = clazz.lookupDeclaration(symbol);
break;
}
if (!hasArgs()) {
listener.error(_location, "Syntax error: argument list expected after '"+_name.toString(1)+"'");
return null;
}
CompilableFunction constructor = clazz.getConstructor();
if (constructor == null) {
listener.error(_location, "Class '" + clazz + "' does not have constructor");
return null;
}
ClassType context = _statement.getClassStatement();
checkArguments(listener, constructor);
Grammar.checkInstanceAccess(listener, _location, _statement, clazz);
//Grammar.checkInstanceAmbiguity(listener, _location, context, clazz);
return new NewNode(context, clazz, constructor, consumeArgs());
}
default:
listener.error(_location, "Entity '" + _name.toString(1) + "' is not a class");
return null;
}
}
}
protected Node doLink(ErrorListener listener)
{
ClassType classtype;
switch(peekKind()) {
case ParserBaseConstants.SYMBOL:
{
String symbol = peekSymbol();
if (symbol.equals("__FILE__")) {
consumeSymbol();
if (_role != GET) {
listener.error(_location, "Pseudo variable '__FILE__' can only be referred");
}
return new ConstantNode(Any.create(_location.getURL().toString()));
} else if (symbol.equals("__LINE__")) {
consumeSymbol();
if (_role != GET) {
listener.error(_location, "Pseudo variable '__LINE__' can only be referred");
}
return new ConstantNode(Any.create(_location.getLine()));
}
}
case ParserBaseConstants.STATIC:
// symbol
return construct(listener, null);
case ParserBaseConstants.MODULE:
// module
consumeSymbol();
if (!hasMoreSymbols()) {
return new TypeNode(_script);
}
return construct(listener, _script);
case ParserBaseConstants.CLASS:
// class
consumeSymbol();
classtype = _statement.getClassStatement();
if (classtype != null) {
return classConstruct(listener, classtype);
} else {
listener.error(_location, "Cannot use 'class' outside classes");
return null;
}
case ParserBaseConstants.FUNCTION:
{
// function
consumeSymbol();
FunctionStatement function = _statement.getFunctionStatement();
if (function != null) {
return onFunction(listener, function);
} else {
listener.error(_location, "Cannot use 'function' outside functions");
return null;
}
}
case ParserBaseConstants.THIS:
case ParserBaseConstants.DOT:
// this
// <dot> <symbol> ...
consumeSymbol();
classtype = _statement.getClassStatement();
if (classtype != null) {
if (_statement.isStaticRegion()) {
listener.error(_location, "Cannot use 'this' in static region");
}
if (hasMoreSymbols()) {
return explicitThisConstruct(listener, classtype);
//return construct(listener, classtype);
} else {
return ThisNode.INSTANCE;
}
} else {
listener.error(_location, "Cannot use 'this' outside classes");
return null;
}
case ParserBaseConstants.SUPER:
// super.method(...)
return superConstruct(listener);
case ParserBaseConstants.NEW:
// new ...
consumeSymbol(); // new
return newConstruct(listener, null);
}
return null;
}
public Node link(ErrorListener listener)
{
Node node = doLink(listener);
if (node == null) {
node = ConstantNode.UNDEFINED;
}
if (node instanceof TypeNode) {
Type type = ((TypeNode)node).getType();
if (type instanceof Synthetic) {
listener.error(_location, "Cannot refer to synthetic entities");
node = ConstantNode.UNDEFINED;
}
}
if (hasMoreSymbols()) {
while(hasMoreSymbols()) {
if (symbolsLeft()==1 && hasArgs()) {
// *.symbol(...)
Parent args = consumeArgs();
if (args.hasNamedParameters()) {
listener.error(_location, "Named parameters are ignored in anonymous invokes");
}
node = new InvokeNode(node, consumeSymbol(), args);
} else {
// *.symbol
node = new AttributeNode(node, consumeSymbol());
}
}
} else {
// *.(...)
if (hasArgs()) {
Parent args = consumeArgs();
if (args.hasNamedParameters()) {
listener.error(_location, "Named parameters are ignored in anonymous calls");
}
node = new DynamicCallNode(node, args);
}
}
return node;
}
public String getReference()
{
return toString();
}
public Location getLocation()
{
return _location;
}
public Type resolveReference(ErrorListener listener)
{
Type type;
if (peekKind() == ParserBaseConstants.MODULE) {
consumeSymbol();
type = _script;
} else {
String symbol = peekSymbol();
type = _statement.lookupAnyDeclaration(symbol);
if (type == null) {
type = LangModule.__module__.lookupDeclaration(symbol);
if (type == null) {
consumeSymbol();
type = lookupLibrary(listener);
} else {
type = LangModule.__module__;
symbol = type.getName();
}
} else {
consumeSymbol();
}
}
while(true) {
if (type == null) {
listener.error(_location, "Entity '" + _name + "' is undeclared");
return null;
}
type = followImports(listener, type);
if (type == null) {
return null;
}
switch(type.getType()) {
case Type.MODULE:
case Type.CLASS:
case Type.INTERFACE:
case Type.NAMESPACE:
{
if (hasMoreSymbols()) {
type = ((Scope)type).lookupDeclaration(consumeSymbol());
break;
} else {
return type;
}
}
default:
{
if (hasMoreSymbols()) {
listener.error(_location, "Entity '" + _name + "' is undeclared");
}
return type;
}
}
}
}
public Any eval()
{
return Any.UNDEFINED;
}
public void compile(ByteCompiler context, int operation)
{
context.getCode().aconst_null();
}
}