package hr.fer.zemris.java.custom.scripting.parser;
import hr.fer.zemris.java.custom.collections.*;
import hr.fer.zemris.java.custom.scripting.nodes.*;
import hr.fer.zemris.java.custom.scripting.tokens.Token;
import hr.fer.zemris.java.custom.scripting.tokens.TokenConstantDouble;
import hr.fer.zemris.java.custom.scripting.tokens.TokenConstantInteger;
import hr.fer.zemris.java.custom.scripting.tokens.TokenFunction;
import hr.fer.zemris.java.custom.scripting.tokens.TokenOperator;
import hr.fer.zemris.java.custom.scripting.tokens.TokenString;
import hr.fer.zemris.java.custom.scripting.tokens.TokenVariable;
/**
* A parser for a document which generates a tree structure of the document text
* @author Dario Miličić
* @version 1.0
*/
public class SmartScriptParser {
public String[] tokensFOR = new String[4];
public String[] tokensEQU;
private ObjectStack docStack;
/**
* Default parser constructor which accepts a text document.
* @param docBody Text document to be parsed.
* @throws SmartScriptParserException
*/
public SmartScriptParser(String docBody) throws SmartScriptParserException {
textParser(docBody);
}
/**
* Checks if the token is valid
* @param str A string to be checked.
* @param TOKEN Token identifier.
* @return Returns true if the token is valid, else returns false.
*/
private boolean isValidToken(String str, int TOKEN) {
/* TOKEN:
* 0 - variable
* 1 - expression
* 2 - function
* 3 - symbol (*,+,/,-)
* 4 - string
*/
switch (TOKEN) {
case 0: // checks variable token
if( !Character.isLetter(str.charAt(0)) || str.charAt(0) == '@' || str.charAt(0) == '"' )
return false;
for(int i = 1; i < str.length(); i++)
if( !Character.isLetter(str.charAt(i)) && !Character.isDigit(str.charAt(i)) && str.charAt(i) != '_' )
return false;
return true;
case 1: // checks expression token
int signed = 0;
int decimal = 0;
for(int i = 0; i < str.length(); i++) {
if(str.charAt(i) == '-' && i == 0 ) signed++;
else if(str.charAt(i) == '.' && str.length() > 1) decimal++;
else if(!Character.isDigit(str.charAt(i))) return false;
}
if( signed <= 1 && decimal <= 1 )
return true;
case 2: // checks function token
if( str.charAt(0) != '@' || !Character.isLetter(str.charAt(1)) )
return false;
for(int i = 2; i < str.length(); i++)
if( !Character.isLetter(str.charAt(i)) && !Character.isDigit(str.charAt(i)) && str.charAt(i) != '_' )
return false;
return true;
case 3: // checks symbol token
char symbol = str.charAt(0);
if( symbol == '*' || symbol == '+' || symbol == '/' || symbol == '-' )
return true;
return false;
}
return false;
}
/**
* Collects all the expressions in a FOR tag and puts it in a string array.
* @param str FOR tag in a string format.
* @param idx Index of a starting element of the FOR tag (whitespace after '$').
* @return Returns the index increased by the size of the FOR loop
*/
private int getFOR(String str, int idx) {
String[] tokens = new String[4];
char c;
int i = 0;
StringBuilder buffer = new StringBuilder();
while ( str.charAt(idx) != '$' ) {
c = str.charAt(idx);
if( str.charAt(idx + 1) == '$' && c != ' ' )
buffer.append(c);
if ( (str.charAt(idx + 1) == '$' || c == ' ') && buffer.length() > 0 ) {
if( isValidToken(buffer.toString(), 0) ||
isValidToken(buffer.toString(), 1)) {
if( i > 4 ) throw new SmartScriptParserException();
tokens[i++] = buffer.toString();
buffer = new StringBuilder();
} else throw new SmartScriptParserException();
} else if(c != ' ')
buffer.append(c);
idx++;
}
for(i = 0; i < tokens.length; i++)
tokensFOR[i] = tokens[i];
return idx - 1;
}
/**
* Collects all the expressions in a = tag and puts it in a string array.
* @param str = tag in a string format.
* @param idx Index of a starting element of the = tag (whitespace after '$').
* @return Returns the index increased by the size of the EQU tag
*/
private int getEQU(String str, int idx) {
char c;
StringBuilder buffer = new StringBuilder();
ArrayBackedIndexedCollection array = new ArrayBackedIndexedCollection();
boolean exit = false;
final int ESC_T = 0;
final int ESC_S = 1;
final int TOKENS = 2;
final int STRING = 3;
final int EXIT = 4;
int state = TOKENS;
while(!exit && idx < str.length()) {
c = str.charAt(idx);
switch (state) {
case TOKENS:
switch (c) {
case '$':
if(buffer.length() > 0) {
array.add((String)buffer.toString());
buffer = new StringBuilder();
}
state = EXIT;
break;
case '"':
state = STRING;
buffer.append(c);
break;
case ' ':
if( buffer.length() > 0 ) {
array.add((String)buffer.toString());
buffer = new StringBuilder();
}
break;
case '\\':
state = ESC_T;
break;
case '\r':
if( buffer.length() > 0 ) {
array.add((String)buffer.toString());
buffer = new StringBuilder();
}
break;
case '\n':
if( buffer.length() > 0 ) {
array.add((String)buffer.toString());
buffer = new StringBuilder();
}
break;
default:
buffer.append(c);
break;
}
idx++;
break;
case STRING:
switch (c) {
case '"':
state = TOKENS;
buffer.append(c);
break;
case '\\':
state = ESC_S;
break;
default:
buffer.append(c);
break;
}
idx++;
break;
case ESC_S:
buffer.append("\\" + c);
state = STRING;
idx++;
break;
case ESC_T:
buffer.append("\\" + c);
state = TOKENS;
idx++;
break;
case EXIT:
exit = true;
break;
}
}
tokensEQU = new String[array.size()];
for (int i = 0; i < array.size(); i++) {
tokensEQU[i] = array.get(i).toString();
}
return idx - 2;
}
/**
* Parses a document text and forms a tree structure using a stack.
* @param str Document text to be parsed.
*/
private void textParser(String str) {
docStack = new ObjectStack();
docStack.push(new DocumentNode());
char c;
StringBuilder buffer = new StringBuilder();
final int ESC = 1;
final int TEXT = 2;
final int TAG_OPEN = 3;
final int TAG_CLOSE = 4;
final int TAG = 5;
int state = TEXT;
int i = 0;
while ( i < str.length() ) {
c = str.charAt(i);
switch (state) {
case TEXT:
if (c == '\\') {
state = ESC;
} else if (c == '[') {
state = TAG_OPEN;
Node parentNode = (Node) docStack.peek();
if( buffer.length() > 0 ) {
TextNode textNode = new TextNode(buffer.toString());
parentNode.addChildNode(textNode);
buffer = new StringBuilder();
}
} else {
buffer.append(c);
}
break;
case ESC:
if(c == '[' || c == 'n' || c == 't' || c == 'r')
buffer.append('\\').append(c);
else throw new SmartScriptParserException();
state = TEXT;
break;
case TAG_OPEN:
if(c != '$') throw new SmartScriptParserException();
if(str.charAt(i+1) == ' ')
i++;
state = TAG;
break;
case TAG:
if( c == '=' ) {
i = getEQU(str, i + 1);
Node parentNode = (Node) docStack.peek();
Token[] echoTokens = new Token[tokensEQU.length];
for(int j = 0; j < tokensEQU.length; j++)
echoTokens[j] = tokenConvert(tokensEQU[j]);
EchoNode equNode = new EchoNode(echoTokens);
parentNode.addChildNode(equNode);
} else if(c == ' ' && buffer.length() > 0) {
if(buffer.toString().equals("FOR")) { // push FOR to stack
i = getFOR(str, i);
Node parentNode = (Node) docStack.peek();
ForLoopNode forNode = new ForLoopNode((TokenVariable)tokenConvert(tokensFOR[0]), tokenConvert(tokensFOR[1]), tokenConvert(tokensFOR[2]), tokenConvert(tokensFOR[3]));
parentNode.addChildNode(forNode);
docStack.push(forNode);
}
else if(buffer.toString().equals("=")) {
i = getEQU(str, i);
Node parentNode = (Node) docStack.peek();
Token[] echoTokens = new Token[tokensEQU.length];
for(int j = 0; j < tokensEQU.length; j++)
echoTokens[j] = tokenConvert(tokensEQU[j]);
EchoNode equNode = new EchoNode(echoTokens);
parentNode.addChildNode(equNode);
}
}
else if(c == '$') {
state = TAG_CLOSE;
if(buffer.toString().equals("END"))
docStack.pop(); // pop stack
}
else if(c != ' ')
buffer.append(c);
break;
case TAG_CLOSE:
if(c != ']') throw new SmartScriptParserException();
buffer = new StringBuilder();
state = TEXT;
break;
default:
break;
}
i++;
}
}
/**
* Looks for the item documentNode on the stack.
* @return Returns a documentNode if the stack is not empty.
*/
public DocumentNode getDocumentNode() {
DocumentNode docNode = null;
try {
docNode = (DocumentNode) docStack.peek();
} catch (ClassCastException e) {
throw new SmartScriptParserException("Syntax error!");
}
return docNode;
}
/**
* Converts strings to tokens.
* @param token String token to convert.
* @return Returned token.
*/
private Token tokenConvert(String token) {
Token tmp;
if (token == null) return null;
try{
int argInt = Integer.parseInt(token);
tmp = new TokenConstantInteger(argInt);
}
catch (NumberFormatException nFE0) {
try {
double argDouble = Double.parseDouble(token);
tmp = new TokenConstantDouble(argDouble);
} catch (NumberFormatException nFE1) {
char c = token.charAt(0);
if(c == '@') {
if( !isValidToken(token, 2) ) throw new SmartScriptParserException();
tmp = new TokenFunction(token);
}
else if(c == '*' || c == '/' || c == '+' || c == '-') {
if( !isValidToken(token, 3) ) throw new SmartScriptParserException();
tmp = new TokenOperator(token);
}
else if(c == '\"') {
tmp = new TokenString(token.substring(1, token.length()-1));
}
else {
if( !isValidToken(token, 0) ) throw new SmartScriptParserException();
tmp = new TokenVariable(token);
}
}
}
return tmp;
}
}