/// <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); }