package com.laytonsmith.core.compiler; import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.constructs.CBareString; import com.laytonsmith.core.constructs.CDouble; import com.laytonsmith.core.constructs.CFunction; import com.laytonsmith.core.constructs.CInt; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.constructs.Token; import com.laytonsmith.core.constructs.Token.TType; import com.laytonsmith.core.constructs.Variable; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.exceptions.ConfigCompileException; import java.io.File; import java.util.ArrayList; import java.util.List; /** * * */ public class NewMethodScriptCompiler { /** * Takes a raw string input, and parses it into a TokenStream, which can be * passed to either the preprocessor, or the compiler. * * @param script * @param file * @param startInPureMscript * @return * @throws ConfigCompileException */ public static TokenStream lex(String script, File file, boolean startInPureMscript) throws ConfigCompileException { script = script.replaceAll("\r\n", "\n"); script = script + "\n"; LexerObject lo = new LexerObject(script, file, startInPureMscript); return lo.lex(); } public static List<NewScript> preprocess(TokenStream tokenStream, Environment compilerEnvironment) throws ConfigCompileException { List<NewScript> scripts = new ArrayList<NewScript>(); //We need to split the command definition and the pure mscript parts. First, //we split on newlines, those are each going to be our alias definitions List<List<Token>> commands = new ArrayList<List<Token>>(); List<Token> working = new ArrayList<Token>(); for (int i = 0; i < tokenStream.size(); i++) { Token t = tokenStream.get(i); if (t.type == Token.TType.NEWLINE) { commands.add(working); working = new ArrayList<Token>(); continue; } working.add(t); } //Now they are split into individual aliases for (List<Token> stream : commands) { //We need to make constructs from the left, and compile the right //Compiling the right can be simply passed off to the compile //function, but we need to parse the left ourselves //We *should* only have (bare) strings, numbers, brackets on the left List<Token> left = new ArrayList<Token>(); TokenStream right = new TokenStream(new ArrayList<Token>(), tokenStream.fileOptions); boolean inLeft = true; boolean hasLabel = false; for (Token t : stream) { if (t.type == Token.TType.ALIAS_END) { inLeft = false; continue; } if (t.type == TType.LABEL) { hasLabel = true; } if (inLeft) { left.add(t); } else { right.add(t); } } ParseTree cright = compile(right, compilerEnvironment); List<Construct> cleft = new ArrayList<Construct>(); boolean atFinalVar = false; boolean atOptionalVars = false; boolean pastLabel = false; String label = ""; try { for (int i = 0; i < left.size(); i++) { Token t = left.get(i); if (hasLabel && !pastLabel) { if (t.type == TType.LABEL) { pastLabel = true; continue; } label += t.val(); continue; } if (atFinalVar) { throw new ConfigCompileException("The final var must be the last declaration in the alias", t.getTarget()); } if (t.type == TType.LSQUARE_BRACKET) { Token tname = left.get(i + 1); atOptionalVars = true; if (tname.val().equals("$")) { atFinalVar = true; } if (tname.type != TType.VARIABLE && tname.type != TType.FINAL_VAR) { throw new ConfigCompileException("Expecting a variable, but found " + tname.val(), tname.getTarget()); } i++; Token next = left.get(i + 1); if (next.type != TType.OPT_VAR_ASSIGN && next.type != TType.RSQUARE_BRACKET) { throw new ConfigCompileException("Expecting either a variable assignment or right square bracket, but found " + next.val(), next.getTarget()); } i++; String defaultVal = ""; if (next.type == TType.OPT_VAR_ASSIGN) { //We have an assignment here Token val = left.get(i + 1); i++; defaultVal = val.val(); next = left.get(i + 1); } if (next.type != TType.RSQUARE_BRACKET) { throw new ConfigCompileException("Expecting a right square bracket, but found " + next.val() + " instead. (Did you forget to quote a multi word string?)", next.getTarget()); } i++; Variable v = new Variable(tname.val(), defaultVal, true, (tname.val().equals("$")), tname.getTarget()); cleft.add(v); continue; } if (t.type == TType.VARIABLE || t.type == TType.FINAL_VAR) { //Required variable if (atOptionalVars) { throw new ConfigCompileException("Only optional variables may come after the first optional variable", t.getTarget()); } if (t.val().equals("$")) { atFinalVar = true; } Variable v = new Variable(t.val(), "", false, t.val().equals("$"), t.getTarget()); cleft.add(v); continue; } cleft.add(tokenToConstruct(t)); } } catch (IndexOutOfBoundsException e) { throw new ConfigCompileException("Expecting more tokens, but reached end of alias signature before tokens were resolved.", left.get(0).getTarget()); } if (!cleft.isEmpty()) { link(cright, compilerEnvironment); scripts.add(new NewScript(cleft, cright, label)); } } return scripts; } /** * TODO: Need a platform resolver here? * @param tokenStream * @param compilerEnvironment * @return * @throws ConfigCompileException */ public static ParseTree compile(TokenStream tokenStream, Environment compilerEnvironment) throws ConfigCompileException { ParseTree root = new ParseTree(new CFunction("__autoconcat__", Target.UNKNOWN), tokenStream.getFileOptions()); new CompilerObject(tokenStream).compile(root, compilerEnvironment); link(root, compilerEnvironment); return root; } private static void link(ParseTree root, Environment compilerEnvirontment) throws ConfigCompileException { //Before we actually link, we need to optimize our branch functions, that is, //currently just if. However, at this point, we also need to optimize __autoconcat__. //so we know what the tree actually looks like. Also, we want to first group all our auto includes //together, along with our actual tree. ParseTree master = new ParseTree(new CFunction("__autoconcat__", Target.UNKNOWN), root.getFileOptions()); for (ParseTree include : compilerEnvirontment.getEnv(CompilerEnvironment.class).getIncludes()) { master.addChild(include); } master.addChild(root); OptimizerObject optimizer = new OptimizerObject(root, compilerEnvirontment); optimizer.optimize(); //root is now optimized } private static Construct tokenToConstruct(Token t) { if (t.type == Token.TType.STRING) { return new CString(t.val(), t.getTarget()); } if (t.type == Token.TType.BARE_STRING) { return new CBareString(t.val(), t.getTarget()); } if (t.type == Token.TType.INTEGER) { return new CInt(Long.parseLong(t.val()), t.getTarget()); } if (t.type == Token.TType.DOUBLE) { return new CDouble(Double.parseDouble(t.val()), t.getTarget()); } return null; } public static void main(String[] args) throws ConfigCompileException { CompilerEnvironment env = new CompilerEnvironment(); env.setConstant("v.n", "value"); TokenStream stream = lex("<! strict; > @var", null, true); StreamUtils.GetSystemOut().println(stream + "\n"); // StreamUtils.GetSystemOut().println(preprocess(stream).toString()); ParseTree tree = compile(stream, Environment.createEnvironment(env)); StreamUtils.GetSystemOut().println(tree.toStringVerbose()); } }