using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using Cosmos.Assembler; using Cosmos.Assembler.x86; using Instruction = Cosmos.Assembler.Instruction; using static XSharp.Compiler.XSRegisters; namespace XSharp.Compiler { /// This class is able to translate a single X# source code line into one or more /// target assembler source code and data lines. The class is a group of pattern each of /// which defines a transformation function from the X# syntax to the target assembler /// syntax. public class TokenPatterns { /// Describe a single pattern with its list of tokens that might include pattern /// reserved syntax token and a transformation function. For ease of search and performance /// an hashcode value is computed on the tokens list content and later used for searching /// a pattern matching an actual line of X# code source. protected class Pattern { public readonly TokenList Tokens; public readonly int Hash; public readonly CodeFunc Code; public readonly string PatternString; public Pattern(TokenList aTokens, CodeFunc aCode, string patternString) { Tokens = aTokens; Hash = aTokens.GetHashCode(); Code = aCode; PatternString = patternString; } } /// The set of blocks for the currently assembled function. Each time we begin /// assembling a new function this blocks collection is reset to an empty state. protected Blocks mBlocks = new Blocks(); protected class Blocks : List { protected int mCurrentLabelID = 0; public void Reset() { mCurrentLabelID = 0; } public Block Current() { return base[Count - 1]; } public void Start(TokenList aTokens, bool aIsCollector) { var xBlock = new Block(); mCurrentLabelID++; xBlock.LabelID = mCurrentLabelID; xBlock.StartTokens = aTokens; // Last because we use Current() above Add(xBlock); xBlock.ParentAssembler = Assembler.CurrentInstance; new Assembler(); } public void End() { Assembler.ClearCurrentInstance(); RemoveAt(Count - 1); } } protected class Block { public TokenList StartTokens; public int LabelID; public Assembler ParentAssembler; public void AddContentsToParentAssembler() { ParentAssembler.Instructions.AddRange(Assembler.CurrentInstance.Instructions); } } protected string mFuncName = null; protected bool mFuncExitFound = false; public bool EmitUserComments = true; public delegate void CodeFunc(TokenList aTokens); protected List mPatterns = new List(); protected bool mInIntHandler; protected string[] mCompareOps; protected List mCompares = new List(); protected string mNamespace = null; protected string GetNamespace() { if (mNamespace == null) { throw new Exception("A namespace has not been defined."); } return mNamespace; } public TokenPatterns() { mCompareOps = "< > = != <= >= 0 !0".Split(' '); var xSizes = "byte , word , dword ".Split(',').ToList(); // We must add this empty size so that we allow constructs where the size is not // explicitly defined in source code. For example : while eax < 0 // otherwise we would have to write : while dword eax < 0 xSizes.Add(""); foreach (var xSize in xSizes) { foreach (var xComparison in mCompareOps) { // Skip 0 and !0 if (!xComparison.Contains("0")) { mCompares.Add(xSize + "_REG " + xComparison + " 123"); mCompares.Add(xSize + "_REG " + xComparison + " _REG"); mCompares.Add(xSize + "_REG " + xComparison + " _REGADDR[1]"); mCompares.Add(xSize + "_REG " + xComparison + " _REGADDR[-1]"); mCompares.Add(xSize + "_REG " + xComparison + " _ABC"); mCompares.Add(xSize + "_REG " + xComparison + " #_ABC"); // mCompares.Add(xSize + "_REGADDR[1] " + xComparison + " 123"); mCompares.Add(xSize + "_REGADDR[-1] " + xComparison + " 123"); mCompares.Add(xSize + "_REGADDR[1] " + xComparison + " _REG"); mCompares.Add(xSize + "_REGADDR[-1] " + xComparison + " _REG"); mCompares.Add(xSize + "_REGADDR[1] " + xComparison + " #_ABC"); mCompares.Add(xSize + "_REGADDR[-1] " + xComparison + " #_ABC"); // mCompares.Add(xSize + "_ABC " + xComparison + " 123"); mCompares.Add(xSize + "_ABC " + xComparison + " _REG"); mCompares.Add(xSize + "_ABC " + xComparison + " #_ABC"); } } } AddPatterns(); } /// Builds a label that is suitable to denote a constant which name is given by the /// token. /// /// protected string ConstLabel(Token aToken) { return GroupLabel("Const_" + aToken); } /// Builds a label at namespace level having the given name. /// Local label name at namespace level. /// The label name protected string GroupLabel(string aLabel) { return GetNamespace() + "_" + aLabel; } /// Builds a label at function level having the given name. /// Local label name at function level. /// The label name protected string FuncLabel(string aLabel) { return GetNamespace() + "_" + mFuncName + "_" + aLabel; } /// Builds a label having the given name at current function block level. /// Local label name at function block level. /// The label name. protected string BlockLabel(string aLabel) { return FuncLabel("Block" + mBlocks.Current().LabelID + "_" + aLabel); } /// Build a label name for the given token. This method enforce the rule for . /// and .. prefixes and build the label at appropriate level. /// /// protected string GetLabel(Token aToken) { if ((aToken.Type != TokenType.AlphaNum) && !aToken.Matches("exit")) { throw new Exception("Label must be AlphaNum."); } string xValue = aToken.RawValue; if (!InFunctionBody) { if (xValue.StartsWith(".")) { return xValue.Substring(1); } return GroupLabel(xValue); } else { if (xValue.StartsWith("..")) { return xValue.Substring(2); } else if (xValue.StartsWith(".")) { return GroupLabel(xValue.Substring(1)); } return FuncLabel(xValue); } } /// Get a flag that tell if we are in a function body or not. This is used by the /// assembler generator when end of source file is reached to make sure the last function /// or interrupt handler is properly closed (see issue #15666) internal bool InFunctionBody { get { return !string.IsNullOrEmpty(mFuncName); } } /// Start a new function having the given name. The current blocks collection is /// reset to an empty state and the function name is saved for later reuse in local to function /// labels' name construction. /// Function name. protected void StartFunc(string aName) { if (InFunctionBody) { throw new Exception( "Found a function/interrupt handler definition embedded inside another function/interrupt handler."); } mFuncName = aName; mFuncExitFound = false; mBlocks.Reset(); } /// Terminate assembling current function. If a local to function exit label has not /// been explicitly defined a new one is automatically created. This is because some "return" /// keyword might have been used in function X# code. This keyword requires an exit label to /// be defined at function level. This method also automatically insert an IRET or RET instruction /// depending on whether the function is an interrupt handler or a standard function. protected void EndFunc() { if (null == mFuncName) { throw new Exception("Found a closing curly brace that doesn't match an opening curly brace."); } if (!mFuncExitFound) { XS.Label(GetNamespace() + "_" + mFuncName + "_Exit"); } if (mInIntHandler) { XS.InterruptReturn(); } else { XS.Set("static_field__Cosmos_Core_INTs_mLastKnownAddress", GetNamespace() + "_" + mFuncName + "_Exit", destinationIsIndirect: true, size: RegisterSize.Int32); XS.Return(); } mFuncName = null; } protected string GetSimpleRef(Token aToken) { return GetLabel(aToken); } private static RegisterSize GetSize(Token aToken) { return GetSize(aToken.RawValue); } private static RegisterSize GetSize(string value) { switch (value) { case "byte": return RegisterSize.Byte8; case "word": return RegisterSize.Short16; case "dword": return RegisterSize.Int32; default: throw new Exception($"Invalid size '{value}'"); } } private static string GetSizeString(RegisterSize size) { switch (size) { case RegisterSize.Byte8: return "byte"; case RegisterSize.Short16: return "word"; case RegisterSize.Int32: return "dword"; default: throw new ArgumentOutOfRangeException(nameof(size), size, null); } } protected string GetRef(List aTokens, ref int rIdx, bool onlySingleTokenRefs = false) { var xToken1 = aTokens[rIdx]; Token xToken2 = null; if (rIdx + 1 < aTokens.Count && !onlySingleTokenRefs) { xToken2 = aTokens[rIdx + 1]; } if (xToken1.Type == TokenType.Register) { if (xToken2 != null && xToken2.RawValue == "[") { if (aTokens[rIdx + 2].RawValue == "-") { rIdx += 5; return "[" + xToken1 + " - " + aTokens[rIdx - 2] + "]"; } rIdx += 4; return "[" + xToken1 + " + " + aTokens[rIdx - 2] + "]"; } rIdx += 1; return xToken1.RawValue; } else if (xToken1.Type == TokenType.AlphaNum) { rIdx += 1; return "[" + GetLabel(xToken1) + "]"; } else if (xToken1.Type == TokenType.ValueInt) { rIdx += 1; return xToken1.RawValue; } else if (xToken1.Type == TokenType.Call) { rIdx += 1; return "@ret_on_stack@"; } else if (xToken1.RawValue == "#") { rIdx += 2; return ConstLabel(xToken2); } else { throw new Exception("Cannot determine reference"); } } protected ConditionalTestEnum GetJump(string aComparison, bool aInvert = false) { if (aInvert) { if (aComparison == "<") { aComparison = ">="; } else if (aComparison == ">") { aComparison = "<="; } else if (aComparison == "=") { aComparison = "!="; } else if (aComparison == "0") { // Same as JE, but implies intent in .asm better aComparison = "!0"; } else if (aComparison == "!0") { // Same as JE, but implies intent in .asm better aComparison = "0"; } else if (aComparison == "!=") { aComparison = "="; } else if (aComparison == "<=") { aComparison = ">"; } else if (aComparison == ">=") { aComparison = "<"; } else { throw new Exception("Unrecognized symbol in conditional: " + aComparison); } } if (aComparison == "<") { return ConditionalTestEnum.Below; // unsigned } else if (aComparison == ">") { return ConditionalTestEnum.Above; // unsigned } else if (aComparison == "=") { return ConditionalTestEnum.Equal; } else if (aComparison == "0") { // Same as JE, but implies intent in .asm better return ConditionalTestEnum.Zero; } else if (aComparison == "!=") { return ConditionalTestEnum.NotEqual; } else if (aComparison == "!0") { // Same as JNE, but implies intent in .asm better return ConditionalTestEnum.NotZero; } else if (aComparison == "<=") { return ConditionalTestEnum.BelowOrEqual; // unsigned } else if (aComparison == ">=") { return ConditionalTestEnum.AboveOrEqual; // unsigned } else { throw new Exception("Unrecognized symbol in conditional: " + aComparison); } } protected void HandleIf(TokenList aTokens, string xComparison) { string xLabel; var xLast = aTokens.Last(); if (xLast.RawValue == "{") { mBlocks.Start(aTokens, false); XS.Jump(GetJump(xComparison, true), BlockLabel("End")); } else { if (xLast.Matches("return")) { xLabel = FuncLabel("Exit"); } else { xLabel = GetLabel(xLast); } XS.Jump(GetJump(xComparison), xLabel); } } protected void AddPatterns() { AddPattern("! Mov EAX, 0", delegate(TokenList aTokens) { XS.LiteralCode(aTokens[0].RawValue); }); AddPattern("// Comment", delegate(TokenList aTokens) { if (EmitUserComments) { string xValue = aTokens[0].RawValue; xValue = xValue.Replace("\"", "\\\""); XS.Comment(xValue); } }); // The value function returns a token containing the comparison var xCompares = new Dictionary>(); var xSizes = new[] { "", "byte", "word", "dword" }; #region Handle all comparisons foreach (var xSize in xSizes) { foreach (var xComparison in mCompareOps) { var xComparisonToken = new Token(-1); xComparisonToken.RawValue = xComparison; // Skip 0 and !0 if (!xComparison.Contains("0")) { xCompares.Add(xSize + " _REG " + xComparison + " 123", (tokenOffset, tokenList) => { var xOffset = tokenOffset; RegisterSize? xTypedSize = null; if (tokenList[xOffset].Type != TokenType.Register) { xTypedSize = GetSize(tokenList[xOffset]); xOffset += 1; } XS.Compare(tokenList[xOffset + 0].Register, tokenList[xOffset + 2].IntValue, size: xTypedSize); return xComparisonToken; }); xCompares.Add(xSize + " _REG " + xComparison + " _REG", (tokenOffset, tokenList) => { var xOffset = tokenOffset; RegisterSize? xTypedSize = null; if (tokenList[xOffset].Type != TokenType.Register) { xTypedSize = GetSize(tokenList[xOffset]); xOffset += 1; } XS.Compare(tokenList[xOffset + 0].Register, tokenList[xOffset + 2].Register); return xComparisonToken; }); xCompares.Add(xSize + " _REG " + xComparison + " _REGADDR[1]", (tokenOffset, tokenList) => { var xOffset = tokenOffset; if (tokenList[xOffset].Type != TokenType.Register) { xOffset += 1; } XS.Compare(tokenList[xOffset + 0].Register, tokenList[xOffset + 2].Register, sourceDisplacement: (int)tokenList[xOffset + 3].IntValue); return xComparisonToken; }); xCompares.Add(xSize + " _REG " + xComparison + " _REGADDR[-1]", (tokenOffset, tokenList) => { var xOffset = tokenOffset; if (tokenList[xOffset].Type != TokenType.Register) { xOffset += 1; } XS.Compare(tokenList[xOffset + 0].Register, tokenList[xOffset + 2].Register, sourceDisplacement: -(int)tokenList[xOffset + 3].IntValue); return xComparisonToken; }); xCompares.Add(xSize + " _REG " + xComparison + " _ABC", (tokenOffset, tokenList) => { var xOffset = tokenOffset; if (tokenList[xOffset].Type != TokenType.Register) { xOffset += 1; } XS.Compare(tokenList[xOffset + 0].Register, GetLabel(tokenList[xOffset + 2]), sourceIsIndirect: true); return xComparisonToken; }); xCompares.Add(xSize + " _REG " + xComparison + " #_ABC", (tokenOffset, tokenList) => { var xOffset = tokenOffset; if (tokenList[xOffset].Type != TokenType.Register) { xOffset += 1; } XS.Compare(tokenList[xOffset + 0].Register, ConstLabel(tokenList[xOffset + 3])); return xComparisonToken; }); xCompares.Add(xSize + " _REGADDR[1] " + xComparison + " 123", (tokenOffset, tokenList) => { var xOffset = tokenOffset; RegisterSize? xTypedSize = null; if (tokenList[xOffset].Type != TokenType.Register) { xTypedSize = GetSize(tokenList[xOffset]); xOffset += 1; } XS.Compare(tokenList[xOffset + 0].Register, tokenList[xOffset + 5].IntValue, destinationDisplacement: (int)tokenList[xOffset + 2].IntValue, size: xTypedSize); return xComparisonToken; }); xCompares.Add(xSize + " _REGADDR[-1] " + xComparison + " 123", (tokenOffset, tokenList) => { var xOffset = tokenOffset; RegisterSize? xTypedSize = null; if (tokenList[xOffset].Type != TokenType.Register) { xTypedSize = GetSize(tokenList[xOffset]); xOffset += 1; } XS.Compare(tokenList[xOffset + 0].Register, tokenList[xOffset + 2].IntValue, destinationDisplacement: -(int)tokenList[xOffset + 1].IntValue, size: xTypedSize); return xComparisonToken; }); xCompares.Add(xSize + " _REGADDR[1] " + xComparison + " _REG", (tokenOffset, tokenList) => { var xOffset = tokenOffset; if (tokenList[xOffset].Type != TokenType.Register) { xOffset += 1; } XS.Compare(tokenList[xOffset + 0].Register, tokenList[xOffset + 2].Register, destinationDisplacement: (int)tokenList[xOffset + 1].IntValue); return xComparisonToken; }); xCompares.Add(xSize + " _REGADDR[-1] " + xComparison + " _REG", (tokenOffset, tokenList) => { var xOffset = tokenOffset; if (tokenList[xOffset].Type != TokenType.Register) { xOffset += 1; } XS.Compare(tokenList[xOffset + 0].Register, tokenList[xOffset + 2].Register, destinationDisplacement: -(int)tokenList[xOffset + 1].IntValue); return xComparisonToken; }); xCompares.Add(xSize + " _REGADDR[1] " + xComparison + " #_ABC", (tokenOffset, tokenList) => { var xOffset = tokenOffset; RegisterSize? xTypedSize = null; if (tokenList[xOffset].Type != TokenType.Register) { xTypedSize = GetSize(tokenList[xOffset]); xOffset += 1; } XS.Compare(tokenList[xOffset + 0].Register, ConstLabel(tokenList[xOffset + 2]), destinationDisplacement: (int)tokenList[xOffset + 1].IntValue, sourceIsIndirect: true, size: xTypedSize); return xComparisonToken; }); xCompares.Add(xSize + " _REGADDR[-1] " + xComparison + " #_ABC", (tokenOffset, tokenList) => { var xOffset = tokenOffset; RegisterSize? xTypedSize = null; if (tokenList[xOffset].Type != TokenType.Register) { xTypedSize = GetSize(tokenList[xOffset]); xOffset += 1; } XS.Compare(tokenList[xOffset + 0].Register, ConstLabel(tokenList[xOffset + 2]), destinationDisplacement: (int)tokenList[xOffset + 1].IntValue, size: xTypedSize); return xComparisonToken; }); xCompares.Add(xSize + " _ABC " + xComparison + " 123", (tokenOffset, tokenList) => { var xOffset = tokenOffset; RegisterSize? xTypedSize = null; if (tokenList[xOffset].Type != TokenType.Register) { xTypedSize = GetSize(tokenList[xOffset]); xOffset += 1; } XS.Compare(GetLabel(tokenList[xOffset + 0]), tokenList[xOffset + 2].IntValue, destinationIsIndirect: true, size: xTypedSize.GetValueOrDefault(RegisterSize.Int32)); return xComparisonToken; }); xCompares.Add(xSize + " _ABC " + xComparison + " _REG", (tokenOffset, tokenList) => { var xOffset = tokenOffset; if (tokenList[xOffset + 2].Type != TokenType.Register) { xOffset += 1; } XS.Compare(GetSimpleRef(tokenList[xOffset + 0]), tokenList[xOffset + 2].Register, destinationIsIndirect: true); return xComparisonToken; }); xCompares.Add(xSize + " _ABC " + xComparison + " #_ABC", (tokenOffset, tokenList) => { var xOffset = tokenOffset; RegisterSize? xTypedSize = null; if (tokenList[xOffset].Type != TokenType.Register) { xTypedSize = GetSize(tokenList[xOffset]); xOffset += 1; } XS.Compare(GetSimpleRef(tokenList[xOffset + 0]), ConstLabel(tokenList[xOffset + 3]), destinationIsIndirect: true, size: xTypedSize.GetValueOrDefault(RegisterSize.Int32)); return xComparisonToken; }); } } } #endregion Handle all comparisons // Labels // Local and proc level are used most, so designed to make their syntax shortest. // Think of the dots like a directory, . is current group, .. is above that. // ..Name: - Global level. Emitted exactly as is. // .Name: - Group level. Group_Name // Name: - Function level. Group_ProcName_Name // The Exit label is a special one that is used as a target for the return instruction. // It deserve special handling. AddPattern("Exit:", delegate(TokenList aTokens) { XS.Label(GetLabel(aTokens[0])); mFuncExitFound = true; }); // Regular label recognition. AddPattern("_ABC:", delegate(TokenList aTokens) { XS.Label(GetLabel(aTokens[0])); }); AddPattern("Call _ABC", delegate(TokenList aTokens) { XS.Call(GetLabel(aTokens[1])); }); AddPattern("_PCALL", delegate(TokenList aTokens) { if (aTokens.Count != 1 || aTokens[0].Type != TokenType.Call) { throw new Exception("Error occured in parametrized call parsing"); } else { List mparts = aTokens[0].RawValue.Remove(aTokens[0].RawValue.Length - 1).Split('(').ToList(); if (mparts.Count < 2) { throw new Exception("Error occured in parametrized call parsing"); } string fname = mparts[0]; mparts.RemoveAt(0); aTokens[0].RawValue = String.Join("(", mparts).Trim(); string val = ""; int idx; var xParams = new List(); int level = 0; foreach (char c in aTokens[0].RawValue) { switch (c) { case ',': if (level == 0) { xParams.Add(val.Trim()); val = ""; } break; case '(': level++; val += c; break; case ')': level--; val += c; break; default: val += c; break; } } if (!String.IsNullOrEmpty(val.Trim())) { xParams.Add(val); } if (level != 0) { throw new Exception("'(' occured without closing equivalent ')' in parametrized function call"); } Parser xParser; xParams.Reverse(); foreach (string p in xParams) { xParser = new Parser(p, 0, false, false); idx = 0; val = GetRef(xParser.Tokens, ref idx); if (val != "@ret_on_stack@") { //XS.PushLiteral(val); throw new Exception(); } else { //aAsm += GetPatternCode(xParser.Tokens).GetCode(false); throw new NotImplementedException("Didn't get converted yet!"); } } XS.Call(GroupLabel(fname)); } }); AddPattern("Goto _ABC", delegate(TokenList aTokens) { XS.Jump(GetLabel(aTokens[1])); }); // Defines a constant having the given name and initial value. AddPattern("const _ABC = 123", delegate(TokenList aTokens) { XS.Const(ConstLabel(aTokens[1]), aTokens[3].IntValue.ToString()); }); // Declare a double word variable having the given name and initialized to 0. The // variable is declared at namespace level. AddPattern("var _ABC", delegate(TokenList aTokens) { XS.DataMember(GetLabel(aTokens[1])); }); // Declare a doubleword variable having the given name and an explicit initial value. The // variable is declared at namespace level. AddPattern("var _ABC = 123", delegate(TokenList aTokens) { XS.DataMember(GetLabel(aTokens[1]), aTokens[3].IntValue); }); // Declare a textual variable having the given name and value. The variable is defined at // namespace level and a null terminating byte is automatically added after the textual // value. AddPattern("var _ABC = 'Text'", delegate(TokenList aTokens) { // Fix issue #15660 by using backquotes for string surrounding and escaping embedded // back quotes. XS.DataMember(GetLabel(aTokens[1]), EscapeBackQuotes(aTokens[3].RawValue)); }); // Declare a one-dimension array of bytes, words or doublewords. All members are initialized to 0. // _ABC is array name. 123 is the total number of items in the array. AddPattern(new string[] { "var _ABC byte[123]", "var _ABC word[123]", "var _ABC dword[123]" }, delegate(TokenList aTokens) { string xSize; if (aTokens[2].Matches("byte")) { xSize = "db"; } else if (aTokens[2].Matches("word")) { xSize = "dw"; } else if (aTokens[2].Matches("dword")) { xSize = "dd"; } else { throw new Exception("Unknown size specified"); } XS.DataMember(GetLabel(aTokens[1]), aTokens[4].IntValue, xSize, "0"); }); foreach (var xCompare in xCompares) { // 0 1 2 3 4 AddPattern("while " + xCompare.Key + " {", delegate(TokenList aTokens) { mBlocks.Start(aTokens, false); XS.Label(BlockLabel("Begin")); int xIdx = 1; Token xComparison = xCompare.Value(1, aTokens); XS.Jump(GetJump(xComparison.RawValue, true), BlockLabel("End")); }); } foreach (var xTail in "goto _ABC|return|{".Split('|')) { // if 0 exit, etc foreach (var xComparison in mCompareOps) { AddPattern("if " + xComparison + " " + xTail, delegate(TokenList aTokens) { string xOp = aTokens[1].RawValue; // !0 is 2 tokens if (aTokens[1].RawValue + aTokens[2].RawValue == "!0") { xOp = "!0"; } HandleIf(aTokens, xOp); }); } // if reg = x exit, etc foreach (var xCompare in xCompares) { // 0 1 2 3 4 AddPattern("if " + xCompare.Key + " " + xTail, delegate(TokenList aTokens) { int xIdx = 1; Token xComparison = xCompare.Value(1, aTokens); HandleIf(aTokens, xComparison.RawValue); }); } } AddPattern("_REG ?= 123", delegate(TokenList aTokens) { XS.Compare(aTokens[0].Register, aTokens[2].IntValue); }); AddPattern("_REG ?= _ABC", delegate(TokenList aTokens) { XS.Compare(aTokens[0].Register, GetSimpleRef(aTokens[2]), sourceIsIndirect: true); }); AddPattern("_REG ?= #_ABC", delegate(TokenList aTokens) { XS.Compare(aTokens[0].Register, ConstLabel(aTokens[2])); }); AddPattern("_REG ?& 123", delegate(TokenList aTokens) { XS.Test(aTokens[0].Register, aTokens[2].IntValue); }); AddPattern("_REG ?& _ABC", delegate(TokenList aTokens) { XS.Test(aTokens[0].Register, GetLabel(aTokens[2]), sourceIsIndirect: true); }); AddPattern("_REG ?& #_ABC", delegate(TokenList aTokens) { XS.Test(aTokens[0].Register, GetLabel(aTokens[2]), sourceIsIndirect: true); }); AddPattern("_REG ~> 123", delegate(TokenList aTokens) { XS.RotateRight(aTokens[0].Register, aTokens[2].IntValue); }); AddPattern("_REG <~ 123", delegate(TokenList aTokens) { XS.RotateLeft(aTokens[0].Register, aTokens[2].IntValue); }); AddPattern("_REG >> 123", delegate(TokenList aTokens) { if (aTokens[2].IntValue > 255) { throw new IndexOutOfRangeException("Value is too large to be used as bitcount!"); } XS.ShiftRight(aTokens[0].Register, (byte)aTokens[2].IntValue); }); AddPattern("_REG << 123", delegate(TokenList aTokens) { if (aTokens[2].IntValue > 255) { throw new IndexOutOfRangeException("Value is too large to be used as bitcount!"); } XS.ShiftLeft(aTokens[0].Register, (byte)aTokens[2].IntValue); }); AddPattern("_REG = 123", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[2].IntValue); }); AddPattern("_REGADDR[1] = 123", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[5].IntValue, destinationDisplacement: (int)aTokens[2].IntValue, size: RegisterSize.Int32); }); AddPattern("_REGADDR[-1] = 123", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[5].IntValue, destinationDisplacement: -(int)aTokens[2].IntValue, size: RegisterSize.Int32); }); AddPattern("_REG = #_ABC", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, ConstLabel(aTokens[3])); }); AddPattern("_REGADDR[1] = #_ABC", delegate(TokenList aTokens) { //XS.Set(RegisterSize.Int32, aTokens[0].IntValue, aTokens[2].IntValue, ConstLabel(aTokens[5])); throw new NotImplementedException(""); }); AddPattern("_REGADDR[-1] = #_ABC", delegate(TokenList aTokens) { var xFirst = GetSimpleRef(aTokens[0]); var xSecond = GetSimpleRef(aTokens[2]); //XS.SetLiteral("dword [" + xFirst + " - " + xSecond + "]", ConstLabel(aTokens[5])); throw new NotImplementedException(""); }); AddPattern("_REG = _REG", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[2].Register); }); AddPattern("_REGADDR[1] = _REG", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[5].Register, destinationDisplacement: (int)aTokens[2].IntValue); }); AddPattern("_REGADDR[-1] = _REG", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[5].Register, destinationDisplacement: -(int)aTokens[2].IntValue); }); AddPattern("_REG = _REGADDR[1]", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[2].Register, sourceDisplacement: (int)aTokens[4].IntValue); }); AddPattern("_REG = _REGADDR[-1]", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[2].Register, sourceDisplacement: -(int)aTokens[4].IntValue); }); AddPattern("_REG = [_REG]", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[3].Register, sourceIsIndirect: true); }); AddPattern("_REG = [_REG + 1]", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[3].Register, sourceDisplacement: (int?)aTokens[5].IntValue); }); AddPattern("_REG = [_REG - 1]", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[3].Register, sourceDisplacement: -(int)aTokens[5].IntValue); }); AddPattern("[_REG] = _REG", delegate(TokenList aTokens) { XS.Set(aTokens[1].Register, aTokens[4].Register, destinationIsIndirect: true); }); AddPattern("[_REG + 1] = _REG", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[3].Register, destinationDisplacement: (int)aTokens[5].IntValue); }); AddPattern("[_REG - 1] = _REG", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, aTokens[3].Register, destinationDisplacement: -(int)aTokens[5].IntValue); }); AddPattern("_REG = _ABC", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, GetLabel(aTokens[2]), sourceIsIndirect: true); }); // why not [var] like registers? Because its less frequent to access the ptr // and it is like a reg.. without [] to get the value... AddPattern("_REG = @_ABC", delegate(TokenList aTokens) { XS.Set(aTokens[0].Register, GetLabel(aTokens[3])); }); AddPattern(new string[] { "Port[DX] = AL", "Port[DX] = AX", "Port[DX] = EAX" }, delegate(TokenList aTokens) { XS.WriteToPortDX(aTokens[5].Register); }); AddPattern(new string[] { "AL = Port[DX]", "AX = Port[DX]", "EAX = Port[DX]" }, delegate(TokenList aTokens) { XS.ReadFromPortDX(aTokens[0].Register); }); AddPattern("+123", delegate(TokenList aTokens) { XS.Push(aTokens[0].IntValue, size: RegisterSize.Int32); }); AddPattern(new string[] { "+123 as byte", "+123 as word", "+123 as dword" }, delegate(TokenList aTokens) { var xSize = GetSize(aTokens[1]); XS.Push(aTokens[1].IntValue, size: xSize); }); AddPattern("+_REG", delegate(TokenList aTokens) { XS.Push(aTokens[1].Register); }); AddPattern(new string[] { //0 1 2 3 "+#_ABC", "+#_ABC as byte", "+#_ABC as word", "+#_ABC as dword" }, delegate(TokenList aTokens) { RegisterSize xSize = RegisterSize.Int32; if (aTokens.Count > 2) { xSize = GetSize(aTokens[3]); } XS.Push(ConstLabel(aTokens[1]), size: xSize); }); AddPattern("+All", delegate(TokenList aTokens) { XS.PushAllRegisters(); }); AddPattern("-All", delegate(TokenList aTokens) { XS.PopAllRegisters(); }); AddPattern("-_REG", delegate(TokenList aTokens) { XS.Pop(aTokens[1].Register); }); AddPattern("_ABC = _REG", delegate(TokenList aTokens) { XS.Set(GetLabel(aTokens[0]), aTokens[2].Register, destinationIsIndirect: true); }); AddPattern("_ABC = #_ABC", delegate(TokenList aTokens) { XS.Set(GetLabel(aTokens[0]), ConstLabel(aTokens[3]), destinationIsIndirect: true, size: RegisterSize.Int32); }); AddPattern("_ABC = 123", delegate(TokenList aTokens) { XS.Set(GetLabel(aTokens[0]), aTokens[2].IntValue, destinationIsIndirect: true); }); AddPattern(new string[] { "_ABC = 123 as byte", "_ABC = 123 as word", "_ABC = 123 as dword" }, delegate(TokenList aTokens) { XS.Set(GetLabel(aTokens[0]), aTokens[2].IntValue, size: GetSize(aTokens[4])); }); AddPattern(new string[] { "_REG + 1", }, delegate(TokenList aTokens) { XS.Add(aTokens[0].Register, aTokens[2].IntValue); }); AddPattern(new string[] { "_REG + _REG" }, delegate(TokenList aTokens) { XS.Add(aTokens[0].Register, aTokens[2].Register); }); AddPattern(new string[] { "_REG - 1", }, delegate(TokenList aTokens) { XS.Sub(aTokens[0].Register, aTokens[2].IntValue); }); AddPattern(new string[] { "_REG - _REG" }, delegate(TokenList aTokens) { XS.Sub(aTokens[0].Register, aTokens[2].Register); }); AddPattern(new string[] { "_REG * 1", }, delegate(TokenList aTokens) { XS.IntegerMultiply(aTokens[0].Register, aTokens[2].IntValue); }); AddPattern(new string[] { "_REG * _REG" }, delegate(TokenList aTokens) { XS.IntegerMultiply(aTokens[0].Register, aTokens[2].Register); }); AddPattern("_REG++", delegate(TokenList aTokens) { XS.Increment(aTokens[0].Register); }); AddPattern("_REG--", delegate(TokenList aTokens) { XS.Decrement(aTokens[0].Register); }); AddPattern(new string[] { "_REG & 1", }, delegate(TokenList aTokens) { XS.And(aTokens[0].Register, aTokens[2].IntValue); }); AddPattern(new string[] { "_REG & _REG" }, delegate(TokenList aTokens) { XS.And(aTokens[0].Register, aTokens[2].Register); }); AddPattern(new string[] { "_REG | 1", }, delegate(TokenList aTokens) { XS.Or(aTokens[0].Register, aTokens[2].IntValue); }); AddPattern(new string[] { "_REG | _REG" }, delegate(TokenList aTokens) { XS.Or(aTokens[0].Register, aTokens[2].Register); }); AddPattern(new string[] { "_REG ^ 1", }, delegate(TokenList aTokens) { XS.Xor(aTokens[0].Register, aTokens[2].IntValue); }); AddPattern(new string[] { "_REG ^ _REG" }, delegate(TokenList aTokens) { XS.Xor(aTokens[0].Register, aTokens[2].Register); }); // End block. This handle both terminating a standard block as well as a function or an // interrupt handler. AddPattern("}", delegate(TokenList aTokens) { if (mBlocks.Count == 0) { EndFunc(); } else { var xBlock = mBlocks.Current(); var xToken1 = xBlock.StartTokens[0]; if (xToken1.Matches("repeat")) { var xCount = xBlock.StartTokens[1].IntValue; for (var i = 1; i <= xCount; i++) { xBlock.AddContentsToParentAssembler(); } } else if (xToken1.Matches("while")) { XS.Jump(BlockLabel("Begin")); XS.Label(BlockLabel("End")); xBlock.AddContentsToParentAssembler(); } else if (xToken1.Matches("if")) { XS.Label(BlockLabel("End")); xBlock.AddContentsToParentAssembler(); } else { throw new Exception("Unknown block starter."); } mBlocks.End(); } }); AddPattern("namespace _ABC", delegate(TokenList aTokens) { mNamespace = aTokens[1].RawValue; }); AddPattern("Return", delegate { XS.Jump(FuncLabel("Exit")); }); AddPattern("Repeat 4 times {", delegate(TokenList aTokens) { mBlocks.Start(aTokens, true); }); AddPattern("Interrupt _ABC {", delegate(TokenList aTokens) { StartFunc(aTokens[1].RawValue); mInIntHandler = true; XS.Label(GetNamespace() + "_" + aTokens[1].RawValue); }); // This needs to be different from return. // return jumps to exit, ret does raw x86 ret AddPattern("Ret", delegate(TokenList aTokens) { XS.Return(); }); AddPattern("IRet", delegate(TokenList aTokens) { XS.InterruptReturn(); }); AddPattern("Function _ABC {", delegate(TokenList aTokens) { StartFunc(aTokens[1].RawValue); mInIntHandler = false; XS.Label(GetNamespace() + "_" + aTokens[1].RawValue); }); AddPattern("Checkpoint 'Text'", delegate(TokenList aTokens) { // This method emits a lot of ASM, but thats what we want becuase // at this point we need ASM as simple as possible and completely transparent. // No stack changes, no register mods, no calls, no jumps, etc. // TODO: Add an option on the debug project properties to turn this off. // Also see WriteDebugVideo in CosmosAssembler.cs var xPreBootLogging = true; if (xPreBootLogging) { UInt32 xVideo = 0xB8000; for (UInt32 i = xVideo; i < xVideo + 80 * 2; i = i + 2) { XS.SetByte(i, 0); XS.SetByte(i + 1, 2); } foreach (var xChar in aTokens[1].RawValue) { XS.SetByte(xVideo, (byte)xChar); xVideo = xVideo + 2; } } }); } /// Fix issue #15660. This method escapes double quotes in the candidate string. /// The string to be sanitized. /// The original string with escaped double quotes. private static string EscapeBackQuotes(string from) { StringBuilder builder = new StringBuilder(); bool sanitized = false; bool escaped = false; foreach (char scannedCharacter in from) { switch (scannedCharacter) { case '\\': escaped = !escaped; break; case '`': if (!escaped) { sanitized = true; builder.Append('\\'); } escaped = false; break; default: escaped = false; break; } builder.Append(scannedCharacter); } return (sanitized) ? builder.ToString() : from; } protected Pattern FindMatch(TokenList aTokens) { int xHash = aTokens.GetPatternHashCode(); // Get a list of matching hashes, but then we have to // search for exact pattern match because it is possible // to have duplicate hashes. Hashes just provide us a quick way // to reduce the search. foreach (var xPattern in mPatterns.Where(q => q.Hash == xHash)) { if (xPattern.Tokens.PatternMatches(aTokens)) { return xPattern; } } return null; } public bool GetPatternCode(TokenList aTokens) { var xPattern = FindMatch(aTokens); if (xPattern == null) { return false; } xPattern.Code(aTokens); //// Apply {0} etc into string //// This happens twice for block code, but its ok because the first pass //// strips out all tags. //for (int i = 0; i < xResult.Code.Count; i++) { // xResult.Code[i] = string.Format(xResult.Code[i], aTokens.ToArray()); //} return true; } public bool GetNonPatternCode(TokenList aTokens) { if (aTokens.Count == 0) { return false; } var xFirst = aTokens[0]; var xLast = aTokens[aTokens.Count - 1]; // Find match and emit X# if (aTokens.Count == 2 && xFirst.Type == TokenType.AlphaNum && xLast.Matches("()")) { // () could be handled by pattern, but best to keep in one place for future //xResult += "Call " + GroupLabel(aTokens[0].Value); XS.Call(GroupLabel(aTokens[0].RawValue)); } return true; } public bool GetCode(string aLine, int lineNumber) { var xParser = new Parser(aLine, lineNumber, false, false); var xTokens = xParser.Tokens; var xResult = GetPatternCode(xTokens); if (!xResult) { if (!GetNonPatternCode(xTokens)) { return false; } } return true; } /// Register a single pattern with its associated transformation handler. /// A single line of X# code that define the pattern optionally using /// pattern reserved syntax. /// The associated code transformation handler. protected void AddPattern(string aPattern, CodeFunc aCode) { Parser xParser = null; try { xParser = new Parser(aPattern, 1, false, true); } catch (Exception e) { throw new Exception(string.Format("Invalid pattern '{0}'", aPattern ?? "NULL"), e); } var xPattern = new Pattern(xParser.Tokens, aCode, aPattern); mPatterns.Add(xPattern); } /// Register a collection of patterns that share a single transformation handler. /// /// A collection of X# lines of code. Each line of code define a /// pattern optionally using the pattern reserved syntax. /// The code transformation handler that is common abmongst all the /// patterns from the collection. protected void AddPattern(string[] aPatterns, CodeFunc aCode) { foreach (var xPattern in aPatterns) { AddPattern(xPattern, aCode); } } } }