public static List <ESD.CommandCall> AssembleCommandScript(EzSembleContext context, Block block) { var result = new List <ESD.CommandCall>(); foreach (Statement st in block.Cmds) { var cmdId = context.GetCommandID(st.Name); result.Add(new ESD.CommandCall() { CommandBank = cmdId.Bank, CommandID = cmdId.ID, Arguments = st.Args.Select(x => AssembleExpression(context, x)).ToList(), }); } return(result); }
/// <summary> /// Assembles a plain text "EzLanguage" script into a list of CommandCall's. /// </summary> public static List <ESD.CommandCall> AssembleCommandScript(EzSembleContext context, string plaintext) { var result = new List <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.InfixToEzLanguage(context, plaintext); 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()); } }
/// <summary> /// Assembles a plain text "EzLanguage" command call into a CommandCall object. /// </summary> public static 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 ESD.CommandCall() { CommandBank = cmdId.Bank, CommandID = cmdId.ID, Arguments = finalArgs.Select(x => AssembleExpression(context, x)).ToList(), }); }
/// <summary> /// Assembles an Expr into bytecode. /// </summary> public static byte[] AssembleExpression(EzSembleContext context, Expr topExpr) { using (var ms = new MemoryStream()) using (var bw = new BinaryWriter(ms, Encoding.Unicode)) { void writeExpr(Expr expr) { // Number if (expr is ConstExpr ce) { if (ce.Value is int i) { bw.Write((byte)0x82); bw.Write(i); } else if (ce.Value is sbyte b) { bw.Write((byte)(b + 64)); } else if (ce.Value is float f) { bw.Write((byte)0x80); bw.Write(f); } else if (ce.Value is double d) { bw.Write((byte)0x81); bw.Write(d); } else if (ce.Value is string s) { if (s.Contains('\r') || s.Contains('\n')) { throw new Exception("String literals may not contain newlines"); } bw.Write((byte)0xA5); bw.Write(Encoding.Unicode.GetBytes(s + "\0")); } else { throw new Exception($"Invalid type {ce.Value.GetType()} for ConstExpr {ce} in {topExpr}"); } } else if (expr is UnaryExpr ue) { writeExpr(ue.Arg); bw.Write(BytesByOperator[ue.Op]); } else if (expr is BinaryExpr be) { writeExpr(be.Lhs); writeExpr(be.Rhs); bw.Write(BytesByOperator[be.Op]); } else if (expr is FunctionCall func) { string name = func.Name; if (name.StartsWith("SetREG")) { int regIndex = byte.Parse(name.Substring(name.Length - 1)); writeExpr(func.Args[0]); bw.Write((byte)(0xA7 + regIndex)); } else if (name.StartsWith("GetREG")) { int regIndex = byte.Parse(name.Substring(name.Length - 1)); bw.Write((byte)(0xAF + regIndex)); } else if (name.StartsWith("StateGroupArg")) { int index = func.Args[0].AsInt(); bw.Write((byte)(index + 64)); bw.Write((byte)0xB8); } else { int id = context.GetFunctionID(name); if (id >= -64 && id <= 63) { bw.Write((byte)(id + 64)); } else { bw.Write((byte)0x82); bw.Write(id); } // for (int i = func.Args.Count - 1; i >= 0; i--) for (int i = 0; i < func.Args.Count; i++) { writeExpr(func.Args[i]); } bw.Write((byte)(0x84 + func.Args.Count)); } } else if (expr is CallResult) { bw.Write((byte)0xB9); } else if (expr is CallOngoing) { bw.Write((byte)0xBA); } else if (expr is Unknown huh) { bw.Write(huh.Opcode); } else { throw new Exception($"Unknown expression subclass {expr.GetType()}: {expr} in {topExpr}"); } if (expr.IfFalse == FalseCond.CONTINUE) { bw.Write(BytesByTerminator['~']); } else if (expr.IfFalse == FalseCond.ABORT) { bw.Write(BytesByTerminator['.']); } } writeExpr(topExpr); bw.Write((byte)0xA1); byte[] arr = ms.ToArray(); if (arr.Length == 1) { throw new Exception($"{topExpr}"); } return(arr); } }
public static string InfixToEzLanguage(EzSembleContext context, string expression) { 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] == '"') { 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); 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 = @"(?<=[-+*/(),^<>=&\|~!\[\]])(?=.)|(?<=.)(?=[-+*/(),^<>=&\|~!\[\]])"; expression = expression.Replace(" ", ""); Regex regExPattern = new Regex(pattern); List <string> expr = new List <string>(regExPattern.Split(expression)); //parse our expression and fix unary + and - ParseUnary(ref expr); Stack <int> funcArgCounts = new Stack <int>(); bool popAndEnqueue() { var popped = stack.Pop(); if (IsFunction(context, popped)) { var argCount = funcArgCounts.Pop(); if (popped.StartsWith("SetREG")) { queue.Add($">[{popped.Substring(popped.Length - 1, 1)}]"); } 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 if (popped == "StateGroupArg") { if (queue.Count == 0 || argCount != 1) { throw new Exception("Invalid StateGroupArg call"); } queue.Add($"#B8"); } else { queue.Add($"({argCount})"); } return(true); } else { queue.Add(popped); return(false); } } void registArgument() { if (funcArgCounts.Count > 0 && funcArgCounts.Peek() == 0) { funcArgCounts.Push(funcArgCounts.Pop() + 1); } } foreach (var s in expr) { 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" || s == "StateGroupArg") { 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 == "(" || s == "[") { stack.Push("("); } //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 == ")" || s == "]") { while (stack.Count != 0 && stack.Peek() != "(" && stack.Peek() != "[") { // This is where we'd add ContinueIfFalse if we cared about it. popAndEnqueue(); } //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() != "(" && 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 Expr BytecodeToInfix(EzSembleContext context, byte[] Bytes) { var bigEndianReverseBytes = Bytes.Reverse().ToArray(); Stack <Expr> exprs = new Stack <Expr>(); List <Expr> popArgs(int amount) { List <Expr> args = new List <Expr>(); for (int i = 0; i < amount; i++) { args.Add(exprs.Pop()); } args.Reverse(); return(args); } for (int i = 0; i < Bytes.Length; i++) { var b = Bytes[i]; if (b >= 0 && b <= 0x7F) { exprs.Push(new ConstExpr { Value = (sbyte)(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"); } exprs.Push(new ConstExpr { Value = text }); i += j + 2; } else if (b == 0x80) { float val; if (!context.IsBigEndian) { val = BitConverter.ToSingle(Bytes, i + 1); } else { val = BitConverter.ToSingle(bigEndianReverseBytes, (bigEndianReverseBytes.Length - 1) - (i + 1) - 4); } exprs.Push(new ConstExpr { Value = val }); i += 4; } else if (b == 0x81) { double val; if (!context.IsBigEndian) { val = BitConverter.ToDouble(Bytes, i + 1); } else { val = BitConverter.ToDouble(bigEndianReverseBytes, (bigEndianReverseBytes.Length - 1) - (i + 1) - 8); } exprs.Push(new ConstExpr { Value = val }); i += 8; } else if (b == 0x82) { int val; if (!context.IsBigEndian) { val = BitConverter.ToInt32(Bytes, i + 1); } else { val = BitConverter.ToInt32(bigEndianReverseBytes, (bigEndianReverseBytes.Length - 1) - (i + 1) - 4); } exprs.Push(new ConstExpr { Value = val }); i += 4; } else if (b == 0x84) { exprs.Push(new FunctionCall { Args = popArgs(0), Name = context.GetFunctionInfo(exprs.Pop().AsInt()).Name }); } else if (b == 0x85) { exprs.Push(new FunctionCall { Args = popArgs(1), Name = context.GetFunctionInfo(exprs.Pop().AsInt()).Name }); } else if (b == 0x86) { exprs.Push(new FunctionCall { Args = popArgs(2), Name = context.GetFunctionInfo(exprs.Pop().AsInt()).Name }); } else if (b == 0x87) { exprs.Push(new FunctionCall { Args = popArgs(3), Name = context.GetFunctionInfo(exprs.Pop().AsInt()).Name }); } else if (b == 0x88) { exprs.Push(new FunctionCall { Args = popArgs(4), Name = context.GetFunctionInfo(exprs.Pop().AsInt()).Name }); } else if (b == 0x89) { exprs.Push(new FunctionCall { Args = popArgs(5), Name = context.GetFunctionInfo(exprs.Pop().AsInt()).Name }); } else if (b == 0x8A) { exprs.Push(new FunctionCall { Args = popArgs(6), Name = context.GetFunctionInfo(exprs.Pop().AsInt()).Name }); } else if (OperatorsByByte.ContainsKey(b)) { if (OperatorsByByte[b] == "N") { exprs.Push(new UnaryExpr { Op = "N", Arg = exprs.Pop() }); } else { exprs.Push(new BinaryExpr { Op = OperatorsByByte[b], Rhs = exprs.Pop(), Lhs = exprs.Pop() }); } } else if (b == 0xA6) { Expr top = exprs.Peek(); top.IfFalse = FalseCond.CONTINUE; } else if (b >= 0xA7 && b <= 0xAE) { byte regIndex = (byte)(b - 0xA7); exprs.Push(new FunctionCall { Args = popArgs(1), Name = $"SetREG{regIndex}" }); } else if (b >= 0xAF && b <= 0xB6) { byte regIndex = (byte)(b - 0xAF); exprs.Push(new FunctionCall { Args = popArgs(0), Name = $"GetREG{regIndex}" }); } else if (b == 0xB7) { Expr top = exprs.Peek(); top.IfFalse = FalseCond.ABORT; } else if (b == 0xB8) { // exprs.Push(new FunctionCall { Args = popArgs(1), Name = "StateGroupArg" }); FunctionCall func = new FunctionCall { Args = popArgs(1), Name = "StateGroupArg" }; ConstExpr ce = func.Args[0] as ConstExpr; // Console.WriteLine($"{ce} {ce.Value.GetType()}"); exprs.Push(func); } else if (b == 0xB9) { exprs.Push(new CallResult()); } else if (b == 0xBA) { // This opcode just returns a constant value 0x7FFFFFFF // But use higher-level representation of it exprs.Push(new CallOngoing()); } else if (b == 0xA1) { //break; } else { exprs.Push(new Unknown { Opcode = b }); } } if (exprs.Count != 1) { throw new Exception("Could not parse expr. Remaining stack: " + string.Join("; ", exprs) + $"; = {string.Join(" ", Bytes.Select(x => x.ToString("X2")))}"); } return(exprs.Pop()); }
private static bool IsFunction(EzSembleContext context, string s) { return(s.StartsWith("f") || context.FunctionIDsByName.ContainsKey(s) || s.StartsWith("GetREG") || s.StartsWith("SetREG") || s == "AbortIfFalse" || s == "StateGroupArg"); }
/// <summary> /// Dissembles bytecode into an "EzLanguage" expression. /// </summary> public static Expr DissembleExpression(EzSembleContext context, byte[] bytes) { return(EzInfixor.BytecodeToInfix(context, bytes)); }
/// <summary> /// Dissembles a list of CommandCall objects into a plain text "EzLanguage" script. /// </summary> public static Block DissembleCommandScript(EzSembleContext context, List <ESD.CommandCall> script) { return(new Block { Cmds = script.Select(x => DissembleCommandCall(context, x)).ToList() }); }
/// <summary> /// Dissembles a CommandCall object into a line of "EzLanguage" plain text. /// </summary> public static Statement DissembleCommandCall(EzSembleContext context, ESD.CommandCall c) { return(new Statement { Name = context.GetCommandInfo(c.CommandBank, c.CommandID).Name, Args = c.Arguments.Select(a => DissembleExpression(context, a)).ToList() }); }