package com.laytonsmith.core; import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.TermColors; import com.laytonsmith.abstraction.Implementation; import com.laytonsmith.abstraction.MCCommandSender; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.StaticLayer; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CDouble; import com.laytonsmith.core.constructs.CEntry; import com.laytonsmith.core.constructs.CInt; import com.laytonsmith.core.constructs.CLabel; import com.laytonsmith.core.constructs.CNull; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CVoid; import com.laytonsmith.core.constructs.Command; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Construct.ConstructType; import com.laytonsmith.core.constructs.IVariable; 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.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; import com.laytonsmith.core.environments.InvalidEnvironmentException; import com.laytonsmith.core.exceptions.CRE.AbstractCREException; import com.laytonsmith.core.exceptions.CRE.CRECastException; import com.laytonsmith.core.exceptions.CRE.CREInsufficientPermissionException; import com.laytonsmith.core.exceptions.CRE.CREInvalidProcedureException; import com.laytonsmith.core.exceptions.CancelCommandException; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigCompileGroupException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.core.exceptions.FunctionReturnException; import com.laytonsmith.core.exceptions.LoopBreakException; import com.laytonsmith.core.exceptions.LoopContinueException; import com.laytonsmith.core.exceptions.ProgramFlowManipulationException; import com.laytonsmith.core.exceptions.StackTraceManager; import com.laytonsmith.core.extensions.Extension; import com.laytonsmith.core.extensions.ExtensionManager; import com.laytonsmith.core.extensions.ExtensionTracker; import com.laytonsmith.core.functions.Function; import com.laytonsmith.core.functions.FunctionBase; import com.laytonsmith.core.functions.FunctionList; import com.laytonsmith.core.profiler.ProfilePoint; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; /** * A script is a section of code that has been preprocessed and split into separate * commands/actions. For instance, the config script: * * /command = /cmd * * /test = /test * * would be two seperate scripts, the first being the /command, and the second being /test. * Certain key information is stored in the Script class. First, the information needed * to see if a target string should trigger this script. Secondly, the default values * of any variables, and thirdly, the unparsed tree for the right side of the script. */ public class Script { private List<Token> left; private List<List<Token>> right; private List<Token> fullRight; private List<Construct> cleft; private List<ParseTree> cright; private boolean nolog = false; //This should be null if we are running in non-alias mode private Map<String, Variable> left_vars; boolean hasBeenCompiled = false; boolean compilerError = false; private final long compileTime; private String label; private Environment CurrentEnv; @Override public String toString() { StringBuilder b = new StringBuilder(); for (Token t : left) { b.append(t.val()).append(" "); } b.append("; compiled: ").append(hasBeenCompiled).append("; errors? ").append(compilerError); return b.toString(); } private Procedure getProc(String name) { return CurrentEnv.getEnv(GlobalEnv.class).GetProcs().get(name); } public Environment getCurrentEnv(){ return CurrentEnv; } public String getLabel(){ return label; } /** * Returns what would normally be on the left side on an alias ie. in config.msa * @return label:/alias arg [ optionalArg ] */ public String getSignature() { StringBuilder b = new StringBuilder(); b.append(getLabel()).append(":"); for (Token t : left) { b.append(t.val()).append(" "); } return b.toString(); } public Script(List<Token> left, List<Token> right) { this.left = left; this.fullRight = right; this.left_vars = new HashMap<String, Variable>(); //this.OriginalEnv = env; compileTime = System.currentTimeMillis(); } private Script(){ compileTime = System.currentTimeMillis(); } public long getCompileTime(){ return compileTime; } public static Script GenerateScript(ParseTree tree, String label){ Script s = new Script(); s.hasBeenCompiled = true; s.compilerError = false; s.cright = new ArrayList<ParseTree>(); s.cright.add(tree); s.label = label; return s; } public boolean uncompilable() { return compilerError; } public void run(final List<Variable> vars, Environment myEnv, final MethodScriptComplete done) { //Some things, such as the label are determined at compile time this.CurrentEnv = myEnv; this.CurrentEnv.getEnv(GlobalEnv.class).SetLabel(this.label); MCCommandSender p = myEnv.getEnv(CommandHelperEnvironment.class).GetCommandSender(); if (!hasBeenCompiled || compilerError) { Target target = Target.UNKNOWN; if (left.size() >= 1) { try{ target = new Target(left.get(0).line_num, left.get(0).file, left.get(0).column); } catch(NullPointerException e){ //Oh well, we tried to get more information } } throw ConfigRuntimeException.CreateUncatchableException("Unable to run command, script not yet compiled, or a compiler error occured for that command." + " To see the compile error, run /reloadaliases", target); } if (p instanceof MCPlayer) { if (CurrentEnv.getEnv(GlobalEnv.class).GetLabel() != null) { String[] groups = CurrentEnv.getEnv(GlobalEnv.class).GetLabel().split("/"); for (String group : groups) { if (group.startsWith("-") && ((MCPlayer)p).inGroup(group.substring(1))) { //negative permission throw new CREInsufficientPermissionException("You do not have permission to use that command", Target.UNKNOWN); } else if (((MCPlayer)p).inGroup(group)) { //They do have permission. break; } } } } try { for (ParseTree rootNode : cright) { if(rootNode == null){ continue; } for (Construct tempNode : rootNode.getAllData()) { if (tempNode instanceof Variable) { if(left_vars == null){ ConfigRuntimeException.CreateUncatchableException("$variables may not be used in this context. Only @variables may be.", tempNode.getTarget()); } ((Variable) tempNode).setVal( new CString( Static.resolveDollarVar(left_vars.get(((Variable) tempNode).getVariableName()), vars).toString(), tempNode.getTarget())); } } MethodScriptCompiler.registerAutoIncludes(CurrentEnv, this); MethodScriptCompiler.execute(rootNode, CurrentEnv, done, this); } } catch (ConfigRuntimeException ex) { //We don't know how to handle this really, so let's pass it up the chain. throw ex; } catch (CancelCommandException e) { //p.sendMessage(e.getMessage()); //The message in the exception is actually empty } catch (LoopBreakException e) { if(p != null){ p.sendMessage("The break() function must be used inside a for() or foreach() loop"); } StreamUtils.GetSystemOut().println("The break() function must be used inside a for() or foreach() loop"); } catch (LoopContinueException e) { if(p != null){ p.sendMessage("The continue() function must be used inside a for() or foreach() loop"); } StreamUtils.GetSystemOut().println("The continue() function must be used inside a for() or foreach() loop"); } catch (FunctionReturnException e) { if(myEnv.getEnv(GlobalEnv.class).GetEvent() != null){ //Oh, we're running in an event handler. Those know how to catch it too. throw e; } if(p != null){ p.sendMessage("The return() function must be used inside a procedure."); } StreamUtils.GetSystemOut().println("The return() function must be used inside a procedure."); } catch (Throwable t) { StreamUtils.GetSystemOut().println("An unexpected exception occured during the execution of a script."); t.printStackTrace(); if(p != null){ p.sendMessage("An unexpected exception occured during the execution of your script. Please check the console for more information."); } } if (done != null) { done.done(null); } } /** * Runs eval on the code tree, and if it returns an ival, resolves it. * @param c * @param env * @return */ public Construct seval(ParseTree c, final Environment env){ Construct ret = eval(c, env); while(ret instanceof IVariable){ IVariable cur = (IVariable)ret; ret = env.getEnv(GlobalEnv.class).GetVarList().get(cur.getVariableName(), cur.getTarget()).ival(); } return ret; } /** * Given the parse tree and environment, executes the tree. * @param c * @param env * @return * @throws CancelCommandException */ public Construct eval(ParseTree c, final Environment env) throws CancelCommandException { if(env.getEnv(GlobalEnv.class).IsInterrupted()){ //First things first, if we're interrupted, kill the script //unconditionally. throw new CancelCommandException("", Target.UNKNOWN); } final Construct m = c.getData(); CurrentEnv = env; //TODO: Reevaluate if this line is needed. The script doesn't know the label inherently, the //environment does, and setting it this way taints the environment. CurrentEnv.getEnv(GlobalEnv.class).SetLabel(this.label); if (m.getCType() == ConstructType.FUNCTION) { StackTraceManager stManager = env.getEnv(GlobalEnv.class).GetStackTraceManager(); boolean addedRootStackElement = false; boolean caughtException = false; try { // If it's an unknown target, this is not user generated code, and we want to skip adding the element here. if(stManager.isStackEmpty() && !m.getTarget().equals(Target.UNKNOWN)){ stManager.addStackTraceElement(new ConfigRuntimeException.StackTraceElement("<<main code>>", m.getTarget())); addedRootStackElement = true; } stManager.setCurrentTarget(c.getTarget()); env.getEnv(GlobalEnv.class).SetScript(this); if (m.val().charAt(0) == '_' && m.val().charAt(1) != '_') { //Not really a function, so we can't put it in Function. Procedure p = getProc(m.val()); if (p == null) { throw new CREInvalidProcedureException("Unknown procedure \"" + m.val() + "\"", m.getTarget()); } Environment newEnv = env; try{ newEnv = env.clone(); } catch(CloneNotSupportedException e){} ProfilePoint pp = env.getEnv(GlobalEnv.class).GetProfiler().start(m.val() + " execution", LogLevel.INFO); Construct ret; try { ret = p.cexecute(c.getChildren(), newEnv, m.getTarget()); } finally { pp.stop(); } return ret; } final Function f; try{ f = (Function)FunctionList.getFunction(m); } catch(ConfigCompileException e){ //Turn it into a config runtime exception. This shouldn't ever happen though. throw ConfigRuntimeException.CreateUncatchableException("Unable to find function " + m.val(), m.getTarget()); } ArrayList<Construct> args = new ArrayList<Construct>(); try{ if (f.isRestricted()) { boolean perm = Static.hasCHPermission(f.getName(), env); if (!perm) { throw new CREInsufficientPermissionException("You do not have permission to use the " + f.getName() + " function.", m.getTarget()); } } if(f.useSpecialExec()){ ProfilePoint p = null; if(f.shouldProfile() && env.getEnv(GlobalEnv.class).GetProfiler() != null && env.getEnv(GlobalEnv.class).GetProfiler().isLoggable(f.profileAt())){ p = env.getEnv(GlobalEnv.class).GetProfiler().start(f.profileMessageS(c.getChildren()), f.profileAt()); } Construct ret; try { ret = f.execs(m.getTarget(), env, this, c.getChildren().toArray(new ParseTree[]{})); } finally { if(p != null){ p.stop(); } } return ret; } for (ParseTree c2 : c.getChildren()) { args.add(eval(c2, env)); } Object[] a = args.toArray(); Construct[] ca = new Construct[a.length]; for (int i = 0; i < a.length; i++) { ca[i] = (Construct) a[i]; //CArray, CBoolean, CDouble, CInt, CNull, CString, CVoid, CEntry, CLabel (only to sconcat). if (!(ca[i] instanceof CArray || ca[i] instanceof CBoolean || ca[i] instanceof CDouble || ca[i] instanceof CInt || ca[i] instanceof CNull || ca[i] instanceof CString || ca[i] instanceof CVoid || ca[i] instanceof IVariable || ca[i] instanceof CEntry || ca[i] instanceof CLabel) && (!f.getName().equals("__autoconcat__") && (ca[i] instanceof CLabel))) { throw new CRECastException("Invalid Construct (" + ca[i].getClass() + ") being passed as an argument to a function (" + f.getName() + ")", m.getTarget()); } while(f.preResolveVariables() && ca[i] instanceof IVariable){ IVariable cur = (IVariable)ca[i]; ca[i] = env.getEnv(GlobalEnv.class).GetVarList().get(cur.getVariableName(), cur.getTarget()).ival(); } } { //It takes a moment to generate the toString of some things, so lets not do it //if we actually aren't going to profile ProfilePoint p = null; if(f.shouldProfile() && env.getEnv(GlobalEnv.class).GetProfiler() != null && env.getEnv(GlobalEnv.class).GetProfiler().isLoggable(f.profileAt())){ p = env.getEnv(GlobalEnv.class).GetProfiler().start(f.profileMessage(ca), f.profileAt()); } Construct ret; try { ret = f.exec(m.getTarget(), env, ca); } finally { if(p != null){ p.stop(); } } return ret; } //We want to catch and rethrow the ones we know how to catch, and then //catch and report anything else. } catch(ConfigRuntimeException | ProgramFlowManipulationException e){ if(e instanceof AbstractCREException){ ((AbstractCREException)e).freezeStackTraceElements(stManager); caughtException = true; } throw e; } catch(InvalidEnvironmentException e){ if(!e.isDataSet()){ e.setData(f.getName()); } throw e; } catch(Exception e){ String version = "Unknown"; try{ version = Main.loadSelfVersion(); } catch(Exception ex){ //Ignored } String brand = Implementation.GetServerType().getBranding(); outer: for(ExtensionTracker tracker : ExtensionManager.getTrackers().values()){ for(FunctionBase b : tracker.getFunctions()){ if(b.getName().equals(f.getName())){ //This extension provided the function, so its the culprit. Report this //name instead of the core plugin's name. for(Extension extension : tracker.getExtensions()){ brand = extension.getName(); break outer; } } } } String emsg = TermColors.RED + "Uh oh! You've found an error in " + TermColors.CYAN + brand + TermColors.RED + ".\nThis is an error caused while running your code, so you may be able to find a workaround," + " but is ultimately an error in " + brand + " itself.\nThe line of code that caused the error was this:\n" + TermColors.WHITE; List<String> args2 = new ArrayList<>(); Map<String, String> vars = new HashMap<>(); for(Construct cc : args){ if(cc instanceof IVariable){ Construct ccc = env.getEnv(GlobalEnv.class).GetVarList().get(((IVariable)cc).getVariableName(), cc.getTarget()).ival(); String vval = ccc.val(); if(ccc instanceof CString){ vval = ccc.asString().getQuote(); } vars.put(((IVariable)cc).getVariableName(), vval); } if(cc == null){ args2.add("java-null"); } else if(cc instanceof CString){ args2.add(cc.asString().getQuote()); } else if(cc instanceof IVariable){ args2.add(((IVariable)cc).getVariableName()); } else { args2.add(cc.val()); } } //Server might not be available in this platform, so let's be sure to ignore those exceptions String modVersion = "Unsupported platform"; try{ modVersion = StaticLayer.GetConvertor().GetServer().getAPIVersion(); } catch(Exception ex){ modVersion = Implementation.GetServerType().name(); } if(!vars.isEmpty()){ emsg += StringUtils.Join(vars, " = ", "\n") + "\n"; } String extensionData = ""; for(ExtensionTracker tracker : ExtensionManager.getTrackers().values()){ for(Extension extension : tracker.getExtensions()){ try { extensionData += TermColors.CYAN + extension.getName() + TermColors.RED + " (version " + TermColors.RESET + extension.getVersion() + TermColors.RED + ");\n"; } catch(AbstractMethodError ex){ // This happens with an old style extensions. Just skip it. extensionData += TermColors.CYAN + "Unknown Extension" + TermColors.RED + " (unknown version);\n"; } } } if(extensionData.equals("")){ extensionData = "No extensions are loaded.\n"; } emsg += f.getName() + "("; emsg += StringUtils.Join(args2, ", "); emsg += ")\n" + TermColors.RED + "on or around " + TermColors.YELLOW + m.getTarget().file() + TermColors.WHITE + ":" + TermColors.CYAN + m.getTarget().line() + TermColors.RED + ".\nPlease report this error to the developers, and be sure to include the version numbers:\n" + TermColors.CYAN + "Server " + TermColors.RED + "version: " + TermColors.RESET + modVersion + TermColors.RED + ";\n" + TermColors.CYAN + Implementation.GetServerType().getBranding() + TermColors.RED + " version: " + TermColors.RESET + version + TermColors.RED + ";\n" + "Loaded extensions and versions:\n" + extensionData + "Here's the stacktrace:\n" + TermColors.RESET; emsg += Static.GetStacktraceString(e); Static.getLogger().log(Level.SEVERE, emsg); throw new CancelCommandException(null, Target.UNKNOWN); } } finally { if(addedRootStackElement && stManager.isStackSingle()){ stManager.popStackTraceElement(); } } } else if (m.getCType() == ConstructType.VARIABLE) { return new CString(m.val(), m.getTarget()); } else { return m; } } public boolean match(String command) { if(cleft == null){ //The compilation error happened during the signature declaration, so //we can't match it, nor can we even tell if it's what they intended for us to run. return false; } boolean case_sensitive = Prefs.CaseSensitive(); String[] cmds = command.split(" "); List<String> args = new ArrayList(Arrays.asList(cmds)); boolean isAMatch = true; StringBuilder lastVar = new StringBuilder(); int lastJ = 0; for (int j = 0; j < cleft.size(); j++) { if (!isAMatch) { break; } lastJ = j; Construct c = cleft.get(j); if(args.size() <= j) { if (c.getCType() != ConstructType.VARIABLE || !((Variable) c).isOptional()) { isAMatch = false; } break; } String arg = args.get(j); if (c.getCType() != ConstructType.VARIABLE) { if (case_sensitive && !c.val().equals(arg) || !case_sensitive && !c.val().equalsIgnoreCase(arg)) { isAMatch = false; continue; } } else { //It's a variable. If it's optional, the rest of them are optional too, so as long as the size of //args isn't greater than the size of cleft, it's a match if (((Variable) c).isOptional()) { if (args.size() <= cleft.size()) { return true; } else { Construct fin = cleft.get(cleft.size() - 1); if (fin instanceof Variable) { if (((Variable) fin).isFinal()) { return true; } } return false; } } } if (j == cleft.size() - 1) { if (cleft.get(j).getCType() == ConstructType.VARIABLE) { Variable lv = (Variable) cleft.get(j); if (lv.isFinal()) { for (int a = j; a < args.size(); a++) { if (lastVar.length() == 0) { lastVar.append(args.get(a)); } else { lastVar.append(" ").append(args.get(a)); } } } } } } boolean lastIsFinal = false; if (cleft.get(cleft.size() - 1) instanceof Variable) { Variable v = (Variable) cleft.get(cleft.size() - 1); if (v.isFinal()) { lastIsFinal = true; } } if ((cleft.get(lastJ) instanceof Variable && ((Variable) cleft.get(lastJ)).isOptional())) { return true; } if (cleft.size() != cmds.length && !lastIsFinal) { isAMatch = false; } return isAMatch; } public List<Variable> getVariables(String command) { String[] cmds = command.split(" "); List<String> args = new ArrayList(Arrays.asList(cmds)); StringBuilder lastVar = new StringBuilder(); ArrayList<Variable> vars = new ArrayList<Variable>(); Variable v = null; for (int j = 0; j < cleft.size(); j++) { try { if (cleft.get(j).getCType() == ConstructType.VARIABLE) { if (((Variable) cleft.get(j)).getVariableName().equals("$")) { for (int k = j; k < args.size(); k++) { lastVar.append(args.get(k).trim()).append(" "); } v = new Variable(((Variable) cleft.get(j)).getVariableName(), lastVar.toString().trim(), Target.UNKNOWN); } else { v = new Variable(((Variable) cleft.get(j)).getVariableName(), args.get(j), Target.UNKNOWN); } } } catch (IndexOutOfBoundsException e) { v = new Variable(((Variable) cleft.get(j)).getVariableName(), ((Variable) cleft.get(j)).getDefault(), Target.UNKNOWN); } if (v != null) { vars.add(v); } } return vars; } public Script compile() throws ConfigCompileException, ConfigCompileGroupException { try { verifyLeft(); compileLeft(); compileRight(); } catch (ConfigCompileException e) { compilerError = true; throw e; } compilerError = false; hasBeenCompiled = true; return this; } private boolean verifyLeft() throws ConfigCompileException { boolean inside_opt_var = false; boolean after_no_def_opt_var = false; String lastVar = null; //Go through our token list and readjust non-spaced symbols. Any time we combine a symbol, //the token becomes a string List<Token> tempLeft = new ArrayList<Token>(); for(int i = 0; i < left.size(); i++){ Token t = left.get(i); if(i == 0 && t.type == TType.NEWLINE){ continue; } if(t.type.isSymbol() && left.size() - 1 > i && left.get(i + 1).type != TType.WHITESPACE){ StringBuilder b = new StringBuilder(); b.append(t.value); i++; Token m = left.get(i); while(m.type.isSymbol() && m.type != TType.WHITESPACE){ b.append(m.value); i++; m = left.get(i); } if(m.type != TType.WHITESPACE && m.type != TType.LABEL){ b.append(m.value); } t = new Token(TType.STRING, b.toString(), t.target); if(m.type == TType.LABEL){ tempLeft.add(t); tempLeft.add(m); continue; } } //Go ahead and toString the other symbols too if(t.type.isSymbol()){ t = new Token(TType.STRING, t.value, t.target); } if(t.type != TType.WHITESPACE){ tempLeft.add(t); } } //Look through and concatenate all tokens before the label, if such exists. boolean hasLabel = false; for(int i = 0; i < tempLeft.size(); i++){ if(tempLeft.get(i).type == TType.LABEL){ hasLabel = true; break; } } if(hasLabel){ StringBuilder b = new StringBuilder(); int count = 0; while(tempLeft.get(count).type != TType.LABEL){ b.append(tempLeft.get(count).val()); count++; } tempLeft.set(0, new Token(TType.STRING, b.toString(), Target.UNKNOWN)); for(int i = 0; i < count - 1; i++){ tempLeft.remove(1); } } left = tempLeft; for (int j = 0; j < left.size(); j++) { Token t = left.get(j); //Token prev_token = j - 2 >= 0?c.tokens.get(j - 2):new Token(TType.UNKNOWN, "", t.line_num); Token last_token = j - 1 >= 0 ? left.get(j - 1) : new Token(TType.UNKNOWN, "", t.getTarget()); Token next_token = j + 1 < left.size() ? left.get(j + 1) : new Token(TType.UNKNOWN, "", t.getTarget()); Token after_token = j + 2 < left.size() ? left.get(j + 2) : new Token(TType.UNKNOWN, "", t.getTarget()); if (j == 0) { if (next_token.type == TType.LABEL) { this.label = t.val(); j--; left.remove(0); left.remove(0); continue; } } if (t.type == TType.LABEL) { continue; } if (t.type.equals(TType.FINAL_VAR) && left.size() - j >= 5) { throw new ConfigCompileException("FINAL_VAR must be the last argument in the alias", t.target); } if (t.type.equals(TType.VARIABLE) || t.type.equals(TType.FINAL_VAR)) { Variable v = new Variable(t.val(), null, t.target); lastVar = t.val(); v.setOptional(last_token.type.equals(TType.LSQUARE_BRACKET)); left_vars.put(t.val(), v); if (v.isOptional()) { after_no_def_opt_var = true; } else { v.setDefault(""); } } //We're looking for a command up front if (j == 0 && !t.value.startsWith("/")) { if (!(next_token.type == TType.LABEL && after_token.type == TType.COMMAND)) { throw new ConfigCompileException("Expected command (/command) at start of alias." + " Instead, found " + t.type + " (" + t.val() + ")", t.target); } } if (last_token.type.equals(TType.LSQUARE_BRACKET)) { inside_opt_var = true; if (!(t.type.equals(TType.FINAL_VAR) || t.type.equals(TType.VARIABLE))) { throw new ConfigCompileException("Unexpected " + t.type.toString() + " (" + t.val() + "), was expecting" + " a $variable", t.target); } } if (after_no_def_opt_var && !inside_opt_var) { if (t.type.equals(TType.VARIABLE) || t.type.equals(TType.FINAL_VAR)) { throw new ConfigCompileException("You cannot have anything other than optional arguments after your" + " first optional argument.", t.target); } } if (!t.type.equals(TType.LSQUARE_BRACKET) && !t.type.equals(TType.OPT_VAR_ASSIGN) && !t.type.equals(TType.RSQUARE_BRACKET) && !t.type.equals(TType.VARIABLE) && !t.type.equals(TType.LIT) && !t.type.equals(TType.COMMAND) && !t.type.equals(TType.FINAL_VAR)) { if (j - 1 > 0 && !(/*t.type.equals(TType.STRING) &&*/ left.get(j - 1).type.equals(TType.OPT_VAR_ASSIGN))) { throw new ConfigCompileException("Unexpected " + t.type + " (" + t.val() + ")", t.target); } } if (last_token.type.equals(TType.COMMAND)) { if (!(t.type.equals(TType.VARIABLE) || t.type.equals(TType.LSQUARE_BRACKET) || t.type.equals(TType.FINAL_VAR) || t.type.equals(TType.LIT) || t.type.equals(TType.STRING))) { throw new ConfigCompileException("Unexpected " + t.type + " (" + t.val() + ") after command", t.target); } } if (inside_opt_var && t.type.equals(TType.OPT_VAR_ASSIGN)) { if (!(next_token.type.isAtomicLit() && after_token.type.equals(TType.RSQUARE_BRACKET) || (next_token.type.equals(TType.RSQUARE_BRACKET)))) { throw new ConfigCompileException("Unexpected token in optional variable", t.target); } else if (next_token.type.isAtomicLit()) { left_vars.get(lastVar).setDefault(next_token.val()); } } if (t.type.equals(TType.RSQUARE_BRACKET)) { if (!inside_opt_var) { throw new ConfigCompileException("Unexpected " + t.type.toString(), t.target); } inside_opt_var = false; } } return true; } private boolean compileLeft() { cleft = new ArrayList<Construct>(); if(label != null && label.startsWith("!")){ if(label.length() > 1){ label = label.substring(1); } nolog = true; } for (int i = 0; i < left.size(); i++) { Token t = left.get(i); if (t.value.startsWith("/")) { cleft.add(new Command(t.val(), t.target)); } else if (t.type == Token.TType.VARIABLE) { cleft.add(new Variable(t.val(), null, t.target)); } else if (t.type.equals(TType.FINAL_VAR)) { Variable v = new Variable(t.val(), null, t.target); v.setFinal(true); cleft.add(v); } else if (t.type.equals(TType.LSQUARE_BRACKET)) { if (i + 2 < left.size() && left.get(i + 2).type.equals(TType.OPT_VAR_ASSIGN)) { Variable v = new Variable(left.get(i + 1).val(), left.get(i + 3).val(), t.target); v.setOptional(true); if (left.get(i + 1).type.equals(TType.FINAL_VAR)) { v.setFinal(true); } cleft.add(v); i += 4; } else { t = left.get(i + 1); Variable v = new Variable(t.val(), null, t.target); v.setOptional(true); if (t.val().equals("$")) { v.setFinal(true); } cleft.add(v); i += 2; } } else { cleft.add(new CString(t.val(), t.getTarget())); } } return true; } public void compileRight() throws ConfigCompileException, ConfigCompileGroupException { List<Token> temp = new ArrayList<Token>(); right = new ArrayList<List<Token>>(); for (Token t : fullRight) { if (t.type == TType.SEPERATOR) { right.add(temp); temp = new ArrayList<Token>(); } else { if(t.type == TType.WHITESPACE){ continue; //Whitespace is ignored on the right side } temp.add(t); } } right.add(temp); cright = new ArrayList<ParseTree>(); for (List<Token> l : right) { cright.add(MethodScriptCompiler.compile(l)); } } public void checkAmbiguous(List<Script> scripts) throws ConfigCompileException { //for (int i = 0; i < scripts.size(); i++) { List<Construct> thisCommand = this.cleft; for (int j = 0; j < scripts.size(); j++) { List<Construct> thatCommand = scripts.get(j).cleft; if (thatCommand == null) { //it hasn't been compiled yet. return; } if (this.cleft == scripts.get(j).cleft) { //Of course this command is going to match it's own signature continue; } boolean soFarAMatch = true; for (int k = 0; k < thisCommand.size(); k++) { try { Construct c1 = thisCommand.get(k); Construct c2 = thatCommand.get(k); if (c1.getCType() != c2.getCType() || ((c1 instanceof Variable) && !((Variable) c1).isOptional())) { soFarAMatch = false; } else { //It's a literal, check to see if it's the same literal if (c1.nval() == null || !c1.val().equals(c2.val())) { soFarAMatch = false; } } } catch (IndexOutOfBoundsException e) { /** * The two commands: * /cmd $var1 [$var2] * /cmd $var1 * would cause this exception to be thrown, but the signatures * are the same, so the fact that they've matched this far means * they are ambiguous. However, * /cmd $var1 $var2 * /cmd $var1 * is not ambiguous */ //thatCommand is the short one if (!(thisCommand.get(k) instanceof Variable) || (thisCommand.get(k) instanceof Variable && !((Variable) thisCommand.get(k)).isOptional())) { soFarAMatch = false; } } } if (thatCommand.size() > thisCommand.size()) { int k = thisCommand.size(); //thisCommand is the short one if (!(thatCommand.get(k) instanceof Variable) || (thatCommand.get(k) instanceof Variable && !((Variable) thatCommand.get(k)).isOptional())) { soFarAMatch = false; } } if (soFarAMatch) { String commandThis = ""; for (Construct c : thisCommand) { commandThis += c.val() + " "; } String commandThat = ""; for (Construct c : thatCommand) { commandThat += c.val() + " "; } scripts.get(j).compilerError = true; this.compilerError = true; throw new ConfigCompileException("The command " + commandThis.trim() + " is ambiguous because it " + "matches the signature of " + commandThat.trim() + " defined at " + thatCommand.get(0).getTarget(), thisCommand.get(0).getTarget()); } } } /** * This is only used by scriptas to hack the label in and out. * @param label */ public void setLabel(String label) { this.label = label; } public boolean doLog(){ return !nolog; } }