package edu.ucsd.arcum.interpreter.ast;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.google.common.collect.Lists;
import edu.ucsd.arcum.exceptions.ArcumError;
import edu.ucsd.arcum.exceptions.SourceLocation;
import edu.ucsd.arcum.interpreter.ast.expressions.ConstraintExpression;
import edu.ucsd.arcum.interpreter.query.Entity;
import edu.ucsd.arcum.interpreter.query.EntityDataBase;
import edu.ucsd.arcum.interpreter.query.IEntityLookup;
import edu.ucsd.arcum.interpreter.query.OptionMatchTable;
import edu.ucsd.arcum.interpreter.satisfier.Satisfier;
public class ErrorMessage
{
public static ErrorMessage EMPTY_MESSAGE = new ErrorMessage();
private SourceLocation position;
private String messageString;
private ConstraintExpression optLocationExpr;
private ErrorMessage() {
this(SourceLocation.GENERATED, "", null);
}
public ErrorMessage(SourceLocation position, String messageString, ConstraintExpression optLocationExpr) {
this.position = position;
this.messageString = messageString;
this.optLocationExpr = optLocationExpr;
}
public String getMessage(IEntityLookup lookup)
{
List<String> textAndVars = lexTextAndVariableParts(messageString);
StringBuilder builder = new StringBuilder();
Iterator<String> it = textAndVars.iterator();
builder.append(it.next());
while (it.hasNext()) {
String varName = it.next();
Object entity = lookup.lookupEntity(varName);
builder.append("\'");
if (entity == null) {
builder.append("##");
}
else {
builder.append(Entity.getDisplayString(entity));
}
builder.append("\'");
String messageText = it.next();
builder.append(messageText);
}
return builder.toString();
}
// Takes in a message string and separates the components that represent error
// message text from the components that represent unquoted variables. The first
// and last element returned is guaranteed to be a message string. So, starting
// at index 1, all odd numbered members are variables.
//
// E.x.: "Found `a and `b" -> {"Found ", "a", " and ", "b", ""}
// "`a is invalid" -> {"", "a", "is invalid"}
private List<String> lexTextAndVariableParts(String string) {
List<String> result = Lists.newArrayList();
StringBuilder builder = new StringBuilder();
for (int i=0; i<messageString.length(); ++i) {
char c = messageString.charAt(i);
if (c == '`') {
String messageTextPart = builder.toString();
result.add(messageTextPart);
builder = new StringBuilder();
++i;
while (i<messageString.length()
&& Character.isJavaIdentifierPart(messageString.charAt(i))) {
builder.append(messageString.charAt(i));
++i;
}
--i;
String varName = builder.toString();
if (varName.isEmpty()) {
ArcumError.fatalUserError(position, "A tick (`) must be followed"
+ " by a valid variable name");
}
result.add(varName);
builder = new StringBuilder();
}
else {
builder.append(c);
}
}
result.add(builder.toString());
return result;
}
private List<String> getVariablesReferenced(String string) {
List<String> list = lexTextAndVariableParts(string);
List<String> result = Lists.newArrayList();
Iterator<String> it = list.iterator();
while (it.hasNext()) {
it.next(); // Eat the message text
if (it.hasNext()) {
result.add(it.next()); // Add the variable name
}
}
return result;
}
private Object evaluateFunctionalExpression(IEntityLookup lookup,
EntityDataBase edb, OptionMatchTable symTab, ConstraintExpression arg)
{
Satisfier sat = new Satisfier(arg);
Object result = sat.evaluateEntityValue(lookup, edb, symTab);
return result;
}
@Override public String toString() {
StringBuilder buff = new StringBuilder();
buff.append("(");
buff.append("\"");
buff.append(messageString);
buff.append("\"");
if (optLocationExpr != null) {
buff.append(", ");
buff.append(optLocationExpr.toString());
}
buff.append(")");
return buff.toString();
}
public boolean hasLocation() {
return optLocationExpr != null;
}
public SourceLocation getLocation(IEntityLookup lookup, EntityDataBase edb,
OptionMatchTable symTab)
{
Object entity = evaluateFunctionalExpression(lookup, edb, symTab, optLocationExpr);
return SourceLocation.fromEntity(entity, edb);
}
public void checkUserDefinedPredicates(List<TraitSignature> tupleSets, Set<String> varsInScope) {
List<String> varsReferenced = getVariablesReferenced(messageString);
for (String var : varsReferenced) {
if (!varsInScope.contains(var)) {
ArcumError.fatalUserError(position,
"Reference to undefined variable %s (check spelling or scope)", var);
}
}
if (hasLocation()) {
optLocationExpr.checkUserDefinedPredicates(tupleSets, varsInScope);
}
}
}