/**
* Copyright (c) 2011 Cloudsmith Inc. and other contributors, as listed below.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cloudsmith
*
*/
package org.cloudsmith.geppetto.pp.dsl.eval;
import java.util.Collections;
import org.cloudsmith.geppetto.pp.AdditiveExpression;
import org.cloudsmith.geppetto.pp.AndExpression;
import org.cloudsmith.geppetto.pp.AtExpression;
import org.cloudsmith.geppetto.pp.DoubleQuotedString;
import org.cloudsmith.geppetto.pp.EqualityExpression;
import org.cloudsmith.geppetto.pp.FunctionCall;
import org.cloudsmith.geppetto.pp.LiteralBoolean;
import org.cloudsmith.geppetto.pp.LiteralHash;
import org.cloudsmith.geppetto.pp.LiteralList;
import org.cloudsmith.geppetto.pp.LiteralName;
import org.cloudsmith.geppetto.pp.LiteralNameOrReference;
import org.cloudsmith.geppetto.pp.LiteralUndef;
import org.cloudsmith.geppetto.pp.MatchingExpression;
import org.cloudsmith.geppetto.pp.MultiplicativeExpression;
import org.cloudsmith.geppetto.pp.OrExpression;
import org.cloudsmith.geppetto.pp.ParenthesisedExpression;
import org.cloudsmith.geppetto.pp.RelationalExpression;
import org.cloudsmith.geppetto.pp.SelectorExpression;
import org.cloudsmith.geppetto.pp.ShiftExpression;
import org.cloudsmith.geppetto.pp.SingleQuotedString;
import org.cloudsmith.geppetto.pp.TextExpression;
import org.cloudsmith.geppetto.pp.UnaryMinusExpression;
import org.cloudsmith.geppetto.pp.UnaryNotExpression;
import org.cloudsmith.geppetto.pp.VariableExpression;
import org.cloudsmith.geppetto.pp.VerbatimTE;
import org.cloudsmith.geppetto.pp.util.TextExpressionHelper;
import org.eclipse.emf.common.util.EList;
import org.eclipse.xtext.util.PolymorphicDispatcher;
/**
* Evaluates Type of an expression.
*
*/
public class PPTypeEvaluator {
public enum PPType {
/** type can not be determined statically */
DYNAMIC,
/** expression results in a string that is known to have numeric content */
NUMERIC,
/** expression results in a string that is known to be not numeric */
STRING,
/** expression results in a string that may or may not be numeric */
DYNAMIC_STRING,
/** expression results in boolean */
BOOLEAN,
/** expression result is a regexp */
REGEXP,
/** expression results in a list */
LIST,
/** expression results in a hash */
HASH,
/** expression is literal undefined */
UNDEF,
/** non value producing expression (procedure, or statement) */
VOID,
}
public enum TypeConformant {
YES, NO, INCONCLUSIVE;
public boolean maybeConformant(TypeConformant tc) {
return tc == YES || tc == INCONCLUSIVE;
}
}
private PolymorphicDispatcher<PPType> typeDispatcher = new PolymorphicDispatcher<PPType>(
"_type", 1, 1, Collections.singletonList(this), PolymorphicDispatcher.NullErrorHandler.<PPType> get()) {
@Override
protected PPType handleNoSuchMethod(Object... params) {
return PPType.VOID;
}
};
protected PPType _type(AdditiveExpression o) {
return PPType.NUMERIC;
}
protected PPType _type(AndExpression o) {
return PPType.BOOLEAN;
}
protected PPType _type(AtExpression o) {
return PPType.DYNAMIC;
}
protected PPType _type(DoubleQuotedString o) {
if(!TextExpressionHelper.hasInterpolation(o)) {
// if there is no interpolation, there is by definition only one StringData
// ignore the fact that a static string could be interpolated since this is an
// esoteric case.
// TODO: Handle this esoteric case "abc${"xyz"}"
EList<TextExpression> parts = o.getStringPart();
if(parts.size() < 1)
return PPType.STRING;
if(parts.size() == 1) {
return isNumericString(((VerbatimTE) parts.get(0)).getText())
? PPType.NUMERIC
: PPType.STRING;
}
throw new IllegalStateException("hasInterpolation() returned false, but there was interpolation");
}
// there is interpolation
// Can check if impossible that it is numeric
// A simplistic check can replace each interpolated expression with 0 and see if the result is numeric.
// This leaves a coupel of esotric cases where someone tries to compose a hex or floating point number and
// the interpolated values are for the 'x' 'X', 'E', or '+'/'-'
//
StringBuilder builder = new StringBuilder();
for(TextExpression te : o.getStringPart()) {
if(te instanceof VerbatimTE)
builder.append(((VerbatimTE) te).getText());
else
builder.append("0");
}
// i.e. either dynamic string that *may* be a number, or a non numeric string otherwise
return isNumericString(builder.toString())
? PPType.DYNAMIC_STRING
: PPType.STRING;
}
protected PPType _type(EqualityExpression o) {
return PPType.BOOLEAN;
}
protected PPType _type(FunctionCall o) {
return PPType.DYNAMIC;
}
protected PPType _type(LiteralBoolean o) {
return PPType.BOOLEAN;
}
protected PPType _type(LiteralHash o) {
return PPType.HASH;
}
protected PPType _type(LiteralList o) {
return PPType.LIST;
}
protected PPType _type(LiteralName o) {
return isNumericString(o.getValue())
? PPType.NUMERIC
: PPType.STRING;
}
protected PPType _type(LiteralNameOrReference o) {
return isNumericString(o.getValue())
? PPType.NUMERIC
: PPType.STRING;
}
protected PPType _type(LiteralUndef o) {
return PPType.UNDEF;
}
protected PPType _type(MatchingExpression o) {
return PPType.BOOLEAN;
}
protected PPType _type(MultiplicativeExpression o) {
return PPType.NUMERIC;
}
protected PPType _type(OrExpression o) {
return PPType.BOOLEAN;
}
protected PPType _type(ParenthesisedExpression o) {
return type(o.getExpr());
}
protected PPType _type(RelationalExpression o) {
return PPType.BOOLEAN;
}
protected PPType _type(SelectorExpression o) {
return PPType.DYNAMIC;
}
protected PPType _type(ShiftExpression o) {
return PPType.NUMERIC;
}
protected PPType _type(SingleQuotedString o) {
return isNumericString(o.getText())
? PPType.NUMERIC
: PPType.STRING;
}
protected PPType _type(UnaryMinusExpression o) {
return PPType.NUMERIC;
}
protected PPType _type(UnaryNotExpression o) {
return PPType.BOOLEAN;
}
protected PPType _type(VariableExpression o) {
return PPType.DYNAMIC;
}
private boolean isNumericString(String s) {
try {
Integer.parseInt(s, 10);
return true;
}
catch(NumberFormatException e) {
// not decimal
}
try {
Integer.parseInt(s, 8);
return true;
}
catch(NumberFormatException e) {
// not octal
}
try {
String tmp = s;
if(s.startsWith("-"))
tmp = s.substring(1);
if(tmp.startsWith("0x") || tmp.startsWith("0X")) {
Integer.parseInt(tmp.substring(2), 16);
return true;
}
}
catch(NumberFormatException e) {
// not hexadecimal
}
try {
Double.valueOf(s);
return true;
}
catch(NumberFormatException e) {
// not floating point
}
return false; // not numeric
}
public TypeConformant numeric(PPType t) {
if(t == PPType.NUMERIC)
return TypeConformant.YES;
if(t == PPType.DYNAMIC || t == PPType.DYNAMIC_STRING)
return TypeConformant.INCONCLUSIVE;
return TypeConformant.NO;
}
public TypeConformant numericType(Object o) {
return numeric(type(o));
}
public PPType type(Object o) {
if(o == null)
return PPType.VOID;
return typeDispatcher.invoke(o);
}
}