/// <summary> /// Assembles a plain text "EzLanguage" script into a list of CommandCall's. /// </summary> public static List <SoulsFormats.ESD.ESD.CommandCall> AssembleCommandScript(EzSembleContext context, string plaintext) { var result = new List <SoulsFormats.ESD.ESD.CommandCall>(); foreach (var cmdTxt in plaintext.Split(';').Select(x => { var cmdLine = x.Replace("\r", "").Trim(' ', '\n'); if (cmdLine.Contains("//")) { cmdLine = cmdLine.Substring(0, cmdLine.IndexOf("//")); } return(cmdLine); }).Where(x => !string.IsNullOrWhiteSpace(x))) { result.Add(AssembleCommandCall(context, cmdTxt.Replace("\n", ""))); } return(result); }
//Get the precedence of the operator/function passed in private static int GetPrecedence(EzSembleContext context, string s) { int max = 0; //if the passed is a function, make it the highest precedence //we simply get the max precedence value for all maths operators and add 1 if (IsFunction(context, s)) { max = Math.Max(Operators[Operators.Keys.First()][0], Operators[Operators.Keys.Last()][0]) + 1; } else { max = Operators[s][0]; } return(max); }
/// <summary> /// Assembles a plain text "EzLanguage" expression into bytecode. /// </summary> public static byte[] AssembleExpression(EzSembleContext context, string plaintext) { var postfixPlaintext = EzInfixor.InfixToPostFix(context, $"({plaintext.Trim('\n', ' ')})"); using (var ms = new MemoryStream()) using (var bw = new BinaryWriter(ms, Encoding.Unicode)) { int current = 0; int next = 0; while (current < postfixPlaintext.Length) { next = current + 1; Parse(postfixPlaintext, bw, current, ref next); current = next; } bw.Write((byte)0xA1); return(ms.ToArray()); } }
public static string InfixToPostFix(EzSembleContext context, string expression) { //expression = expression.Replace("\n", "~"); List <string> queue = new List <string>(); Stack <string> stack = new Stack <string>(); List <string> stringSubstitutions = new List <string>(); if (expression.Contains("\"")) { int current = 0; int next = current + 1; while (current < expression.Length) { next = current + 1; if (expression[current] == '"') { string thisText = ""; while (next < expression.Length && expression[next] != '"') { next++; } if (next == expression.Length) { throw new Exception("Unclosed string literal"); } string value = expression.Substring(current + 1, next - current - 1); if (value.Contains('\r') || value.Contains('\n')) { throw new Exception("String literals may not contain newlines"); } stringSubstitutions.Add(value); //expression = expression.Replace(value, $"{{{stringSubstitutions.Count - 1}}}"); next++; } current = next; } for (int i = 0; i < stringSubstitutions.Count; i++) { expression = expression.Replace(stringSubstitutions[i], $"{{{i}}}"); } } //populate operators //int format is {precedence, association -> 0=Left 1=Right} //string pattern = @"(?<=[^\\.a-zA-Z\\d])|(?=[^\\.a-zA-Z\\d])"; string pattern = @"(?<=[-+*/(),^<>=&\|~])(?=.)|(?<=.)(?=[-+*/(),^<>=&\|~])"; expression = expression.Replace(" ", ""); Regex regExPattern = new Regex(pattern); List <string> expr = new List <string>(regExPattern.Split(expression)); //expr.RemoveAll(s => String.IsNullOrEmpty(s.Trim())); //parse our expression and fix unary + and - ParseUnary(ref expr); //int continuationsQueued = 0; Stack <int> funcArgCounts = new Stack <int>(); bool popAndEnqueue(bool rightParenthesis = false) { var popped = stack.Pop(); if (IsFunction(context, popped)) { //Queue<string> garbage = new Queue<string>(); //while (queue[queue.Count - 1] == "~") //{ // garbage.Enqueue(queue[queue.Count - 1]); // queue.RemoveAt(queue.Count - 1); //} var argCount = funcArgCounts.Pop(); if (popped.StartsWith("SetREG")) { queue.Add($">[{popped.Substring(popped.Length - 1, 1)}]"); //var argCount = int.Parse(queue[queue.Count - 1]); //queue.RemoveAt(queue.Count - 1); //if (argCount == 1) //{ // queue.Add($">[{popped.Substring(popped.Length - 1, 1)}]"); //} //else //{ // throw new Exception("SetREG must be passed exactly 1 argument."); //} } else if (popped.StartsWith("GetREG")) { queue.Add($"[{popped.Substring(popped.Length - 1, 1)}]>"); } else if (popped == "AbortIfFalse") { if (queue.Count == 0 || argCount != 1) { throw new Exception("Invalid AbortIfFalse call"); } queue.Add($"."); } else { //queue.RemoveAt(queue.Count - 1); queue.Add($"({argCount})"); } //while (garbage.Count > 0) // queue.Add(garbage.Dequeue()); return(true); } else { queue.Add(popped); //if (rightParenthesis) //{ // queue.Add("~"); //} return(false); } } void registArgument() { if (funcArgCounts.Count > 0 && funcArgCounts.Peek() == 0) { funcArgCounts.Push(funcArgCounts.Pop() + 1); } } foreach (var s in expr) { //if (s == "~") //{ // continuationsQueued++; // //queue[queue.Count - 1] += "~"; // //queue.Add("~"); //} //else if (Operators.ContainsKey(s)) { //while the stack is not empty and the top of the stack is not an ( while (stack.Count > 0 && stack.Peek() != "(") { if ((GetAssociation(s) == 0 && GetPrecedence(context, s) <= GetPrecedence(context, stack.Peek())) || (GetAssociation(s) == 1 && GetPrecedence(context, s) < GetPrecedence(context, stack.Peek())) ) { popAndEnqueue(); } else { break; } } //push operator onto the stack stack.Push(s); } //These things aren't technically functions and thus do not //need to push their ID as an expression. else if (s.StartsWith("GetREG") || s.StartsWith("SetREG") || s == "AbortIfFalse") { registArgument(); stack.Push(s); funcArgCounts.Push(0); } //is our token on our defined functions else if (IsFunction(context, s)) { registArgument(); stack.Push(s); queue.Add(context.GetFunctionID(s).ToString()); funcArgCounts.Push(0); } //handle opening parenthesis //simply push this on the stack else if (s == "(") { stack.Push(s); } //handle closing parenthesis //pop all operators off the stack until the matching //opening parenthesis is found and then discard the //opening parenthesis else if (s == ")") { while (stack.Count != 0 && stack.Peek() != "(") { if (!popAndEnqueue(rightParenthesis: true)) { queue.Add("~"); } } //if (funcArgCounts.Count > 0) // queue.Add($"{funcArgCounts.Pop()}"); //forget the ( stack.Pop(); } //do we have an argument separator, if so, pop everything off the stack //until we reach the opening parenthesis, but leave that on the stack else if (s == ",") { while (stack.Peek() != "(") { popAndEnqueue(); } funcArgCounts.Push(funcArgCounts.Pop() + 1); } else { //none of the above so queue it registArgument(); queue.Add(s); } } //pop off the rest of the stack while (stack.Count != 0) { popAndEnqueue(); } var resultExpr = string.Join(" ", queue); for (int i = 0; i < stringSubstitutions.Count; i++) { resultExpr = resultExpr.Replace($"{{{i}}}", stringSubstitutions[i]); } if (resultExpr == "GetStateChangeType 233") { throw new Exception(); } return(resultExpr); }
public static string BytecodeToInfix(EzSembleContext context, byte[] Bytes) { //string MEOWDEBUG_OLD_DISSEMBLE = MEOWDEBUG_OldDissemble(Bytes); var bigEndianReverseBytes = Bytes.Reverse().ToArray(); Stack <string> stack = new Stack <string>(); Queue <string> garbage = new Queue <string>(); string popLastNonGarbageAndStoreGarbage(bool wantNumber = false) { var popped = stack.Pop(); //while (popped == "~" || popped == ".") //{ // garbage.Enqueue(popped); // popped = stack.Pop(); //} if (wantNumber) { return(popped.Trim('(', ')')); } else { return(popped); } } void restoreGarbage() { while (garbage.Count > 0) { stack.Push(garbage.Dequeue()); } } for (int i = 0; i < Bytes.Length; i++) { var b = Bytes[i]; if (b >= 0 && b <= 0x7F) { stack.Push($"{b - 64}"); } else if (b == 0xA5) { int j = 0; while (Bytes[i + j + 1] != 0 || Bytes[i + j + 2] != 0) { j += 2; } string text = context.IsBigEndian ? Encoding.BigEndianUnicode.GetString(Bytes, i + 1, j) : Encoding.Unicode.GetString(Bytes, i + 1, j); if (text.Contains('"') || text.Contains('\r') || text.Contains('\n')) { throw new Exception("Illegal character in string literal"); } stack.Push($"\"{text}\""); i += j + 2; } else if (b == 0x80) { if (!context.IsBigEndian) { stack.Push($"{BitConverter.ToSingle(Bytes, i + 1)}"); } else { stack.Push($"{BitConverter.ToSingle(bigEndianReverseBytes, (bigEndianReverseBytes.Length - 1) - (i + 1) - 4)}"); } i += 4; } else if (b == 0x81) { if (!context.IsBigEndian) { stack.Push($"{BitConverter.ToDouble(Bytes, i + 1)}"); } else { stack.Push($"{BitConverter.ToDouble(bigEndianReverseBytes, (bigEndianReverseBytes.Length - 1) - (i + 1) - 8)}"); } i += 8; } else if (b == 0x82) { if (!context.IsBigEndian) { stack.Push($"{BitConverter.ToInt32(Bytes, i + 1)}"); } else { stack.Push($"{BitConverter.ToInt32(bigEndianReverseBytes, (bigEndianReverseBytes.Length - 1) - (i + 1) - 4)}"); } i += 4; } else if (b == 0x84) { var id = popLastNonGarbageAndStoreGarbage(true); restoreGarbage(); stack.Push($"{context.GetFunctionInfo(int.Parse(id)).Name}()"); } else if (b == 0x85) { var arg1 = popLastNonGarbageAndStoreGarbage(); var id = popLastNonGarbageAndStoreGarbage(true); restoreGarbage(); stack.Push($"{context.GetFunctionInfo(int.Parse(id)).Name}({arg1})"); } else if (b == 0x86) { var arg2 = popLastNonGarbageAndStoreGarbage(); var arg1 = popLastNonGarbageAndStoreGarbage(); var id = popLastNonGarbageAndStoreGarbage(true); restoreGarbage(); stack.Push($"{context.GetFunctionInfo(int.Parse(id)).Name}({arg1}, {arg2})"); } else if (b == 0x87) { var arg3 = popLastNonGarbageAndStoreGarbage(); var arg2 = popLastNonGarbageAndStoreGarbage(); var arg1 = popLastNonGarbageAndStoreGarbage(); var id = popLastNonGarbageAndStoreGarbage(true); restoreGarbage(); stack.Push($"{context.GetFunctionInfo(int.Parse(id)).Name}({arg1}, {arg2}, {arg3})"); } else if (b == 0x88) { var arg4 = popLastNonGarbageAndStoreGarbage(); var arg3 = popLastNonGarbageAndStoreGarbage(); var arg2 = popLastNonGarbageAndStoreGarbage(); var arg1 = popLastNonGarbageAndStoreGarbage(); var id = popLastNonGarbageAndStoreGarbage(true); restoreGarbage(); stack.Push($"{context.GetFunctionInfo(int.Parse(id)).Name}({arg1}, {arg2}, {arg3}, {arg4})"); } else if (b == 0x89) { var arg5 = popLastNonGarbageAndStoreGarbage(); var arg4 = popLastNonGarbageAndStoreGarbage(); var arg3 = popLastNonGarbageAndStoreGarbage(); var arg2 = popLastNonGarbageAndStoreGarbage(); var arg1 = popLastNonGarbageAndStoreGarbage(); var id = popLastNonGarbageAndStoreGarbage(true); restoreGarbage(); stack.Push($"{context.GetFunctionInfo(int.Parse(id)).Name}({arg1}, {arg2}, {arg3}, {arg4}, {arg5})"); } else if (b == 0x8A) { var arg6 = popLastNonGarbageAndStoreGarbage(); var arg5 = popLastNonGarbageAndStoreGarbage(); var arg4 = popLastNonGarbageAndStoreGarbage(); var arg3 = popLastNonGarbageAndStoreGarbage(); var arg2 = popLastNonGarbageAndStoreGarbage(); var arg1 = popLastNonGarbageAndStoreGarbage(); var id = popLastNonGarbageAndStoreGarbage(true); restoreGarbage(); stack.Push($"{context.GetFunctionInfo(int.Parse(id)).Name}({arg1}, {arg2}, {arg3}, {arg4}, {arg5}, {arg6})"); } else if (OperatorsByByte.ContainsKey(b)) { var item2 = popLastNonGarbageAndStoreGarbage(); var item1 = popLastNonGarbageAndStoreGarbage(); restoreGarbage(); stack.Push($"{GetExpressionWithParenthesesIfContainsOperator(item1, OperatorsByByte[b])} " + $"{OperatorsByByte[b]} {GetExpressionWithParenthesesIfContainsOperator(item2, OperatorsByByte[b])}"); } else if (b == 0xA6) { //if (stack.Count > 2) //{ // var stackContents = new string[stack.Count]; // int j = stack.Count - 1; // while (stack.Count > 0) // { // var latest = stack.Pop(); // stackContents[j] = latest; // j--; // } // stack.Push($"({string.Join(" ", stackContents)})"); // Console.WriteLine("TEST"); //} //else //{ // stack.Push($"({stack.Pop()})"); //} stack.Push($"({stack.Pop()})"); } else if (b >= 0xA7 && b <= 0xAE) { byte regIndex = (byte)(b - 0xA7); var item = popLastNonGarbageAndStoreGarbage(); restoreGarbage(); stack.Push($"SetREG{regIndex}({item})"); } else if (b >= 0xAF && b <= 0xB6) { byte regIndex = (byte)(b - 0xAF); stack.Push($"GetREG{regIndex}()"); } else if (b == 0xB7) { var item = stack.Pop(); stack.Push($"AbortIfFalse({item})"); } else if (b == 0xA1) { //break; } else { stack.Push($"#{b:X2}"); } } return(string.Join(" ", stack)); }
private static bool IsFunction(EzSembleContext context, string s) { return(s.StartsWith("f") || context.FunctionIDsByName.ContainsKey(s) || s.StartsWith("GetREG") || s.StartsWith("SetREG") || (s == "AbortIfFalse")); }
/// <summary> /// Dissembles a list of CommandCall objects into a plain text "EzLanguage" script. /// </summary> public static string DissembleCommandScript(EzSembleContext context, List <SoulsFormats.ESD.ESD.CommandCall> script) { return(string.Join("\n", script.Select(x => $"{DissembleCommandCall(context, x)};"))); }
/// <summary> /// Dissembles a CommandCall object into a line of "EzLanguage" plain text. /// </summary> public static string DissembleCommandCall(EzSembleContext context, SoulsFormats.ESD.ESD.CommandCall c) { return($"{context.GetCommandInfo(c.CommandBank, c.CommandID).Name}({string.Join(", ", c.Arguments.Select(a => DissembleExpression(context, a)))})"); }
//public static string MEOWDEBUG_OldDissemble(byte[] bytes) //{ // if (bytes.Last() != 0xA1) // throw new Exception("All evaluators must end with 0xA1"); // var sb = new StringBuilder(); // bool newline = true; // for (int i = 0; i < bytes.Length - 1; i++) // { // byte b = bytes[i]; // if (TerminatorsByByte.ContainsKey(b)) // { // newline = true; // sb.Append(TerminatorsByByte[b]); // } // else // { // if (newline) // newline = false; // else // sb.Append(" "); // if (b >= 0 && b <= 0x7F) // { // sb.Append($"{b - 64}"); // } // else if (b == 0xA5) // { // int j = 0; // while (bytes[i + j + 1] != 0 || bytes[i + j + 2] != 0) // j += 2; // string text = Encoding.Unicode.GetString(bytes, i + 1, j); // if (text.Contains('"') || text.Contains('\r') || text.Contains('\n')) // throw new Exception("Illegal character in string literal"); // sb.Append($"\"{text}\""); // i += j + 2; // } // else if (b == 0x80) // { // sb.Append($"{BitConverter.ToSingle(bytes, i + 1)}"); // i += 4; // } // else if (b == 0x81) // { // sb.Append($"{BitConverter.ToDouble(bytes, i + 1)}"); // i += 8; // } // else if (b == 0x82) // { // sb.Append($"{BitConverter.ToInt32(bytes, i + 1)}"); // i += 4; // } // else if (b >= 0x84 && b <= 0x8A) // { // sb.Append($"({b - 0x84})"); // } // else if (OperatorsByByte.ContainsKey(b)) // { // sb.Append(OperatorsByByte[b]); // } // else if (b >= 0xA7 && b <= 0xAE) // { // sb.Append($">[{b - 0xA7}]"); // } // else if (b >= 0xAF && b <= 0xB6) // { // sb.Append($"[{b - 0xAF}]>"); // } // else if (b == 0xA1) // { // throw new Exception("Evaluators may not contain more than one 0xA1"); // } // else // { // sb.Append($"#{b.ToString("X2")}"); // } // } // } // return sb.ToString().TrimEnd(); //} /// <summary> /// Dissembles bytecode into an "EzLanguage" plain text expression. /// </summary> public static string DissembleExpression(EzSembleContext context, byte[] bytes) { return(EzInfixor.BytecodeToInfix(context, bytes)); }
/// <summary> /// Assembles a plain text "EzLanguage" command call into a CommandCall object. /// </summary> public static SoulsFormats.ESD.ESD.CommandCall AssembleCommandCall(EzSembleContext context, string plaintext) { var regex = System.Text.RegularExpressions.Regex.Match(plaintext, @"^([A-Za-z0-9_:]+)\((.*)\)$"); if (regex.Groups.Count != 3) { throw new Exception($"Invalid EzLanguage command call text: \"{plaintext}\""); } var command = regex.Groups[1].Value; var cmdId = context.GetCommandID(command); var argsText = regex.Groups[2].Value.Trim(); var finalArgs = new List <string>(); if (!string.IsNullOrWhiteSpace(argsText)) { // If <= 2 chars theres no way there can be more than 1 arg // And obviously if there's no commas if (argsText.Length <= 2 || !argsText.Contains(",")) { finalArgs = new List <string>() { argsText }; } else { int thisArgStartIndex = 0; int innerParenthesisLevel = 0; for (int i = 0; i < argsText.Length; i++) { if (i < argsText.Length - 1) { if (argsText[i] == '(') { innerParenthesisLevel++; } else if (argsText[i] == ')') { if (innerParenthesisLevel > 0) { innerParenthesisLevel--; } else { throw new Exception("Extra parenthesis found in command call args."); } } if (argsText[i] == ',') { if (innerParenthesisLevel == 0) { finalArgs.Add(argsText.Substring(thisArgStartIndex, i - thisArgStartIndex)); thisArgStartIndex = i + 1; } } } else //Very last char { if (argsText[i] == ',' || argsText[i] == '(') { throw new Exception("Very last char in command args cannot be a '(' or ','"); } else if (argsText[i] == ')') { if (innerParenthesisLevel > 0) { innerParenthesisLevel--; } else { throw new Exception("Extra parenthesis found in command call args."); } } } } if (thisArgStartIndex < argsText.Length - 1) { finalArgs.Add(argsText.Substring(thisArgStartIndex, (argsText.Length) - thisArgStartIndex)); } if (innerParenthesisLevel != 0) { throw new Exception("Unclosed parenthesis found in command call args."); } } } return(new SoulsFormats.ESD.ESD.CommandCall() { CommandBank = cmdId.Bank, CommandID = cmdId.ID, Arguments = finalArgs.Select(x => AssembleExpression(context, x)).ToList(), }); }