public static List <UndertaleInstruction> Assemble(string source, IList <UndertaleFunction> funcs, IList <UndertaleVariable> vars, IList <UndertaleString> strg, UndertaleData data = null) { var lines = source.Replace("\r", "").Split('\n'); uint addr = 0; Dictionary <string, uint> labels = new Dictionary <string, uint>(); Dictionary <UndertaleInstruction, string> labelTargets = new Dictionary <UndertaleInstruction, string>(); List <UndertaleInstruction> instructions = new List <UndertaleInstruction>(); Dictionary <string, UndertaleVariable> localvars = new Dictionary <string, UndertaleVariable>(); foreach (var fullline in lines) { string line = fullline; if (line.Length == 0) { continue; } if (line[0] == ';') { continue; } if (line[0] == '>') { // Code entry inside of this one line = line.Substring(2, line.Length - 2).Trim(); int space = line.IndexOf(' '); string codeName = line.Substring(0, space); var code = data.Code.ByName(codeName); if (code == null) { throw new Exception($"Failed to find code entry with name \"{codeName}\"."); } string info = line.Substring(space + 1); Match match = Regex.Match(info, @"^\(locals=(.*)\,\s*argc=(.*)\)$"); if (!match.Success) { throw new Exception("Sub-code entry format error"); } code.LocalsCount = ushort.Parse(match.Groups[1].Value); code.ArgumentsCount = ushort.Parse(match.Groups[2].Value); code.Offset = addr * 4; continue; } if (line[0] == ':' && line.Length >= 3) { if (line[1] == '[') { string label = line.Substring(2, line.IndexOf(']') - 2); if (!string.IsNullOrEmpty(label)) { if (labels.ContainsKey(label)) { throw new Exception("Duplicate label: " + label); } labels.Add(label, addr); } continue; } } if (line[0] == '.') { // Assembler directive // TODO: Does not update the CodeLocals block yet!! string[] aaa = line.Split(' '); if (aaa[0] == ".localvar") { if (aaa.Length >= 4) { var varii = vars[Int32.Parse(aaa[3])]; if (data?.GeneralInfo?.BytecodeVersion >= 15 && varii.InstanceType != UndertaleInstruction.InstanceType.Local) { throw new Exception("Not a local var"); } if (varii.Name.Content != aaa[2]) { throw new Exception("Name mismatch"); } localvars.Add(aaa[2], varii); } } else { throw new Exception("Unknown assembler directive: " + aaa[0]); } continue; } string labelTgt; UndertaleInstruction instr = AssembleOne(line, funcs, vars, strg, localvars, out labelTgt, data); instr.Address = addr; if (labelTgt != null) { labelTargets.Add(instr, labelTgt); } instructions.Add(instr); addr += instr.CalculateInstructionSize(); } foreach (var pair in labelTargets) { pair.Key.JumpOffset = (int)labels[pair.Value] - (int)pair.Key.Address; } return(instructions); }
public static List <UndertaleInstruction> Assemble(string source, IList <UndertaleFunction> funcs, IList <UndertaleVariable> vars, IList <UndertaleString> strg, UndertaleData data = null) { var lines = source.Split('\n'); uint addr = 0; Dictionary <string, uint> labels = new Dictionary <string, uint>(); Dictionary <UndertaleInstruction, string> labelTargets = new Dictionary <UndertaleInstruction, string>(); List <UndertaleInstruction> instructions = new List <UndertaleInstruction>(); Dictionary <string, UndertaleVariable> localvars = new Dictionary <string, UndertaleVariable>(); foreach (var fullline in lines) { string line = fullline; if (line.Length > 0 && line[0] == ';') { continue; } string label = null; int labelEnd = line.IndexOf(':'); if (labelEnd > 0) { bool isLabel = true; for (var i = 0; i < labelEnd; i++) { if (!Char.IsLetterOrDigit(line[i]) && line[i] != '_') { isLabel = false; break; } } if (isLabel) { label = line.Substring(0, labelEnd).Trim(); line = line.Substring(labelEnd + 1); } } line = line.Trim(); if (String.IsNullOrEmpty(line)) { if (!String.IsNullOrEmpty(label)) { throw new Exception("Label with no instruction"); } else { continue; } } if (line.StartsWith(".")) { // Assembler directive // TODO: Does not update the CodeLocals block yet!! string[] aaa = line.Split(' '); if (aaa[0] == ".localvar") { if (aaa.Length >= 4) { var varii = vars[Int32.Parse(aaa[3])]; if (data?.GeneralInfo?.BytecodeVersion >= 15 && varii.InstanceType != UndertaleInstruction.InstanceType.Local) { throw new Exception("Not a local var"); } if (varii.Name.Content != aaa[2]) { throw new Exception("Name mismatch"); } localvars.Add(aaa[2], varii); } } else { throw new Exception("Unknown assembler directive: " + aaa[0]); } continue; } // Really ugly hack for compiling array variable references // See https://github.com/krzys-h/UndertaleModTool/issues/27#issuecomment-426637438 Func <int, UndertaleInstruction.InstanceType?> lookOnStack = (int amt) => { int stackCounter = amt; foreach (var i in instructions.Cast <UndertaleInstruction>().Reverse()) { if (stackCounter == 1) // This needs to be here because otherwise sth[aaa].another[bbb] doesn't work (damn this workaround is getting crazy, CHAOS, CHAOS) { if (i.Kind == UndertaleInstruction.Opcode.Push) { return(UndertaleInstruction.InstanceType.Self); // This is probably an instance variable then (e.g. pushi.e 1337; push.v self.someinstance; conv.v.i; pushi.e 0; pop.v.v [array]alarm) } else if (i.Kind == UndertaleInstruction.Opcode.PushLoc) { return(UndertaleInstruction.InstanceType.Local); } } //int old = stackCounter; stackCounter -= UndertaleInstruction.CalculateStackDiff(i); //Debug.WriteLine(i.ToString() + "; " + old + " -> " + stackCounter); if (stackCounter == 0) { if (i.Kind == UndertaleInstruction.Opcode.PushI) { return((UndertaleInstruction.InstanceType?)(short) i.Value); } else if (i.Kind == UndertaleInstruction.Opcode.Dup) { stackCounter += 1 + i.DupExtra; // Keep looking for the value that was duplicated } else { throw new Exception("My workaround still sucks"); } } } return(null); }; string labelTgt; UndertaleInstruction instr = AssembleOne(line, funcs, vars, strg, localvars, out labelTgt, data, lookOnStack); instr.Address = addr; if (labelTgt != null) { labelTargets.Add(instr, labelTgt); } if (!String.IsNullOrEmpty(label)) { if (labels.ContainsKey(label)) { throw new Exception("Duplicate label: " + label); } labels.Add(label, instr.Address); } instructions.Add(instr); addr += instr.CalculateInstructionSize(); } if (labels.ContainsKey("func_end")) { throw new Exception("func_end is a reserved label name"); } labels.Add("func_end", addr); foreach (var pair in labelTargets) { pair.Key.JumpOffset = (int)labels[pair.Value] - (int)pair.Key.Address; } return(instructions); }
public static List <UndertaleInstruction> Assemble(string source, IList <UndertaleFunction> funcs, IList <UndertaleVariable> vars, IList <UndertaleString> strg) { var lines = source.Split('\n'); uint addr = 0; Dictionary <string, uint> labels = new Dictionary <string, uint>(); Dictionary <UndertaleInstruction, string> labelTargets = new Dictionary <UndertaleInstruction, string>(); List <UndertaleInstruction> instructions = new List <UndertaleInstruction>(); Dictionary <string, UndertaleVariable> localvars = new Dictionary <string, UndertaleVariable>(); foreach (var fullline in lines) { string line = fullline; if (line.Length > 0 && line[0] == ';') { continue; } string label = null; int labelEnd = line.IndexOf(':'); if (labelEnd >= 0) { label = line.Substring(0, labelEnd).Trim(); line = line.Substring(labelEnd + 1); if (String.IsNullOrEmpty(label)) { throw new Exception("Empty label"); } } line = line.Trim(); if (String.IsNullOrEmpty(line)) { if (!String.IsNullOrEmpty(label)) { throw new Exception("Label with no instruction"); } else { continue; } } if (line.StartsWith(".")) { // Assembler directive // TODO: Does not update the CodeLocals block yet!! string[] aaa = line.Split(' '); if (aaa[0] == ".localvar") { if (aaa.Length >= 4) { var varii = vars[Int32.Parse(aaa[3])]; if (varii.InstanceType != UndertaleInstruction.InstanceType.Local) { throw new Exception("Not a local var"); } if (varii.Name.Content != aaa[2]) { throw new Exception("Name mismatch"); } localvars.Add(aaa[2], varii); } } else { throw new Exception("Unknown assembler directive: " + aaa[0]); } continue; } // Really ugly hack for compiling array variable references // See https://github.com/krzys-h/UndertaleModTool/issues/27#issuecomment-426637438 UndertaleInstruction instTypePush = instructions.Cast <UndertaleInstruction>().Reverse().Where((x) => UndertaleInstruction.GetInstructionType(x.Kind) == UndertaleInstruction.InstructionType.PushInstruction).ElementAtOrDefault(1); UndertaleInstruction.InstanceType?instTypeOnStack = instTypePush != null && instTypePush.Kind == UndertaleInstruction.Opcode.PushI ? (UndertaleInstruction.InstanceType?)(short) instTypePush.Value : null; string labelTgt; UndertaleInstruction instr = AssembleOne(line, funcs, vars, strg, localvars, out labelTgt, instTypeOnStack); instr.Address = addr; if (labelTgt != null) { labelTargets.Add(instr, labelTgt); } if (!String.IsNullOrEmpty(label)) { if (labels.ContainsKey(label)) { throw new Exception("Duplicate label: " + label); } labels.Add(label, instr.Address); } instructions.Add(instr); addr += instr.CalculateInstructionSize(); } if (labels.ContainsKey("func_end")) { throw new Exception("func_end is a reserved label name"); } labels.Add("func_end", addr); foreach (var pair in labelTargets) { pair.Key.JumpOffset = (int)labels[pair.Value] - (int)pair.Key.Address; } return(instructions); }