package com.laytonsmith.core.compiler;
import com.laytonsmith.core.ParseTree;
import com.laytonsmith.core.constructs.CBoolean;
import com.laytonsmith.core.constructs.CDouble;
import com.laytonsmith.core.constructs.CFunction;
import com.laytonsmith.core.constructs.CInt;
import com.laytonsmith.core.constructs.CKeyword;
import com.laytonsmith.core.constructs.CLabel;
import com.laytonsmith.core.constructs.CNull;
import com.laytonsmith.core.constructs.CString;
import com.laytonsmith.core.constructs.CSymbol;
import com.laytonsmith.core.constructs.Construct;
import com.laytonsmith.core.constructs.NewIVariable;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.constructs.Token;
import com.laytonsmith.core.constructs.Token.TType;
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.exceptions.ConfigCompileException;
import java.util.Arrays;
import java.util.EmptyStackException;
import java.util.List;
import java.util.Stack;
/**
*
*
*/
class CompilerObject {
TokenStream stream;
Stack<ParseTree> nodes = new Stack<ParseTree>();
int autoConcatCounter = 0;
int bracketCounter = 0;
Stack<Target> bracketLines = new Stack<Target>();
int braceCounter = 0;
Stack<Target> braceLines = new Stack<Target>();
Stack<Target> functionLines = new Stack<Target>();
ParseTree pointer;
ParseTree root;
CompilerEnvironment env;
CompilerObject(TokenStream stream) {
this.stream = stream;
}
Token peek() {
if (stream.isEmpty()) {
return new Token(TType.UNKNOWN, "", Target.UNKNOWN);
}
return stream.get(0);
}
Token consume() {
return stream.remove(0);
}
void compile(ParseTree root, Environment compilerEnv) throws ConfigCompileException {
this.root = root;
nodes.push(root);
pointer = root;
this.env = compilerEnv.getEnv(CompilerEnvironment.class);
while (!stream.isEmpty()) {
compile0();
}
if (bracketCounter > 0) {
throw new ConfigCompileException("Unclosed brackets. (Did you forget a right bracket (])?)", bracketLines.peek());
}
if (braceCounter > 0) {
throw new ConfigCompileException("Unclosed braces. (Did you forget a right brace (})?)", braceLines.peek());
}
if (!functionLines.isEmpty()) {
throw new ConfigCompileException("Unclosed left parenthesis. (Did you forget to close a function?)", functionLines.peek());
}
}
void compile0() throws ConfigCompileException {
Token t = consume();
if (t.type == TType.NEWLINE) {
return;
}
if (t.type == TType.CONST_START) {
StringBuilder constName = new StringBuilder();
while ((t = consume()).type != TType.RCURLY_BRACKET) {
if (t.type != TType.BARE_STRING && t.type != TType.CONCAT) {
throw new ConfigCompileException("Constant names may only contain names and dots.", t.getTarget());
}
constName.append(t.val());
}
Construct constant = env.getConstant(constName.toString());
if (constant == null) {
throw new ConfigCompileException("Expected the constant ${" + constName.toString() + "} to be provided in the compilation options, but it wasn't.", t.getTarget());
}
t = new Token(TType.STRING, constant.val(), constant.getTarget());
}
if (t.type == TType.BARE_STRING && peek().type == TType.FUNC_START) {
consume();
CFunction f = new CFunction(t.val(), t.getTarget());
functionLines.add(peek().getTarget());
pushNode(f);
return;
}
if (t.type == TType.FUNC_END || t.type == TType.COMMA) {
if (autoConcatCounter > 0) {
autoConcatCounter--;
popNode(t.getTarget());
}
}
if (t.type == TType.COMMA) {
return;
}
if (t.type == TType.FUNC_END) {
//We're done with this child, so push it up
popNode(t.getTarget());
functionLines.pop();
return;
}
if (t.type == TType.LSQUARE_BRACKET) {
CFunction f = new CFunction("__cbracket__", Target.UNKNOWN);
pushNode(f);
bracketCounter++;
bracketLines.push(t.getTarget());
return;
}
if (t.type == TType.RSQUARE_BRACKET) {
if (bracketCounter == 0) {
throw new ConfigCompileException("Unexpected right bracket. (Did you have too many right square brackets (]) in your code?)", t.getTarget());
}
bracketCounter--;
bracketLines.pop();
popNode(t.getTarget());
return;
}
if (t.type == TType.LCURLY_BRACKET) {
CFunction f = new CFunction("__cbrace__", Target.UNKNOWN);
pushNode(f);
braceCounter++;
braceLines.push(t.getTarget());
return;
}
if (t.type == TType.RCURLY_BRACKET) {
if (braceCounter == 0) {
throw new ConfigCompileException("Unexpected right brace. (Did you have too many right braces (}) in your code?)", t.getTarget());
}
braceCounter--;
braceLines.pop();
popNode(t.getTarget());
return;
}
//If the next token ISN'T a ) , } ] we need to autoconcat this
if (peek().type != TType.FUNC_END && peek().type != TType.COMMA && peek().type != TType.RCURLY_BRACKET && peek().type != TType.RSQUARE_BRACKET) {
//... unless we're already in an autoconcat
if (!(pointer.getData() instanceof CFunction && ((CFunction) pointer.getData()).val().equals("__autoconcat__"))) {
CFunction f = new CFunction("__autoconcat__", Target.UNKNOWN);
pushNode(f);
autoConcatCounter++;
}
}
if (t.type == TType.BARE_STRING && peek().type == TType.LABEL) {
consume();
pointer.addChild(new ParseTree(new CLabel(new CString(t.val(), t.getTarget())), stream.getFileOptions()));
return;
}
if (t.type.isIdentifier()) {
//If it's an atomic, put it in a construct and parse tree, then add it
pointer.addChild(new ParseTree(resolveIdentifier(t), stream.getFileOptions()));
return;
}
if (t.type.isSymbol()) {
pointer.addChild(new ParseTree(new CSymbol(t.val(), t.type, t.getTarget()), stream.getFileOptions()));
return;
}
//Now we have to check ahead for commas and other division parameters.
}
private void pushNode(CFunction node) {
ParseTree n = new ParseTree(node, stream.getFileOptions());
pointer.addChild(n);
nodes.push(n);
pointer = n;
}
private void popNode(Target t) throws ConfigCompileException {
try {
nodes.pop();
pointer = nodes.peek();
} catch (EmptyStackException e) {
throw new ConfigCompileException("Unmatched closing parenthesis. (Did you put too many right parenthesis?)", t);
}
}
private static List<String> keywords = Arrays.asList(new String[]{"else", "bind", "proc"});
private Construct resolveIdentifier(Token t) throws ConfigCompileException {
switch (t.type) {
case STRING:
return new CString(t.val(), t.getTarget());
// case SMART_STRING:
//
// case VARIABLE:
//
// case FINAL_VAR:
case IVARIABLE:
return new NewIVariable(t.val(), t.getTarget());
case BARE_STRING:
if (t.val().equals("true")) {
return CBoolean.GenerateCBoolean(true, t.getTarget());
} else if (t.val().equals("false")) {
return CBoolean.GenerateCBoolean(false, t.getTarget());
} else if (t.val().equals("null")) {
return CNull.GenerateCNull(t.getTarget());
} else if (keywords.contains(t.val())) {
return new CKeyword(t.val(), t.getTarget());
} else {
if (stream.fileOptions.isStrict()) {
throw new ConfigCompileException("Bare strings not allowed in strict mode. (" + t.val() + ")", t.getTarget());
} else {
return new CString(t.val(), t.getTarget());
}
}
case DOUBLE:
return new CDouble(t.val(), t.getTarget());
case INTEGER:
return new CInt(t.val(), t.getTarget());
default:
throw new ConfigCompileException("Unexpected identifier? Found '" + t.val() + "' but was not any expected value.", t.getTarget());
}
}
}