public CondIntermediate ExtractInstrArgs(Instr instr) { CmdCond cmd = new CmdCond { Name = Doc.Name }; if (instr.Layers != null) { throw new ArgumentException($"Cannot decompile {instr} because it's a control flow instruction with a layer"); } List <int> ignore = new List <int>(); if (ControlArg >= 0) { ignore.Add(ControlArg); if (instr.Args[ControlArg] is ParamArg) { throw new FancyNotSupportedException($"Control arg {ControlArg} in comes from event params, cannot decompile", instr); } } Cond retCond = cmd; if (CompareArg >= 0) { ignore.Add(NegateArg); ignore.Add(CompareArg); CompareCond cmp = new CompareCond(); cmp.Type = (ComparisonType)ExtractIntArg(instr, NegateArg); cmp.Rhs = instr.Args[CompareArg]; if (CompareArg2 >= 0) { ignore.Add(CompareArg2); cmp.Lhs = instr.Args[CompareArg2]; // All args should be ignored at this point } else { cmp.CmdLhs = cmd; } retCond = cmp; } else if (NegateArg >= 0) { ignore.Add(NegateArg); cmd.Negate = ExtractIntArg(instr, NegateArg) != TrueOp; } foreach (KeyValuePair <int, int> req in ExtraArgs) { ignore.Add(req.Key); } for (int i = 0; i < instr.Args.Count; i++) { if (!ignore.Contains(i)) { cmd.Args.Add(instr.Args[i]); } } // Hide default optional arguments here, all-or-nothing. This makes for nicer output. if (Doc.OptionalArgs > 0) { int hidable = 0; for (hidable = 0; hidable < Doc.OptionalArgs; hidable++) { int pos = Doc.Args.Count - 1 - hidable; // If arg exists and is default value, it can be hidden if (pos >= cmd.Args.Count) { break; } if (!TryExtractIntArg(cmd.Args[pos], out int arg) || arg.ToString() != Doc.Args[pos].Default.ToString()) { break; } } if (hidable == Doc.OptionalArgs) { cmd.Args.RemoveRange(Doc.Args.Count - Doc.OptionalArgs, Doc.OptionalArgs); } } CondIntermediate ret; if (Variant == ControlType.COND) { int reg = ExtractIntArg(instr, ControlArg); ret = new CondAssign { Cond = retCond, ToCond = reg, Op = reg == 0 ? CondAssignOp.Assign : (reg > 0 ? CondAssignOp.AssignAnd : CondAssignOp.AssignOr), }; } else if (Variant == ControlType.SKIP) { ret = new Goto { Cond = retCond, SkipLines = ExtractIntArg(instr, ControlArg) }; } else if (Variant == ControlType.END) { ret = new End { Cond = retCond, Type = ExtractIntArg(instr, ControlArg) }; } else if (Variant == ControlType.GOTO) { ret = new Goto { Cond = retCond, ToLabel = $"L{ExtractIntArg(instr, ControlArg)}" }; } else if (Variant == ControlType.WAIT) { ret = new Wait { Cond = retCond, Special = true }; } else { throw new ArgumentException($"Unrecognized variant style {Variant}"); } return(ret); }
// As part of instruction compilation, rewrite all instruction so that they correspond to valid emevd commands. // But don't convert them into instructions yet, since we need to edit control things like condition group register allocation and line skipping. public List <Intermediate> ExpandCond(Intermediate im, Func <string> newVar) { if (im is Instr || im is Label || im is NoOp || im is JSStatement) { return(new List <Intermediate> { im }); } else if (im is CondIntermediate condIm) { // Either use a direct command, or require a condition group because of negation or missing variant (or, in future, optional args). // Some commands do not support condition groups: // - goto/skip/end only: CompareCompiledConditionGroup, CompareNumberOfCoopClients, CompareNumberOfCoopClients // - goto only: HollowArenaMatchType Cond cond = condIm.Cond; if (cond == null) { return(new List <Intermediate> { im }); } else if (cond is ErrorCond) { // Just quit out here. Error is already recorded elsewhere return(new List <Intermediate>()); } else if (cond is OpCond) { throw new Exception($"Internal error: should have expanded out all subconditions in {im}"); } string docName = cond.DocName; if (!CondDocs.TryGetValue(docName, out FunctionDoc functionDoc)) { throw new Exception($"Internal error: Unknown condition function {docName}"); } ControlType type = condIm.ControlType; // The only non-synthetic use of WAIT is in condition groups, and these can just be converted to main group eval with no behavior change. if (type == ControlType.WAIT) { type = ControlType.COND; } bool indirection = false; if (functionDoc.Variants.ContainsKey(type)) { // If variant exists, see if it can be used ConditionVariant mainVariant = GetVariantByName(docName, functionDoc.Variants[type]); if (cond.Negate && mainVariant.NegateArg == -1) { if (!functionDoc.Variants.ContainsKey(ControlType.COND)) { throw new FancyNotSupportedException($"Can't use {docName} in this form since it does't have a condition version. Add or remove negation so that it can be translated to emevd.", im); } // Console.WriteLine($"Expanding {im} because it can't be negated as {type}"); indirection = true; } } else { // Needed for DS1, which only has skip. The instruction is rewritten later with # of lines if the label is synthetic. if (type == ControlType.GOTO && functionDoc.Variants.ContainsKey(ControlType.SKIP)) { // The type is ControlType.SKIP in this case, which will be accounted for once SkipLines is filled in. } else if (functionDoc.Variants.ContainsKey(ControlType.COND)) { indirection = true; } else { // Also part of error: can't use compiled groups in condition group. throw new FancyNotSupportedException($"Can't use statement type {type} with {functionDoc.Name}; can only use it with {string.Join(", ", functionDoc.Variants.Keys)}", im); } } if (indirection) { string var = newVar() + "z"; ConditionVariant cmdVariant = GetVariantByName(docName, functionDoc.Variants[ControlType.COND]); CondRef tmpCond = new CondRef { Compiled = false, Name = var }; if (cond.Negate && cmdVariant.NegateArg == -1) { cond.Negate = false; tmpCond.Negate = true; } Intermediate instr = new CondAssign { Cond = cond, Op = CondAssignOp.Assign, ToVar = var, Labels = condIm.Labels }; // Note that this is destructive. If this becomes a problem with multiple references that should // remain divergent, conditions may need some deep cloning routines. condIm.Cond = tmpCond; condIm.Labels = new List <string>(); return(new List <Intermediate> { instr, condIm }); } else { return(new List <Intermediate> { condIm }); } } else { throw new Exception($"Internal error: unable to compile unknown instruction {im}"); } }