/*
* $Id: Grammar.java,v 1.7 2002/09/16 08:05:03 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;
import java.util.ArrayList;
import java.util.StringTokenizer;
import anvil.core.Any;
import anvil.ErrorListener;
import anvil.Location;
import anvil.script.statements.Statement;
import anvil.script.statements.FunctionStatement;
import anvil.script.expression.ConstantNode;
import anvil.script.expression.Expression;
import anvil.script.expression.ExpressionList;
import anvil.script.expression.Node;
import anvil.script.expression.StringBufferNode;
import anvil.script.parser.ExpressionParser;
import anvil.script.parser.NestedParser;
import anvil.script.parser.ParserBase;
import anvil.script.parser.TemplateParser;
import anvil.util.Conversions;
/**
* class Grammar
*
* @author: Jani Lehtim�ki
*/
public class Grammar
{
public static final boolean isValidIdentifier(String name)
{
return isValidIdentifier(name, false);
}
public static final boolean isValidIdentifier(String name, boolean allowDot)
{
if (name == null) {
return false;
}
int n = name.length();
if (n == 0) {
return false;
}
char ch = name.charAt(0);
if (ch == '$' || !Character.isJavaIdentifierStart(ch)) {
return false;
}
for (int i=1; i<n; i++) {
ch = name.charAt(i);
if (ch == '$' || !Character.isJavaIdentifierPart(ch)) {
if (!allowDot || ch != '.') {
return false;
}
}
}
return true;
}
public static final Expression parseExpression(String expression, Location location, TemplateParser parser)
{
return doParseExpression(ExpressionParser.TYPE_VALUE, expression, location, parser);
}
public static final Expression parseAssignmentExpression(String expression, Location location, TemplateParser parser)
{
return doParseExpression(ExpressionParser.TYPE_ASSIGNMENT, expression, location, parser);
}
public static final Expression parseStandaloneExpression(String expression, Location location, TemplateParser parser)
{
return doParseExpression(ExpressionParser.TYPE_STANDALONE, expression, location, parser);
}
public static final Expression parseAssignableExpression(String expression, Location location, TemplateParser parser)
{
return doParseExpression(ExpressionParser.TYPE_ASSIGNABLE, expression, location, parser);
}
public static final Expression[] parseForeachExpression(String expression, Location location, TemplateParser parser)
{
if (expression != null) {
ExpressionParser expressionParser = new ExpressionParser(parser, location, expression);
return expressionParser.parseForeachExpression();
} else {
parser.error(location, "Expression attribute missing");
return new Expression[0];
}
}
public static final Expression doParseExpression(int type, String expression, Location location, TemplateParser parser)
{
if (expression != null) {
ExpressionParser expressionParser =
new ExpressionParser(parser, location, expression);
return expressionParser.parseExpression(type);
} else {
parser.error(location, "Expression attribute missing");
return Expression.NULL;
}
}
public static final Node parseString(ParserBase parser, Location location, String image)
{
if (image.indexOf("${") >= 0) {
ArrayList sections = new ArrayList();
StringBuffer buffer = new StringBuffer();
int n = image.length();
char ch;
for(int i=0; i<n; i++) {
ch = image.charAt(i);
if ((i+2<n) && (ch == '$') && (image.charAt(i+1) == '{')) {
if (buffer.length()>0) {
sections.add(new ConstantNode(Conversions.unescape(buffer.toString(), false)));
}
buffer.setLength(0);
boolean singleQuote = false;
boolean doubleQuote = false;
int nesting = 1;
int startIndex = i + 2;
i++;
while(i+1<n && nesting>0) {
ch = image.charAt(++i);
switch(ch) {
case '{':
if (!singleQuote || !doubleQuote) {
nesting++;
}
buffer.append(ch);
break;
case '}':
if (!singleQuote || !doubleQuote) {
nesting--;
}
if (nesting>0) {
buffer.append(ch);
}
break;
case '\\':
if (i+1<n) {
char ch2 = image.charAt(++i);
if (ch2 == '}' || ch2 == '{') {
buffer.append(ch2);
} else {
buffer.append(ch);
buffer.append(ch2);
}
}
break;
case '\'':
if (doubleQuote == false) {
singleQuote = !singleQuote;
}
buffer.append(ch);
break;
case '"':
if (singleQuote == false) {
doubleQuote = !doubleQuote;
}
buffer.append(ch);
break;
default:
buffer.append(ch);
}
}
NestedParser expressionParser =
new NestedParser(parser, location, buffer.toString(), startIndex);
Expression expression = expressionParser.parseExpression();
if (expression.isConstant()) {
sections.add(new ConstantNode(expression.eval().toString()));
} else {
Node child = expression.getChild(0);
if (child != null) {
sections.add(child);
}
}
buffer.setLength(0);
} else {
buffer.append(ch);
}
}
if (buffer.length()>0) {
sections.add(new ConstantNode(Conversions.unescape(buffer.toString(), false)));
}
n = sections.size();
ExpressionList list = new ExpressionList(n);
for(int i=0; i<n; i++) {
list.setChild(i, (Node)sections.get(i));
}
return new StringBufferNode(list);
} else {
return new ConstantNode(Conversions.unescape(image, false));
}
}
public static void checkInstanceAccess(ErrorListener listener,
Location location, Statement context, ClassType target)
{
ClassType context_class = null;
if (context != null) {
context_class = context.getClassStatement();
}
while(target != null) {
ClassType[] required = target.getEnclosingClasses();
int n = required.length;
if (n>0) {
if (context.isStaticRegion()) {
listener.error(location, "Attempting to access instance of class '"+target+"' from static region");
return;
}
}
for(int i=0; i<n; i++) {
boolean found = false;
ClassType parent = required[i];
Type type = context_class;
while(type != null) {
if (type == parent) {
found = true;
break;
}
type = type.getParent();
}
/*if (!found) {
ClassType clazz = context_class;
while(clazz != null) {
if (clazz == parent) {
found = true;
break;
}
clazz = clazz.getBaseClass();
}
}*/
if (!found) {
listener.error(location, "Instance of enclosing '"+parent+"' of '"+target+"' is not accessible here");
}
}
target = target.getBaseClass();
}
}
public static void checkInstanceAmbiguity(ErrorListener listener,
Location location, ClassType context, Type member)
{
boolean accessible = false;
Type member_parent = member.getParent();
ClassType clazz = context;
while(clazz != null) {
if (clazz == member_parent) {
ClassType[] parents = context.getEnclosingClasses();
int n = parents.length;
for(int i=0; i<n; i++) {
if (parents[i] == member_parent) {
listener.error(location, "Ambiguous reference to '" + member + "' inherited from class '" +
member_parent+"'. Explicit qualification is required.");
return;
}
}
return;
}
clazz = clazz.getBaseClass();
}
}
public static final Name parseDottedName(String dottedname)
{
return parseDottedName(null, null, dottedname);
}
public static final Name parseDottedName(ErrorListener listener, Location location, String dottedname)
{
dottedname = dottedname.trim();
Name name = new Name();
boolean valid = true;
StringTokenizer tokenizer = new StringTokenizer(dottedname, ".");
while(tokenizer.hasMoreTokens()) {
String image = tokenizer.nextToken().trim();
if (!Grammar.isValidIdentifier(image)) {
if (listener != null) {
listener.error(location, "Syntax error, symbol '"+image+"' is invalid");
}
valid = false;
}
name.add(image);
}
if (valid) {
return name;
} else {
return null;
}
}
public static final Name[] parseDottedNames(ErrorListener listener, Location location, String dottednames)
{
boolean valid = true;
StringTokenizer tokenizer = new StringTokenizer(dottednames, ",");
ArrayList list = new ArrayList();
while(tokenizer.hasMoreTokens()) {
Name name = parseDottedName(listener, location, tokenizer.nextToken());
if (name != null) {
list.add(name);
} else {
valid = false;
}
}
if (valid) {
return (Name[])list.toArray(new Name[list.size()]);
} else {
return null;
}
}
public static final Name[] parseImportNames(ErrorListener listener, Location location, String names)
{
boolean valid = true;
names = names.replace('\t', ' ');
names = names.replace('\r', ' ');
names = names.replace('\n', ' ');
StringTokenizer tokenizer = new StringTokenizer(names, ",");
ArrayList list = new ArrayList();
while(tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken() + ' ';
String as = null;
boolean star = false;
int i = token.indexOf(" as ");
if (i>0) {
as = token.substring(i+4).trim();
if (!isValidIdentifier(as)) {
listener.error(location, "Name '"+as+"' is not a valid identifier");
}
token = token.substring(0, i).trim();
} else {
token = token.trim();
}
if (token.endsWith("*")) {
if (as != null) {
listener.error(location,"Syntax error: 'as' and '*' may not be used together in import");
}
star = true;
token = token.substring(0, token.length()-2).trim();
if (token.endsWith(".")) {
token = token.substring(0, token.length()-1);
}
}
Name name = parseDottedName(listener, location, token);
if (name != null) {
name.setAs(as);
if (star) {
name.enableStar();
}
list.add(name);
} else {
valid = false;
}
}
if (valid) {
return (Name[])list.toArray(new Name[list.size()]);
} else {
return null;
}
}
public static final int countEscapeDepth(FunctionStatement context, Statement target)
{
int depth = 0;
Statement target_parent = target.getFunctionStatement().getContext();
while(context != null) {
if (context == target) {
break;
}
if (context == target_parent) {
depth--;
break;
}
depth++;
context = context.getContext();
}
if (context == null) {
return -1;
}
return depth;
}
public static Type follow(Scope scope, String name)
{
int start = 0;
int n = name.length();
while(true) {
int end = name.indexOf('.', start+1);
if (end == -1) {
end = name.length();
}
Type type = scope.lookupDeclaration(name.substring(start, end));
if (type == null) {
return null;
}
if (end < n) {
if (type instanceof Scope) {
scope = (Scope)type;
} else {
return null;
}
} else {
return type;
}
start = end + 1;
}
}
public static final String buildQualifiedName(Type type)
{
StringBuffer buffer = new StringBuffer(32);
if (buildQualifiedName0(buffer, (Scope)type.getParent())) {
buffer.append('.');
}
buffer.append(type.getName());
return buffer.toString();
}
private static final boolean buildQualifiedName0(StringBuffer buffer, Scope scope)
{
if (scope == null) {
return false;
}
if (scope.getType() == Type.MODULE) {
return false;
}
if (buildQualifiedName0(buffer, scope.getParent())) {
buffer.append('.');
}
buffer.append(scope.getName());
return true;
}
public static final Module getModuleOf(Type type)
{
if (type.getType() == Type.MODULE) {
return (Module)type;
}
Scope scope = type.getParent();
while(scope != null) {
if (scope.getType() == Type.MODULE) {
return (Module)scope;
}
scope = scope.getParent();
}
return null;
}
}