package jeql.syntax;
import java.util.ArrayList;
import java.util.List;
import jeql.engine.Scope;
import jeql.engine.UndefinedVariableException;
import jeql.syntax.util.StringLiteralUtil;
/**
* Represents a RichString,
* and provides lexing from the tokenImage contents
* (without surrounding quotes).
* A RichString supports the standard Java-style character quoting.
* It can also spread across multiple lines in the
* defining file, which will be preserved as EOLs in the
* string value.
* A RichString can contain "$" references to variables
* defined in the current scope, which will be inserted as string values.
*
* @author Martin Davis
*
*/
public class RichString
{
// lexical scanning states
private static final int STATE_STR = 1;
private static final int STATE_VAR = 2;
private static final int STATE_VAR_PAREN = 3;
// markers for section types
private static final String TYPE_STRING = "string";
private static final String TYPE_VAR = "var";
private static boolean isVarChar(char c)
{
if (Character.isLetterOrDigit(c) || c == '_')
return true;
return false;
}
private String strLiteral;
private List sectionVal = new ArrayList();
private List sectionType = new ArrayList();
public RichString(String tokenContents)
{
this.strLiteral = tokenContents;
parse(strLiteral);
checkVars();
unescapeStrings();
}
private void parse(String strLiteral)
{
int pos = 0;
int len = strLiteral.length();
int state = STATE_STR;
int sectionStartPos = 0;
while (true) {
char c = 0;
if (pos < len)
c = strLiteral.charAt(pos);
pos++;
switch (state) {
case STATE_STR:
switch (c) {
case '\\':
// scan past escaped char
pos++;
break;
case '$':
state = STATE_VAR;
add(TYPE_STRING, sectionStartPos, pos-1);
sectionStartPos = pos;
break;
case 0:
add(TYPE_STRING, sectionStartPos, pos-1);
return;
}
break;
case STATE_VAR:
if (c == '{') {
state = STATE_VAR_PAREN;
sectionStartPos = pos;
}
else if (isVarChar(c)) {
// scan this char of variable
}
else if (c == 0) {
add(TYPE_VAR, sectionStartPos, pos-1);
sectionStartPos = pos;
return;
}
else {
// not var char - finish var scanning
state = STATE_STR;
add(TYPE_VAR, sectionStartPos, pos-1);
sectionStartPos = pos - 1;
}
break;
case STATE_VAR_PAREN:
if (c == '}') {
// finish var scanning
state = STATE_STR;
add(TYPE_VAR, sectionStartPos, pos-1);
sectionStartPos = pos;
}
else if (c == 0) {
add(TYPE_VAR, sectionStartPos, pos-1);
return;
}
// otherwise scan char as variable char.
break;
}
}
}
private void add(Object typeCode, int start, int end)
{
String section = strLiteral.substring(start, end);
sectionVal.add(section);
sectionType.add(typeCode);
}
private void checkVars()
{
// should do something here
}
private void unescapeStrings() {
for (int i = 0; i < sectionVal.size(); i++) {
if (sectionType.get(i) == TYPE_STRING) {
String s = (String) sectionVal.get(i);
sectionVal.set(i, StringLiteralUtil.decodeEscapedString(s));
}
}
}
public Object eval(Scope scope)
{
StringBuffer buf = new StringBuffer();
for (int i = 0; i < sectionVal.size(); i++) {
String s = (String) sectionVal.get(i);
if (sectionType.get(i) == TYPE_STRING) {
buf.append(s);
}
else {
// variable reference - insert value
if (! scope.hasVariable(s)) {
throw new UndefinedVariableException(s);
}
Object o = scope.getVariable(s);
// if variable is null, add empty string to avoid error
if (o != null)
buf.append(o.toString());
}
}
return buf.toString();
}
}