public static List<BFOpcode> ParseCodeblock(uint[] data, out bool isExtendedOpcodePresent) { isExtendedOpcodePresent = false; List<BFOpcode> opcodes = new List<BFOpcode>(2 << 12); for (int i = 0; i < data.Length; i++) { BFInstruction instruction = (BFInstruction)(data[i] & 0x0000FFFF); BFOpcode op = null; switch (instruction) { case BFInstruction.PushUInt32: { if (!isExtendedOpcodePresent) // optimization, keep track of whether or not there are extended opcodes for sorting later isExtendedOpcodePresent = true; Debug.Assert((data[i] & 0xFFFF0000) == 0); // debug: check if the high bits are indeed zeroed out op = new BFOpcode(instruction, i, data[++i]); // consume next int for op } break; case BFInstruction.PushFloat: { if (!isExtendedOpcodePresent) // optimization, keep track of whether or not there are extended opcodes for sorting later isExtendedOpcodePresent = true; Debug.Assert((data[i] & 0xFFFF0000) == 0); // debug: check if the high bits are indeed zeroed out op = new BFOpcode(instruction, i, BitConverter.ToSingle(BitConverter.GetBytes(data[++i]), 0)); // consume next int as float for op } break; case BFInstruction.Add: case BFInstruction.NotEqual: case BFInstruction.PushResult: case BFInstruction.Return: case BFInstruction.Subtract: case BFInstruction.Equal: Debug.Assert((data[i] & 0xFFFF0000) == 0); // debug: check if the high bits are indeed zeroed out op = new BFOpcode(instruction, i, null); // uses no operands, high bits are zeroed out break; default: op = new BFOpcode(instruction, i, ((data[i] & 0xFFFF0000) >> 16)); break; } opcodes.Add(op); } return opcodes; }
public static BFFile AssembleFromASMText(string path) { using (StreamReader reader = new StreamReader(path)) { // keep track of various indices int lineIndex = -1; int procIndex = 0; int jumpIndex = 0; int opIndex = 0; // list for fixing up jump references later List<Tuple<string, int>> unresolvedJumps = new List<Tuple<string, int>>(); List<Tuple<string, int>> unresolvedProcs = new List<Tuple<string, int>>(); // code label and opcode lists List<BFCodeLabel> procedures = new List<BFCodeLabel>(); List<BFCodeLabel> jumps = new List<BFCodeLabel>(); List<BFOpcode> opcodes = new List<BFOpcode>(); while (!reader.EndOfStream) { lineIndex++; // trim, clean, split line string[] tokens = reader.ReadLine() .Trim() .Replace("\t", string.Empty) .Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); // skip if line contains no tokens or starts with a comment if (tokens.Length == 0 || tokens[0] == ";") continue; if (tokens[0].EndsWith(":")) // we got a label { if (tokens[0].StartsWith("@")) // and the label is a jump label identifier { jumps.Add(ParseASMLabel(lineIndex, tokens[0], jumpIndex, opIndex, '@', ':')); jumpIndex++; } else // the label is a procedure identifier { procedures.Add(ParseASMLabel(lineIndex, tokens[0], procIndex, opIndex, ':')); procIndex++; } } else // we got an instruction { // instruction without any procedures, throw an error if (procedures.Count == 0) throw new BFASMParserException("Instruction defined at line {0} before any procedures", lineIndex + 1); BFInstruction instr; BFOpcode op = null; bool isDefined = ASMKeywordToBFInstruction.TryGetValue(tokens[0].ToLowerInvariant(), out instr); if (!isDefined) // 'unk_XX' instruction expected { string opNumStr = tokens[0].Split(new string[] { "unk_" }, StringSplitOptions.RemoveEmptyEntries)[0]; int opNumInt; long operand; if (opNumStr.Length < 2 || !int.TryParse(opNumStr, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out opNumInt) || !long.TryParse(tokens[1], out operand)) { throw new BFASMParserException("unk_XX", lineIndex + 1); } op = new BFOpcode((ushort)opNumInt, 0, operand); } else // regular instruction expected { // verify the number of arguments VerifyArgCount(instr, tokens, lineIndex + 1); switch (instr) { case BFInstruction.BeginProcedure: case BFInstruction.CallNative: case BFInstruction.CallProcedure: case BFInstruction.Jump: case BFInstruction.JumpIfFalse: string strArg = tokens[1]; int intArg; if (int.TryParse(strArg, out intArg)) // reference by id { op = new BFOpcode(instr, intArg); } else // reference by name { op = ParseInstrWithStringLiteralArgs(instr, strArg, lineIndex, opIndex, unresolvedProcs, unresolvedJumps); } break; case BFInstruction.PushFloat: op = ParseInstrWithDecimalLiteralArgs(instr, tokens, lineIndex); break; case BFInstruction.PushUInt16: case BFInstruction.PushVariable: case BFInstruction.SetVariable: case BFInstruction.PushUInt32: op = ParseInstrWithNumberLiteralArgs(instr, tokens, lineIndex); break; default: op = new BFOpcode(instr, null); // opcode takes no operands, eg add, subtract, return break; } } // this shouldn't be possible // but they said the same about the world being round if (op == null) throw new BFASMParserException(instr, lineIndex + 1); opcodes.Add(op); opIndex++; } } // fix up unresolved jump/procedure references ResolveLabelReferences(unresolvedJumps, jumps, opcodes); ResolveLabelReferences(unresolvedProcs, procedures, opcodes); return new BFFile(procedures.ToArray(), jumps.ToArray(), opcodes); } }