// this combine method is for single-level macros, where another level already exists public void CombineFree(ScriptMacro ma) { // loop for all for (int i = 0; i < 0x100; i++) { if (Optimized[i] == null) { // nothing here Optimized[i] = ma; } // if not free, put nothing there } }
// this combine method is for multi-level macros public GenericScriptItem[] Combine(ScriptMacro macro, int depth) { // if null, we dont even need to bother if (macro == null) { return(Optimized); } // if too deep, just return normally // combine this layer int RangeStart, RangeEnd; if (!macro.GetRange(depth, out RangeStart, out RangeEnd)) { // if too deep, just return normally return(Optimized); } // loop for all for (int i = RangeStart; i <= RangeEnd; i++) { if (Optimized[i] == null) { // nothing here if (depth < macro.pre.Length - 1) { Optimized[i] = new ScriptArrayItem(parent); (Optimized[i] as ScriptArrayItem).Combine(macro, depth + 1); } else { // if last level, just put macro in there Optimized[i] = macro; } } else if (Optimized[i].type == ScriptItemType.ArrayItem) { // if another array, see if we can insert us there (Optimized[i] as ScriptArrayItem).Combine(macro, depth + 1); } } // and finally at the end of the day, we can rest assured everything is sorted. Phew. return(Optimized); }
// function to calculate the optimized arrays of items or array items to quickly return items for certain types of requests public GenericScriptItem[] Optimize() { Optimized = new GenericScriptItem[0x100]; foreach (GenericScriptItem entry in Items) { switch (entry.type) { case ScriptItemType.NULL: screrr(entry.line, "Type of item is NULL! This is most likely a programming error in SMPS2ASM!"); break; case ScriptItemType.Equate: ScriptEquate eq = (entry as ScriptEquate); // only pre-calculated equates are possible to be used if (!eq.CheckEvaluate()) { S2AScript.screrr(entry.line, "Equates that are being optimized into a look-up-table must be possible to be pre-calculated! Equate with contents '" + eq.val + "' failed to be pre-calculated."); } // get offset int v; if (!Parse.DoubleToInt(GetEquate(eq.equ).value, out v)) { screrr(entry.line, "Equate value can not be accurately converted from double floating point to int! Equate with contents '" + eq.val + "' failed to be conveted to 32-bit signed integer."); } // save entry or throw error. if (Optimized[v] == null) { Optimized[v] = entry; } else { screrr(entry.line, "Entity " + entry.identifier + " conflicts with " + Optimized[v].identifier + " at line " + Optimized[v].line + ", both trying to occupy the value " + v + " (0x" + v.ToString("X2") + ")! Optimization requires no such conflicts."); } break; case ScriptItemType.Macro: ScriptMacro ma = (entry as ScriptMacro); // collect range int rangeStart, rangeEnd; if (!ma.GetRange(0, out rangeStart, out rangeEnd)) { S2AScript.screrr(entry.line, "Unable to parse first level macro range. Macro range of '" + (ma.pre.Length > 0 ? ma.pre[0] : "") + "' is not valid."); } // if true, there is only 1 level to this macro bool onlylevel = ma.pre.Length == 1; for (int i = rangeStart; i <= rangeEnd; i++) { if (onlylevel) { if (Optimized[i] == null) { Optimized[i] = ma; } else if (Optimized[i].type == ScriptItemType.ArrayItem) { (Optimized[i] as ScriptArrayItem).CombineFree(ma); } else { screrr(entry.line, "Entity " + entry.identifier + " conflicts with " + Optimized[i].identifier + " at line " + Optimized[i].line + ", both trying to occupy the value " + i + " (0x" + i.ToString("X2") + ")! Optimization requires no such conflicts."); } } else { if (Optimized[i] == null) { Optimized[i] = new ScriptArrayItem(parent); (Optimized[i] as ScriptArrayItem).Combine(ma, 1); } else if (Optimized[i].type == ScriptItemType.ArrayItem) { (Optimized[i] as ScriptArrayItem).Combine(ma, 1); } else { screrr(entry.line, "Entity " + entry.identifier + " conflicts with " + Optimized[i].identifier + " at line " + Optimized[i].line + ", both trying to occupy the value " + i + " (0x" + i.ToString("X2") + ")! Optimization requires no such conflicts."); } } } break; case ScriptItemType.ArrayItem: screrr(entry.line, "Unoptimized list contains a pre-occupied technical element that may not be interpreted. This is likely a programming error, please report to devs!"); break; case ScriptItemType.Import: ScriptArray sc = context.GetSubscript((entry as ScriptImport).name); if (sc.Optimized == null) { sc.Optimize(); } Optimized = ConvertSMPS.context.Combine(new GenericScriptItem[][] { Optimized, sc.Optimized }); break; // all these items are invalid inside the LUT. default: screrr(entry.line, "Optimized look-up-table may only contain unoptimizable elements! Look-up-tables may contain either Equates, or macros."); break; } } return(Optimized); }
private void ParseScript(string data, string[] args, int lnoffs, ref Stack <ScriptArray> stack) { // chars to trim from strings char[] trim = new char[] { ' ', '\t' }; // some variables to help with stuff Stack <ScriptCondition> co = new Stack <ScriptCondition>(); if (debug) { Debug(new string('-', 80)); } // use line num to accurately report issues uint lnum = (uint)lnoffs, tabs = 0; foreach (string ln in data.Replace("\r", "").Split('\n')) { lnum++; // trim whitespaces and tabs from start. Also C# is gay sometimes string line = ln.Trim(trim); // ignore empty lines if (line.Length > 0) { try { switch (line.ElementAt(0)) { case '#': //ignore comments break; case '}': tabs--; // we need 2 entries to remove one of them, to have one entry still in stack if (stack.Count < 2) { screrr(lnum, "Stack is empty. Maybe there is an extra script block end?"); } // } { -> else if (line.EndsWith("{")) { // if co not set, its a problem if (co.Count <= 0) { screrr(lnum, "Else called when not in condition block"); } // if already in false block, error if (stack.Peek() == co.Peek().False) { screrr(lnum, "Else called when already inside else block"); } // pop last entry and push false block in stack.Pop(); stack.Push(co.Peek().False); if (debug) { Debug(lnum, tabs++, "}{"); } } else { // pop from co stack (NEEDS TO BE FIRST FUU) if (co.Count > 0 && (co.Peek().True == stack.Peek() || co.Peek().False == stack.Peek())) { co.Pop(); } stack.Pop(); if (debug) { Debug(lnum, tabs, "}"); } } break; case '?': if (line.Length < 3) { // if line is too short, it is a major problem screrr(lnum, "Name not specified."); } string name = line.Substring(1, line.Length - 2).Trim(trim); if (name.Length <= 0) { // no usable name screrr(lnum, "Name not specified."); } if (line.EndsWith("{")) { // declaring subscript ScriptArray f = new ScriptArray(stack.Peek()); subscripts.Add(name, f); stack.Push(f); if (debug) { Debug(lnum, tabs++, '?' + name + " {"); } } else if (line.EndsWith(";")) { // subscript importing stack.Peek().Add(new ScriptImport(lnum, stack.Peek(), name)); if (debug) { Debug(lnum, tabs, '?' + name + ';'); } } else { // if line doesnt end with ; or {, there is an issue screrr(lnum, "invalid end token."); } break; case '/': // get the label only if there is a space char bool singlemode = line.Length > 1 && line.ElementAt(1) == '/'; line = line.Substring(singlemode ? 2 : 1); string lbl = ""; int ind; if ((ind = line.IndexOf(' ')) != -1) { lbl = line.Substring(0, ind); line = line.Substring(ind + 1); } // check if rest of the info exists if (line.Length < 2) { screrr(lnum, "Illegal line"); } List <bool> types = new List <bool>(); List <string> names = new List <string>(); while (line.Length > 0) { // get the caller type types.Add(line.StartsWith("?")); if (!types.Last() && !line.StartsWith(">")) { screrr(lnum, "Illegal type '" + line.ElementAt(0) + "'"); } // get the offset of the last char int last = line.IndexOf(' ') + 1; if (last == 0) { last = line.Length; } // add the string in and remove from line names.Add(line.Substring(1, last - 1).Trim(trim)); line = line.Substring(last); } stack.Peek().Add(new ScriptExecute(lnum, stack.Peek(), lbl, types.ToArray(), names.ToArray(), singlemode)); // write debug info if (debug) { string db = '/' + lbl; for (int i = 0; i < types.Count; i++) { db += ' ' + (types[i] ? "?" : ">") + names[i]; } Debug(lnum, tabs, db); } break; case '=': int indx = line.IndexOf(' '); if (indx == -1) { screrr(lnum, "Expected a whitespace (' '), but found none."); } // just some sanity checks, dont worry string equ = line.Substring(1, indx - 1), val = line.Substring(indx + 1); if (equ.Length < 1) { screrr(lnum, "Equate name not specified."); } if (val.Length < 1) { screrr(lnum, "Equate value not specified."); } ScriptEquate scre = new ScriptEquate(lnum, stack.Peek(), equ, val); stack.Peek().Add(scre); if (debug) { string n = scre.GetName(); Equate e = GetEquate(n); Debug(lnum, tabs, '=' + n + ' ' + val + (e.calculated ? " " + e.value : "")); } break; case '!': // skip ! and any spaces line = line.Substring(1).TrimStart(trim); // loop til all arguments are found int index; List <string> pre = new List <string>(); while (!line.StartsWith(">")) { // find the next , index = line.IndexOf(','); // if > is earlier than , (because arguments also can have them), then use > instead if (line.IndexOf('>') < index || index < 1) { index = line.IndexOf('>'); } if (index < 1) { screrr(lnum, "Invalid or nonexistent trigger byte at macro block."); } // create new argument and pre.Add(line.Substring(0, index).Trim(trim)); line = line.Substring(index); // if starts with , then remove it if (line.StartsWith(",")) { line = line.Substring(1); } } // get the name index = line.IndexOf(':'); if (index == -1) { screrr(lnum, "Expecting semicolon (':')"); } string nam = line.Substring(1, index - 1).Trim(trim); line = line.Substring(index + 1).TrimStart(trim); if (nam.Length < 1) { screrr(lnum, "Invalid name"); } // get the arguments List <string> arg = new List <string>(); while (!line.StartsWith(";") && !line.StartsWith("{")) { // find the next , index = line.IndexOf(','); // if failed, then try to find the end icon if (index < 1) { index = line.IndexOf(';'); } if (index < 1) { index = line.IndexOf('{'); } if (index < 1) { screrr(lnum, "Invalid or nonexistent argument at macro block."); } // create new argument and arg.Add(line.Substring(0, index).Trim(trim)); line = line.Substring(index); // if starts with , then remove it if (line.StartsWith(",")) { line = line.Substring(1); } } // create a new macro ScriptMacro s = new ScriptMacro(lnum, stack.Peek(), nam, pre.ToArray(), arg.ToArray()); stack.Peek().Add(s); // if ends with this, put shit in inner block if (line.EndsWith("{")) { stack.Push(s.Inner); } // write debug shit if (debug) { Debug(lnum, tabs, '!' + string.Join(", ", pre) + " > " + nam + ": " + string.Join(", ", arg) + line.ElementAt(line.Length - 1)); if (line.EndsWith("{")) { tabs++; } } break; case '@': // check if there is a space int indie = line.IndexOf(' '); if (indie == -1) { screrr(lnum, "Expected whitespace (' '), but found none."); } // get the equate name and remove from line string eqq = line.Substring(1, indie - 1); line = line.Substring(indie + 1).TrimStart(trim); // check for next space indie = line.IndexOf(' '); if (indie == -1) { screrr(lnum, "Expected whitespace (' '), but found none."); } // get the arg number int argnum; if (!Int32.TryParse(line.Substring(0, indie), out argnum)) { screrr(lnum, "Line '" + line.Substring(0, indie) + "' can not be parsed as a number!"); } string da; // check if there are arguments at this offset if (argnum >= args.Length) { // print out the string indie = line.IndexOf("\"") + 1; if (indie == 0) { screrr(lnum, "Expected string, but found none."); } Console.Write(line.Substring(indie, line.LastIndexOf("\"") - indie) + ": "); da = ConsoleArguments.Get(args, new ArgHandler[] { new ArgHandler(line.Substring(indie, line.LastIndexOf("\"") - indie) + ":", (_data, ret) => ret ? _data : null), }, new ButtonHandler[] { })[0]; if (debug) { Debug(lnum, tabs, "@? " + eqq + ' ' + argnum + ' ' + (indie > 0 ? line.Substring(indie, line.LastIndexOf("\"") - indie) : "INVALID") + ' ' + da); } } else { // read argument da = args[argnum]; if (debug) { indie = line.IndexOf("\"") + 1; Debug(lnum, tabs, "@ " + eqq + ' ' + argnum + ' ' + (indie > 0 ? line.Substring(indie, line.LastIndexOf("\"") - indie) : "INVALID") + ' ' + da); } } stack.Peek().Add(new ScriptEquate(lnum, stack.Peek(), eqq, da)); break; case 'c': // ehhhhhhh =/ if (!line.EndsWith("{")) { screrr(lnum, "No opening script block."); } // create the condition block, and push true block in stack co.Push(new ScriptCondition(lnum, stack.Peek(), line.Substring(1, line.Length - 2).Trim(trim))); stack.Peek().Add(co.Peek()); stack.Push(co.Peek().True); if (debug) { Debug(lnum, tabs++, "c " + line.Substring(1, line.Length - 2).Trim(trim) + " {"); } break; case 'f': // check for { at the end of line int inde = line.IndexOf('{'); if (inde == -1 || !line.EndsWith("{")) { screrr(lnum, "Expected block start ('{') at end of line, but found none."); } string counter = line.Substring(1, inde - 1).Trim(trim); ScriptRepeat r = new ScriptRepeat(lnum, stack.Peek(), counter); stack.Peek().Add(r); stack.Push(r.Inner); if (debug) { Debug(lnum, tabs++, "f " + counter + " {"); } break; case 'w': // check for { at the end of line int ine = line.IndexOf('{'); if (ine == -1 || !line.EndsWith("{")) { screrr(lnum, "Expected block start ('{') at end of line, but found none."); } string cond = line.Substring(1, ine - 1).Trim(trim); ScriptWhile w = new ScriptWhile(lnum, stack.Peek(), cond); stack.Peek().Add(w); stack.Push(w.Inner); if (debug) { Debug(lnum, tabs++, "w " + cond + " {"); } break; case ':': if (line.Length < 3) { screrr(lnum, "Unexpected end of line!"); } switch (line[1]) { case '?': { // check for { at the end of line int inxd = line.IndexOf('{'); if (inxd == -1) { screrr(lnum, "Expected block start ('{') at end of line, but found none."); } string num = line.Substring(2, inxd - 2).Trim(trim); try { ScriptArgMod m = new ScriptArgMod(lnum, stack.Peek(), num); stack.Peek().Add(m); stack.Push(m.Inner); if (debug) { Debug(lnum, tabs++, ":? " + m.num + " {"); } } catch (Exception) { screrr(lnum, "Failed to parse '" + num + "'!"); } } break; case '-': { string num = line.Substring(2, line.Length - 2).Trim(trim); try { ScriptArgRmv m = new ScriptArgRmv(lnum, stack.Peek(), num); stack.Peek().Add(m); if (debug) { Debug(lnum, tabs++, ":- " + m.num); } } catch (Exception) { screrr(lnum, "Failed to parse '" + num + "'!"); } } break; case '=': { int inxd = line.IndexOf(' '); if (inxd == -1) { screrr(lnum, "Expected space separator (' ') in the middle of the line, but found none."); } string num = line.Substring(2, inxd - 2).Trim(trim); string oper = line.Substring(inxd, line.Length - inxd).Trim(trim); try { ScriptArgEqu m = new ScriptArgEqu(lnum, stack.Peek(), num, oper); stack.Peek().Add(m); if (debug) { Debug(lnum, tabs++, ":= " + m.num + " " + m.operation); } } catch (Exception) { screrr(lnum, "Failed to parse '" + num + "'!"); } } break; default: screrr(lnum, "Unrecognized argument modifier type '" + line[1] + "'!"); break; } break; case '~': int indix = line.IndexOf(' '); if (indix == -1) { screrr(lnum, "Expected a whitespace (' '), but found none."); } // just some sanity checks, dont worry string labl = line.Substring(1, indix - 1), type = line.Substring(indix + 1); if (labl.Length < 1) { screrr(lnum, "Lable name not specified."); } if (type.Length < 1) { screrr(lnum, "Lable type or command not specified."); } if (type.StartsWith(":")) { // lable mod LableMod l = new LableMod(lnum, stack.Peek(), labl, type.Substring(1)); stack.Peek().Add(l); if (debug) { Debug(lnum, tabs, '~' + labl + " :" + l.num); } } else // lable create { stack.Peek().Add(new LableCreate(lnum, stack.Peek(), labl.Trim(trim), type)); if (debug) { Debug(lnum, tabs, '~' + labl + ' ' + type); } } break; case '$': stack.Peek().Add(new ScriptOperation(lnum, stack.Peek(), line = line.Substring(1).Trim(trim))); if (debug) { Debug(lnum, tabs, '$' + line); } break; case '>': // if line not long enough, problem if (line.Length < 3) { screrr(lnum, "Expected type and offset!"); } // check if valid type char ttype = line.ElementAt(1); if (ttype != 'a' && ttype != 'b' && ttype != 'f') { screrr(lnum, "Goto type '" + ttype + "' not recognized!"); } // arg1 = type (char), arg2 = rest of the line stack.Peek().Add(new ScriptGoto(lnum, stack.Peek(), ttype, line = line.Substring(2).Trim(trim))); if (debug) { Debug(lnum, tabs, ">" + ttype + " " + line); } break; case ';': stack.Peek().Add(new ScriptStop(lnum, stack.Peek())); if (debug) { Debug(lnum, tabs, ";"); } break; case '%': stack.Peek().Add(new ScriptComment(lnum, stack.Peek(), line.Substring(1))); if (debug) { Debug(lnum, tabs, line); } break; case '+': stack.Peek().Add(new ScriptPrint(lnum, stack.Peek(), line.Substring(1))); if (debug) { Debug(lnum, tabs, line); } break; case 's': { int idx = line.IndexOf(' '); string mac = line.Substring(1, idx >= 0 ? idx - 1 : line.Length - 1); switch (mac.ToLowerInvariant()) { case "inc": { if (idx == -1) { screrr(lnum, "Macro does not have a file name!"); } string path = line.Substring(idx + 1, line.Length - idx - 1); if (path.StartsWith("\"") && path.EndsWith("\"")) { path = path.Substring(1, path.Length - 2); } if (!File.Exists(path)) { screrr(lnum, "File '" + path + "' does not exist!"); } try { string[] file = File.ReadAllLines(path); if (debug) { Debug(lnum, tabs, "--; macro: parse another file '" + path + "' (" + file.Length + " lines)"); } ParseScript(string.Join("\n", file), args, 0, ref stack); if (debug) { Debug(lnum, tabs, "--; return to previous file"); } } catch (Exception) { screrr(lnum, "Failed to load file contents for file '" + path + "'!"); } } break; case "datamacro": { if (idx == -1) { screrr(lnum, "Data macro has not been defined!"); } Output.DataMacro = line.Substring(idx + 1, line.Length - idx - 1).Trim(); } break; case "lablenumber": { if (idx == -1) { screrr(lnum, "Lable number format has not been defined!"); } string fmt = line.Substring(idx + 1, line.Length - idx - 1).Trim().ToLowerInvariant(); if (LableRule.RandomRules[fmt] == null) { screrr(lnum, "Invalid lable number format '" + fmt + "'!"); } LableRule.GetNextRandom = LableRule.RandomRules[fmt]; } break; case "version": if (line.Substring(idx + 1, line.Length - idx - 1).Trim().ToLowerInvariant() != VERSION) { screrr(lnum, "Your script is out of date, and may not be executed! Please update the script to " + VERSION + "!"); } break; default: screrr(lnum, "Macro type '" + mac + "' not recognized!"); break; } } break; default: // incase we cant figure out what command this is screrr(lnum, "Symbol not recognized: '" + line.ElementAt(0) + "'"); return; } } catch (Exception e) { screrr(lnum, e.ToString()); } } } if (debug) { Debug(new string('-', 80)); } }