From dd93b7d2b912e4e8a41b73327fbf023e3d08b855 Mon Sep 17 00:00:00 2001 From: Matthijs ter Woord Date: Sun, 24 May 2015 16:33:21 +0200 Subject: [PATCH] Initial keyboard work. Does not work due to bug in the compiler. (Ldelem_ref) --- source/Cosmos.HAL/.editorconfig | 2 + source/Cosmos.HAL/Cosmos.HAL.csproj | 2 + source/Cosmos.HAL/DefaultKeyboard.cs | 480 ++++++++++++++++++ source/Cosmos.HAL/Global.cs | 12 +- source/Cosmos.HAL/Keyboard.cs | 467 ++--------------- source/Cosmos.IL2CPU/AppAssembler.cs | 103 ++-- source/Cosmos.IL2CPU/IL/Ldelem_Ref.cs | 6 +- source/Cosmos.IL2CPU/ILOpCode.cs | 3 +- .../Cosmos.System.Plugs/System/ConsoleImpl.cs | 37 +- source/Cosmos.System/.editorconfig | 2 + source/Cosmos.System/Cosmos.System.csproj | 1 + source/Cosmos.sln.DotSettings | 3 +- 12 files changed, 628 insertions(+), 490 deletions(-) create mode 100644 source/Cosmos.HAL/.editorconfig create mode 100644 source/Cosmos.HAL/DefaultKeyboard.cs create mode 100644 source/Cosmos.System/.editorconfig diff --git a/source/Cosmos.HAL/.editorconfig b/source/Cosmos.HAL/.editorconfig new file mode 100644 index 000000000..2788264c4 --- /dev/null +++ b/source/Cosmos.HAL/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +indent_size=4 diff --git a/source/Cosmos.HAL/Cosmos.HAL.csproj b/source/Cosmos.HAL/Cosmos.HAL.csproj index b4350830d..104b84f2b 100644 --- a/source/Cosmos.HAL/Cosmos.HAL.csproj +++ b/source/Cosmos.HAL/Cosmos.HAL.csproj @@ -80,6 +80,7 @@ + @@ -145,6 +146,7 @@ + diff --git a/source/Cosmos.HAL/DefaultKeyboard.cs b/source/Cosmos.HAL/DefaultKeyboard.cs new file mode 100644 index 000000000..fd8aa0947 --- /dev/null +++ b/source/Cosmos.HAL/DefaultKeyboard.cs @@ -0,0 +1,480 @@ +using System; +using System.Collections.Generic; + +namespace Cosmos.HAL +{ + //public delegate void HandleKeyboardDelegate(byte aScanCode, bool aReleased); + + public class DefaultKeyboard: Keyboard + { + + protected override void Initialize() + { + if (mKeys == null) + { + CreateDefaultKeymap(); + } + } + + private bool mEscaped; + private List mKeys; + private bool mShiftState; + private bool mCtrlState; + private bool mAltState; + + public bool ShiftPressed + { + get + { + return mShiftState; + } + } + + public bool CtrlPressed + { + get + { + return mCtrlState; + } + } + + public bool AltPressed + { + get + { + return mAltState; + } + } + + protected override void HandleScancode(byte aScancode, bool aReleased) + { + uint xTheScancode = aScancode; + if (mEscaped) + { + xTheScancode = (ushort)(xTheScancode << 8); + mEscaped = false; + } + switch (xTheScancode) + { + case 0x36: + case 0x2A: + { + mShiftState = !aReleased; + break; + } + case 0x1D: + { + mCtrlState = !aReleased; + break; + } + case 0x38: + { + mAltState = !aReleased; + break; + } + default: + { + if ((mCtrlState) && (mAltState) && (xTheScancode == 0x53)) + { + Console.WriteLine("Detected Ctrl-Alt-Delete! Rebooting System..."); + Core.Global.CPU.Reboot(); + } + if (mShiftState) + { + xTheScancode = xTheScancode << 16; + } + if (!aReleased) + { + ConsoleKeyInfo xKeyInfo; + if (!GetKey(xTheScancode, out xKeyInfo)) + { + //DebugUtil.SendError("Keyboard", "error while getting scancode character!"); + } + else + { + //DebugUtil.SendDoubleNumber("Keyboard", "Scancode and Char", xTheScancode, 32, xTheChar, 16); + Enqueue(xKeyInfo); + } + } + break; + } + } + } + + private void CreateDefaultKeymap() + { + mKeys = new List(164); + + //TODO: fn (for laptops) + + #region Letters + + AddKey(0x10, 'q', ConsoleKey.Q); + AddKey(0x100000, 'Q', ConsoleKey.Q); + AddKey(0x11, 'w', ConsoleKey.W); + AddKey(0x110000, 'W', ConsoleKey.W); + AddKey(0x12, 'e', ConsoleKey.E); + AddKey(0x120000, 'E', ConsoleKey.E); + AddKey(0x13, 'r', ConsoleKey.R); + AddKey(0x130000, 'R', ConsoleKey.R); + AddKey(0x14, 't', ConsoleKey.T); + AddKey(0x140000, 'T', ConsoleKey.T); + AddKey(0x15, 'y', ConsoleKey.Y); + AddKey(0x150000, 'Y', ConsoleKey.Y); + AddKey(0x16, 'u', ConsoleKey.U); + AddKey(0x160000, 'U', ConsoleKey.U); + AddKey(0x17, 'i', ConsoleKey.I); + AddKey(0x170000, 'I', ConsoleKey.I); + AddKey(0x18, 'o', ConsoleKey.O); + AddKey(0x180000, 'O', ConsoleKey.O); + AddKey(0x19, 'p', ConsoleKey.P); + AddKey(0x190000, 'P', ConsoleKey.P); + + AddKey(0x1E, 'a', ConsoleKey.A); + AddKey(0x1E0000, 'A', ConsoleKey.A); + AddKey(0x1F, 's', ConsoleKey.S); + AddKey(0x1F0000, 'S', ConsoleKey.S); + AddKey(0x20, 'd', ConsoleKey.D); + AddKey(0x200000, 'D', ConsoleKey.D); + AddKey(0x21, 'f', ConsoleKey.F); + AddKey(0x210000, 'F', ConsoleKey.F); + AddKey(0x22, 'g', ConsoleKey.G); + AddKey(0x220000, 'G', ConsoleKey.G); + AddKey(0x23, 'h', ConsoleKey.H); + AddKey(0x230000, 'H', ConsoleKey.H); + AddKey(0x24, 'j', ConsoleKey.J); + AddKey(0x240000, 'J', ConsoleKey.J); + AddKey(0x25, 'k', ConsoleKey.K); + AddKey(0x250000, 'K', ConsoleKey.K); + AddKey(0x26, 'l', ConsoleKey.L); + AddKey(0x260000, 'L', ConsoleKey.L); + + AddKey(0x2C, 'z', ConsoleKey.Z); + AddKey(0x2C0000, 'Z', ConsoleKey.Z); + AddKey(0x2D, 'x', ConsoleKey.X); + AddKey(0x2D0000, 'X', ConsoleKey.X); + AddKey(0x2E, 'c', ConsoleKey.C); + AddKey(0x2E0000, 'C', ConsoleKey.C); + AddKey(0x2F, 'v', ConsoleKey.V); + AddKey(0x2F0000, 'V', ConsoleKey.V); + AddKey(0x30, 'b', ConsoleKey.B); + AddKey(0x300000, 'B', ConsoleKey.B); + AddKey(0x31, 'n', ConsoleKey.N); + AddKey(0x310000, 'N', ConsoleKey.N); + AddKey(0x32, 'm', ConsoleKey.M); + AddKey(0x320000, 'M', ConsoleKey.M); + + #endregion + + #region digits + + //AddKey(0x1, '`'); + //AddKey(0x10000, '~'); + AddKey(0x29, '`', ConsoleKey.NoName); + AddKey(0x290000, '~', ConsoleKey.NoName); + AddKey(0x2, '1', ConsoleKey.D1); + AddKey(0x20000, '!', ConsoleKey.D1); + AddKey(0x3, '2', ConsoleKey.D2); + AddKey(0x30000, '@', ConsoleKey.D2); + AddKey(0x4, '3', ConsoleKey.D3); + AddKey(0x40000, '#', ConsoleKey.D3); + AddKey(0x5, '4', ConsoleKey.D4); + AddKey(0x50000, '$', ConsoleKey.D5); + AddKey(0x6, '5', ConsoleKey.D5); + AddKey(0x60000, '%', ConsoleKey.D5); + AddKey(0x7, '6', ConsoleKey.D6); + AddKey(0x70000, '^', ConsoleKey.D6); + AddKey(0x8, '7', ConsoleKey.D7); + AddKey(0x80000, '&', ConsoleKey.D7); + AddKey(0x9, '8', ConsoleKey.D8); + AddKey(0x90000, '*', ConsoleKey.D8); + AddKey(0xA, '9', ConsoleKey.D9); + AddKey(0xA0000, '(', ConsoleKey.D9); + AddKey(0xB, '0', ConsoleKey.D0); + AddKey(0xB0000, ')', ConsoleKey.D0); + + #endregion + + #region Special + + AddKeyWithShift(0x0E, '\u0968', ConsoleKey.Backspace); //Backspace + AddKeyWithShift(0x0F, '\t', ConsoleKey.Tab); //Tabulator + AddKeyWithShift(0x1C, '\n', ConsoleKey.Enter); //Enter + AddKeyWithShift(0x39, ' ', ConsoleKey.Spacebar); //Space + AddKeyWithShift(0x4b, '\u2190', ConsoleKey.LeftArrow); //Left arrow + AddKeyWithShift(0x48, '\u2191', ConsoleKey.UpArrow); //Up arrow + AddKeyWithShift(0x4d, '\u2192', ConsoleKey.RightArrow); //Right arrow + AddKeyWithShift(0x50, '\u2193', ConsoleKey.DownArrow); //Down arrow + + AddKeyWithShift(0x5b, ConsoleKey.LeftWindows); + AddKeyWithShift(0x5c, ConsoleKey.RightWindows); + //AddKey(0x5d, ConsoleKey.NoName); //Context Menu + + AddKeyWithShift(0x52, ConsoleKey.Insert); + AddKeyWithShift(0x47, ConsoleKey.Home); + AddKeyWithShift(0x49, ConsoleKey.PageUp); + AddKeyWithShift(0x53, ConsoleKey.Delete); + AddKeyWithShift(0x4f, ConsoleKey.End); + AddKeyWithShift(0x51, ConsoleKey.PageDown); + + AddKeyWithShift(0x37, ConsoleKey.PrintScreen); + //AddKeyWithShift(0x46, ConsoleKey.NoName); //Scroll Lock + //AddKeyWithShift(0x3a, ConsoleKey.NoName); //Caps Lock + AddKeyWithShift(0x45, ConsoleKey.Pause); + + AddKeyWithShift(0x3b, ConsoleKey.F1); + AddKeyWithShift(0x3c, ConsoleKey.F2); + AddKeyWithShift(0x3d, ConsoleKey.F3); + AddKeyWithShift(0x3e, ConsoleKey.F4); + AddKeyWithShift(0x3f, ConsoleKey.F5); + AddKeyWithShift(0x40, ConsoleKey.F6); + AddKeyWithShift(0x41, ConsoleKey.F7); + AddKeyWithShift(0x42, ConsoleKey.F8); + AddKeyWithShift(0x43, ConsoleKey.F9); + AddKeyWithShift(0x44, ConsoleKey.F10); + AddKeyWithShift(0x57, ConsoleKey.F11); + AddKeyWithShift(0x58, ConsoleKey.F12); + + AddKeyWithShift(0x1, ConsoleKey.Escape); + + #endregion + + #region Punctuation and Signs + + AddKey(0x27, ';', ConsoleKey.NoName); + AddKey(0x270000, ':', ConsoleKey.NoName); + AddKey(0x28, '\'', ConsoleKey.NoName); + AddKey(0x280000, '"', ConsoleKey.NoName); + AddKey(0x2B, '\\', ConsoleKey.NoName); + AddKey(0x2B0000, '|', ConsoleKey.NoName); + AddKey(0x33, ',', ConsoleKey.OemComma); + AddKey(0x330000, '<', ConsoleKey.OemComma); + AddKey(0x34, '.', ConsoleKey.OemPeriod); + AddKey(0x340000, '>', ConsoleKey.OemPeriod); + AddKey(0x35, '/', ConsoleKey.Divide); + AddKey(0x350000, '?', ConsoleKey.Divide); + //AddKey(0x4A, '-'); + AddKey(0x0C, '-', ConsoleKey.Subtract); + AddKey(0x0C0000, '_', ConsoleKey.Subtract); + AddKey(0x0D, '=', ConsoleKey.OemPlus); + AddKey(0x0D0000, '+', ConsoleKey.OemPlus); + //AddKey(0x4E, '+'); + AddKey(0x1A, '[', ConsoleKey.NoName); + AddKey(0x1A0000, '{', ConsoleKey.NoName); + AddKey(0x1B, ']', ConsoleKey.NoName); + AddKey(0x1B0000, '}', ConsoleKey.NoName); + + AddKeyWithShift(0x4c, '5', ConsoleKey.NumPad5); + + AddKeyWithShift(0x4a, '-', ConsoleKey.OemMinus); + AddKeyWithShift(0x4e, '+', ConsoleKey.OemPlus); + + AddKeyWithShift(0x37, '*', ConsoleKey.Multiply); + + #endregion + } + + private void AddKey(uint p, char p_2, ConsoleKey p_3) + { + mKeys.Add(new KeyMapping(p, p_2, p_3)); + } + + private void AddKeyWithShift(uint p, char p_2, ConsoleKey p_3) + { + AddKey(p, p_2, p_3); + AddKey(p << 16, p_2, p_3); + } + + private void AddKey(uint p, ConsoleKey p_3) + { + AddKey(p, '\0', p_3); + } + + private void AddKeyWithShift(uint p, ConsoleKey p_3) + { + AddKeyWithShift(p, '\0', p_3); + } + + public void ChangeKeyMap(List aKeys) + { + mKeys = aKeys; + } + + public bool GetCharValue(uint aScanCode, out char aValue) + { + for (int i = 0; i < mKeys.Count; i++) + { + //if (i == 0) { + // Console.Write("ScanCode in KeyMapping: "); + // Interrupts.WriteNumber(mKeys[i].Scancode, 32); + // Console.WriteLine(""); + //} + if (mKeys[i].Scancode == aScanCode) + { + if (mKeys[i].Value != '\0') + { + aValue = mKeys[i].Value; + return true; + } + break; + } + } + + aValue = '\0'; + return false; + } + + public bool GetKeyValue(uint aScanCode, out ConsoleKey aValue) + { + for (int i = 0; i < mKeys.Count; i++) + { + if (mKeys[i].Scancode == aScanCode) + { + aValue = mKeys[i].Key; + return true; + } + } + + aValue = ConsoleKey.NoName; + return false; + } + + public bool GetKeyMapping(uint aScanCode, out KeyMapping aValue) + { + for (int i = 0; i < mKeys.Count; i++) + { + + if (mKeys[i].Scancode == aScanCode) + { + aValue = mKeys[i]; + return true; + } + } + + aValue = null; + return false; + } + + //public char ReadChar() + //{ + // char xResult = '\0'; + // while (mBuffer.Count == 0 || !GetCharValue(mBuffer.Dequeue(), out xResult)) + // { + // //Global.Sleep(10); //ToDo optimize value + // if (Core.Global.CPU == null) + // { + // return '\0'; + // } + // Core.Global.CPU.Halt(); + // } + // return xResult; + //} + + //public bool GetChar(out char c) + //{ + // c = '\0'; + + // if (mBuffer.Count > 0) + // { + // GetCharValue(mBuffer.Dequeue(), out c); + // return true; + // } + // else + // { + // return false; + // } + //} + + //public ConsoleKey ReadKey() + //{ + // ConsoleKey xResult = ConsoleKey.NoName; + // while (mBuffer.Count == 0 || !GetKeyValue(mBuffer.Dequeue(), out xResult)) + // { + // //Global.Sleep(10); //ToDo optimize value + // Core.Global.CPU.Halt(); + // } + // return xResult; + //} + + public bool GetKey(uint aScancode, out ConsoleKeyInfo keyInfo) + { + ConsoleKey xKey; + + if (!GetKeyValue(aScancode, out xKey)) + { + keyInfo = new ConsoleKeyInfo(); + return false; + } + + char xChar; + if (!GetCharValue(aScancode, out xChar)) + { + keyInfo = new ConsoleKeyInfo(); + return false; + } + + keyInfo = new ConsoleKeyInfo(xChar, xKey, ShiftPressed, AltPressed, ControlPressed); + return true; + } + + //public bool GetMapping(out KeyMapping c) + //{ + // c = null; + + // if (mBuffer.Count > 0) + // { + // GetKeyMapping(mBuffer.Dequeue(), out c); + // return true; + // } + // else + // { + // return false; + // } + //} + + //public uint ReadScancode() + //{ + // while (mBuffer.Count == 0) + // { + // Core.Global.CPU.Halt(); + // } + + // return mBuffer.Dequeue(); + //} + + //public bool GetScancode(out uint c) + //{ + // if (mBuffer.Count > 0) + // { + // c = mBuffer.Dequeue(); + // return true; + // } + // else + // { + // c = 0; + // return false; + // } + //} + + public class KeyMapping + { + public uint Scancode; + public char Value; + public ConsoleKey Key; + + public KeyMapping(uint aScanCode, char aValue, ConsoleKey aKey) + { + Scancode = aScanCode; + Value = aValue; + Key = aKey; + } + + public KeyMapping(uint aScanCode, ConsoleKey aKey) + { + Scancode = aScanCode; + Value = '\0'; + Key = aKey; + } + } + } +} diff --git a/source/Cosmos.HAL/Global.cs b/source/Cosmos.HAL/Global.cs index 17e867d5b..a391e12ff 100644 --- a/source/Cosmos.HAL/Global.cs +++ b/source/Cosmos.HAL/Global.cs @@ -18,7 +18,7 @@ namespace Cosmos.HAL { var xIO = aControllerID == BlockDevice.Ata.ControllerIdEnum.Primary ? Cosmos.Core.Global.BaseIOGroups.ATA1 : Cosmos.Core.Global.BaseIOGroups.ATA2; var xATA = new BlockDevice.AtaPio(xIO, aControllerID, aBusPosition); if (xATA.DriveType != BlockDevice.AtaPio.SpecLevel.Null) { - BlockDevice.BlockDevice.Devices.Add(xATA); + BlockDevice.BlockDevice.Devices.Add(xATA); var xMbrData = new byte[512]; xATA.ReadBlock(0UL, 1U, xMbrData); var xMBR = new BlockDevice.MBR(xMbrData); @@ -64,21 +64,21 @@ namespace Cosmos.HAL { static internal void InitStaticDevices() { //TextScreen = new TextScreen(); Global.Dbg.Send("CLS"); - + TextScreen.Clear(); Global.Dbg.Send("Keyboard"); - Keyboard = new Keyboard(); + Keyboard = new DefaultKeyboard(); // Find hardcoded ATA controllers Global.Dbg.Send("ATA Master"); InitAta(BlockDevice.Ata.ControllerIdEnum.Primary, BlockDevice.Ata.BusPositionEnum.Master); //Global.Dbg.Send("ATA Slave"); - //InitAta(BlockDevice.Ata.ControllerIdEnum.Primary, BlockDevice.Ata.BusPositionEnum.Slave); + //InitAta(BlockDevice.Ata.ControllerIdEnum.Primary, BlockDevice.Ata.BusPositionEnum.Slave); - //TODO Need to change code to detect if ATA controllers are present or not. How to do this? via PCI enum? - // They do show up in PCI space as well as the fixed space. + //TODO Need to change code to detect if ATA controllers are present or not. How to do this? via PCI enum? + // They do show up in PCI space as well as the fixed space. // Or is it always here, and was our compiler stack corruption issue? //InitAta(BlockDevice.Ata.ControllerIdEnum.Secondary, BlockDevice.Ata.BusPositionEnum.Master); //InitAta(BlockDevice.Ata.ControllerIdEnum.Secondary, BlockDevice.Ata.BusPositionEnum.Slave); diff --git a/source/Cosmos.HAL/Keyboard.cs b/source/Cosmos.HAL/Keyboard.cs index 7da68d218..177bfea3a 100644 --- a/source/Cosmos.HAL/Keyboard.cs +++ b/source/Cosmos.HAL/Keyboard.cs @@ -4,439 +4,78 @@ using System.Linq; using System.Text; namespace Cosmos.HAL { - public delegate void HandleKeyboardDelegate(byte aScanCode, bool aReleased); - public class Keyboard : Device { + public abstract class Keyboard : Device { + // TODO: MtW: I don't like the following line in the baseclass, but for now, lets keep it here. protected Core.IOGroup.Keyboard IO = Core.Global.BaseIOGroups.Keyboard; + protected Keyboard() + { + mQueuedKeys = new Queue(); - private HandleKeyboardDelegate mHandleKeyboardKey; - public void Initialize(HandleKeyboardDelegate aHandleKeyboardKeyDelegate) { - mHandleKeyboardKey = aHandleKeyboardKeyDelegate; - } - - public Keyboard() { - mBuffer = new Queue(BufferSize); - - Initialize(HandleScancode); + Initialize(); Core.INTs.SetIrqHandler(0x01, HandleIRQ); - // TODO: Need to add support for mult keyboards. ie one in PS2 and one in USB, or even more + } - if (mKeys == null) { - CreateDefaultKeymap(); + /// + /// Initialize the device. Happens before the interrupt is registered, ie before the class is being used. + /// + protected abstract void Initialize(); + + private void HandleIRQ(ref Core.INTs.IRQContext aContext) + { + byte xScanCode = IO.Port60.Byte; + bool xReleased = (xScanCode & 0x80) == 0x80; + if (xReleased) + { + xScanCode = (byte)(xScanCode ^ 0x80); } + HandleScancode(xScanCode, xReleased); } - public void HandleIRQ(ref Core.INTs.IRQContext aContext) { - if (mHandleKeyboardKey != null) { - byte xScanCode = IO.Port60.Byte; - bool xReleased = (xScanCode & 0x80) == 0x80; - if (xReleased) { - xScanCode = (byte)(xScanCode ^ 0x80); - } - mHandleKeyboardKey(xScanCode, xReleased); + protected abstract void HandleScancode(byte aScancode, bool aReleased); + + private readonly Queue mQueuedKeys; + + protected void Enqueue(ConsoleKeyInfo aKey) + { + mQueuedKeys.Enqueue(aKey); + } + + public bool TryReadKey(out ConsoleKeyInfo oKey) + { + if (mQueuedKeys.Count > 0) + { + oKey = mQueuedKeys.Dequeue(); + return true; } - } - - private Queue mBuffer; - private const int BufferSize = 64; - private bool mEscaped; - private List mKeys; - private bool mShiftState; - private bool mCtrlState; - private bool mAltState; - - public bool ShiftPressed { - get { - return mShiftState; - } - } - public bool CtrlPressed { - get { - return mCtrlState; - } - } - public bool AltPressed { - get { - return mAltState; - } - } - - protected void HandleScancode(byte aScancode, bool aReleased) { - uint xTheScancode = aScancode; - if (mEscaped) { - xTheScancode = (ushort)(xTheScancode << 8); - mEscaped = false; - } - switch (xTheScancode) { - case 0x36: - case 0x2A: { - mShiftState = !aReleased; - break; - } - case 0x1D: { - mCtrlState = !aReleased; - break; - } - case 0x38: { - mAltState = !aReleased; - break; - } - default: { - if ((mCtrlState) && (mAltState) && (xTheScancode == 0x53)) { - Console.WriteLine("Detected Ctrl-Alt-Delete! Rebooting System..."); - Core.Global.CPU.Reboot(); - } - if (mShiftState) { - xTheScancode = xTheScancode << 16; - } - if (mBuffer.Count < BufferSize) { - if (!aReleased) { - char xTheChar; - if (!GetCharValue(xTheScancode, out xTheChar)) { - //DebugUtil.SendError("Keyboard", "error while getting scancode character!"); - } else { - //DebugUtil.SendDoubleNumber("Keyboard", "Scancode and Char", xTheScancode, 32, xTheChar, 16); - } - mBuffer.Enqueue(xTheScancode); - } - } - break; - } - } - } - - // Can merge HandleScancode after we remove old code - // Remove the static.. Make it a real class - protected void ByteReceived(byte aValue) { - bool xReleased = (aValue & 0x80) == 0x80; - if (xReleased) { - aValue = (byte)(aValue ^ 0x80); - } - mHandleKeyboardKey(aValue, xReleased); - } - - private void CreateDefaultKeymap() { - mKeys = new List(164); - - //TODO: fn (for laptops) - - #region Letters - AddKey(0x10, 'q', ConsoleKey.Q); - AddKey(0x100000, 'Q', ConsoleKey.Q); - AddKey(0x11, 'w', ConsoleKey.W); - AddKey(0x110000, 'W', ConsoleKey.W); - AddKey(0x12, 'e', ConsoleKey.E); - AddKey(0x120000, 'E', ConsoleKey.E); - AddKey(0x13, 'r', ConsoleKey.R); - AddKey(0x130000, 'R', ConsoleKey.R); - AddKey(0x14, 't', ConsoleKey.T); - AddKey(0x140000, 'T', ConsoleKey.T); - AddKey(0x15, 'y', ConsoleKey.Y); - AddKey(0x150000, 'Y', ConsoleKey.Y); - AddKey(0x16, 'u', ConsoleKey.U); - AddKey(0x160000, 'U', ConsoleKey.U); - AddKey(0x17, 'i', ConsoleKey.I); - AddKey(0x170000, 'I', ConsoleKey.I); - AddKey(0x18, 'o', ConsoleKey.O); - AddKey(0x180000, 'O', ConsoleKey.O); - AddKey(0x19, 'p', ConsoleKey.P); - AddKey(0x190000, 'P', ConsoleKey.P); - - AddKey(0x1E, 'a', ConsoleKey.A); - AddKey(0x1E0000, 'A', ConsoleKey.A); - AddKey(0x1F, 's', ConsoleKey.S); - AddKey(0x1F0000, 'S', ConsoleKey.S); - AddKey(0x20, 'd', ConsoleKey.D); - AddKey(0x200000, 'D', ConsoleKey.D); - AddKey(0x21, 'f', ConsoleKey.F); - AddKey(0x210000, 'F', ConsoleKey.F); - AddKey(0x22, 'g', ConsoleKey.G); - AddKey(0x220000, 'G', ConsoleKey.G); - AddKey(0x23, 'h', ConsoleKey.H); - AddKey(0x230000, 'H', ConsoleKey.H); - AddKey(0x24, 'j', ConsoleKey.J); - AddKey(0x240000, 'J', ConsoleKey.J); - AddKey(0x25, 'k', ConsoleKey.K); - AddKey(0x250000, 'K', ConsoleKey.K); - AddKey(0x26, 'l', ConsoleKey.L); - AddKey(0x260000, 'L', ConsoleKey.L); - - AddKey(0x2C, 'z', ConsoleKey.Z); - AddKey(0x2C0000, 'Z', ConsoleKey.Z); - AddKey(0x2D, 'x', ConsoleKey.X); - AddKey(0x2D0000, 'X', ConsoleKey.X); - AddKey(0x2E, 'c', ConsoleKey.C); - AddKey(0x2E0000, 'C', ConsoleKey.C); - AddKey(0x2F, 'v', ConsoleKey.V); - AddKey(0x2F0000, 'V', ConsoleKey.V); - AddKey(0x30, 'b', ConsoleKey.B); - AddKey(0x300000, 'B', ConsoleKey.B); - AddKey(0x31, 'n', ConsoleKey.N); - AddKey(0x310000, 'N', ConsoleKey.N); - AddKey(0x32, 'm', ConsoleKey.M); - AddKey(0x320000, 'M', ConsoleKey.M); - #endregion - - #region digits - //AddKey(0x1, '`'); - //AddKey(0x10000, '~'); - AddKey(0x29, '`', ConsoleKey.NoName); - AddKey(0x290000, '~', ConsoleKey.NoName); - AddKey(0x2, '1', ConsoleKey.D1); - AddKey(0x20000, '!', ConsoleKey.D1); - AddKey(0x3, '2', ConsoleKey.D2); - AddKey(0x30000, '@', ConsoleKey.D2); - AddKey(0x4, '3', ConsoleKey.D3); - AddKey(0x40000, '#', ConsoleKey.D3); - AddKey(0x5, '4', ConsoleKey.D4); - AddKey(0x50000, '$', ConsoleKey.D5); - AddKey(0x6, '5', ConsoleKey.D5); - AddKey(0x60000, '%', ConsoleKey.D5); - AddKey(0x7, '6', ConsoleKey.D6); - AddKey(0x70000, '^', ConsoleKey.D6); - AddKey(0x8, '7', ConsoleKey.D7); - AddKey(0x80000, '&', ConsoleKey.D7); - AddKey(0x9, '8', ConsoleKey.D8); - AddKey(0x90000, '*', ConsoleKey.D8); - AddKey(0xA, '9', ConsoleKey.D9); - AddKey(0xA0000, '(', ConsoleKey.D9); - AddKey(0xB, '0', ConsoleKey.D0); - AddKey(0xB0000, ')', ConsoleKey.D0); - - #endregion - - #region Special - AddKeyWithShift(0x0E, '\u0968', ConsoleKey.Backspace); //Backspace - AddKeyWithShift(0x0F, '\t', ConsoleKey.Tab); //Tabulator - AddKeyWithShift(0x1C, '\n', ConsoleKey.Enter); //Enter - AddKeyWithShift(0x39, ' ', ConsoleKey.Spacebar); //Space - AddKeyWithShift(0x4b, '\u2190', ConsoleKey.LeftArrow); //Left arrow - AddKeyWithShift(0x48, '\u2191', ConsoleKey.UpArrow); //Up arrow - AddKeyWithShift(0x4d, '\u2192', ConsoleKey.RightArrow); //Right arrow - AddKeyWithShift(0x50, '\u2193', ConsoleKey.DownArrow); //Down arrow - - AddKeyWithShift(0x5b, ConsoleKey.LeftWindows); - AddKeyWithShift(0x5c, ConsoleKey.RightWindows); - //AddKey(0x5d, ConsoleKey.NoName); //Context Menu - - AddKeyWithShift(0x52, ConsoleKey.Insert); - AddKeyWithShift(0x47, ConsoleKey.Home); - AddKeyWithShift(0x49, ConsoleKey.PageUp); - AddKeyWithShift(0x53, ConsoleKey.Delete); - AddKeyWithShift(0x4f, ConsoleKey.End); - AddKeyWithShift(0x51, ConsoleKey.PageDown); - - AddKeyWithShift(0x37, ConsoleKey.PrintScreen); - //AddKeyWithShift(0x46, ConsoleKey.NoName); //Scroll Lock - //AddKeyWithShift(0x3a, ConsoleKey.NoName); //Caps Lock - AddKeyWithShift(0x45, ConsoleKey.Pause); - - AddKeyWithShift(0x3b, ConsoleKey.F1); - AddKeyWithShift(0x3c, ConsoleKey.F2); - AddKeyWithShift(0x3d, ConsoleKey.F3); - AddKeyWithShift(0x3e, ConsoleKey.F4); - AddKeyWithShift(0x3f, ConsoleKey.F5); - AddKeyWithShift(0x40, ConsoleKey.F6); - AddKeyWithShift(0x41, ConsoleKey.F7); - AddKeyWithShift(0x42, ConsoleKey.F8); - AddKeyWithShift(0x43, ConsoleKey.F9); - AddKeyWithShift(0x44, ConsoleKey.F10); - AddKeyWithShift(0x57, ConsoleKey.F11); - AddKeyWithShift(0x58, ConsoleKey.F12); - - AddKeyWithShift(0x1, ConsoleKey.Escape); - #endregion - - #region Punctuation and Signs - AddKey(0x27, ';', ConsoleKey.NoName); - AddKey(0x270000, ':', ConsoleKey.NoName); - AddKey(0x28, '\'', ConsoleKey.NoName); - AddKey(0x280000, '"', ConsoleKey.NoName); - AddKey(0x2B, '\\', ConsoleKey.NoName); - AddKey(0x2B0000, '|', ConsoleKey.NoName); - AddKey(0x33, ',', ConsoleKey.OemComma); - AddKey(0x330000, '<', ConsoleKey.OemComma); - AddKey(0x34, '.', ConsoleKey.OemPeriod); - AddKey(0x340000, '>', ConsoleKey.OemPeriod); - AddKey(0x35, '/', ConsoleKey.Divide); - AddKey(0x350000, '?', ConsoleKey.Divide); - //AddKey(0x4A, '-'); - AddKey(0x0C, '-', ConsoleKey.Subtract); - AddKey(0x0C0000, '_', ConsoleKey.Subtract); - AddKey(0x0D, '=', ConsoleKey.OemPlus); - AddKey(0x0D0000, '+', ConsoleKey.OemPlus); - //AddKey(0x4E, '+'); - AddKey(0x1A, '[', ConsoleKey.NoName); - AddKey(0x1A0000, '{', ConsoleKey.NoName); - AddKey(0x1B, ']', ConsoleKey.NoName); - AddKey(0x1B0000, '}', ConsoleKey.NoName); - - AddKeyWithShift(0x4c, '5', ConsoleKey.NumPad5); - - AddKeyWithShift(0x4a, '-', ConsoleKey.OemMinus); - AddKeyWithShift(0x4e, '+', ConsoleKey.OemPlus); - - AddKeyWithShift(0x37, '*', ConsoleKey.Multiply); - #endregion - } - - private uint KeyCount = 0; - - private void AddKey(uint p, char p_2, ConsoleKey p_3) { - mKeys.Add(new KeyMapping(p, p_2, p_3)); - KeyCount++; - } - private void AddKeyWithShift(uint p, char p_2, ConsoleKey p_3) { - AddKey(p, p_2, p_3); - AddKey(p << 16, p_2, p_3); - } - private void AddKey(uint p, ConsoleKey p_3) { - AddKey(p, '\0', p_3); - } - private void AddKeyWithShift(uint p, ConsoleKey p_3) { - AddKeyWithShift(p, '\0', p_3); - } - - public void ChangeKeyMap(List aKeys) { - mKeys = aKeys; - } - - public bool GetCharValue(uint aScanCode, out char aValue) { - for (int i = 0; i < mKeys.Count; i++) { - //if (i == 0) { - // Console.Write("ScanCode in KeyMapping: "); - // Interrupts.WriteNumber(mKeys[i].Scancode, 32); - // Console.WriteLine(""); - //} - if (mKeys[i].Scancode == aScanCode) { - if (mKeys[i].Value != '\0') { - aValue = mKeys[i].Value; - return true; - } - break; - } - } - - aValue = '\0'; - return false; - } - public bool GetKeyValue(uint aScanCode, out ConsoleKey aValue) { - for (int i = 0; i < mKeys.Count; i++) { - if (mKeys[i].Scancode == aScanCode) { - aValue = mKeys[i].Key; - return true; - } - } - - aValue = ConsoleKey.NoName; - return false; - } - public bool GetKeyMapping(uint aScanCode, out KeyMapping aValue) { - for (int i = 0; i < mKeys.Count; i++) { - - if (mKeys[i].Scancode == aScanCode) { - aValue = mKeys[i]; - return true; - } - } - - aValue = null; + oKey = default(ConsoleKeyInfo); return false; } - public char ReadChar() { - char xResult = '\0'; - while (mBuffer.Count == 0 || !GetCharValue(mBuffer.Dequeue(), out xResult)) { - //Global.Sleep(10); //ToDo optimize value - if (Core.Global.CPU == null) - { - return '\0'; - } + public ConsoleKeyInfo ReadKey() + { + while (mQueuedKeys.Count == 0) + { Core.Global.CPU.Halt(); } - return xResult; + return mQueuedKeys.Dequeue(); } - public bool GetChar(out char c) { - c = '\0'; - - if (mBuffer.Count > 0) { - GetCharValue(mBuffer.Dequeue(), out c); - return true; - } else { - return false; - } + public bool ShiftPressed + { + get; + protected set; } - public ConsoleKey ReadKey() { - ConsoleKey xResult = ConsoleKey.NoName; - while (mBuffer.Count == 0 || !GetKeyValue(mBuffer.Dequeue(), out xResult)) { - //Global.Sleep(10); //ToDo optimize value - Core.Global.CPU.Halt(); - } - return xResult; - } - public bool GetKey(out ConsoleKey c) { - c = ConsoleKey.NoName; - - if (mBuffer.Count > 0) { - GetKeyValue(mBuffer.Dequeue(), out c); - return true; - } else { - return false; - } + public bool ControlPressed + { + get; + protected set; } - public KeyMapping ReadMapping() { - KeyMapping xResult = null; - while (mBuffer.Count == 0 || !GetKeyMapping(mBuffer.Dequeue(), out xResult)) { - //Global.Sleep(10); //ToDo optimize value - Core.Global.CPU.Halt(); - } - return xResult; - } - public bool GetMapping(out KeyMapping c) { - c = null; - - if (mBuffer.Count > 0) { - GetKeyMapping(mBuffer.Dequeue(), out c); - return true; - } else { - return false; - } - } - - public uint ReadScancode() { - while (mBuffer.Count == 0) { - Core.Global.CPU.Halt(); - } - - return mBuffer.Dequeue(); - } - public bool GetScancode(out uint c) { - if (mBuffer.Count > 0) { - c = mBuffer.Dequeue(); - return true; - } else { - c = 0; - return false; - } - } - - public class KeyMapping { - public uint Scancode; - public char Value; - public ConsoleKey Key; - - public KeyMapping(uint aScanCode, char aValue, ConsoleKey aKey) { - Scancode = aScanCode; - Value = aValue; - Key = aKey; - } - public KeyMapping(uint aScanCode, ConsoleKey aKey) { - Scancode = aScanCode; - Value = '\0'; - Key = aKey; - } + public bool AltPressed + { + get; + protected set; } } } diff --git a/source/Cosmos.IL2CPU/AppAssembler.cs b/source/Cosmos.IL2CPU/AppAssembler.cs index 7fecc1afe..e01246cb8 100644 --- a/source/Cosmos.IL2CPU/AppAssembler.cs +++ b/source/Cosmos.IL2CPU/AppAssembler.cs @@ -445,73 +445,80 @@ namespace Cosmos.IL2CPU public void ProcessMethod(MethodInfo aMethod, List aOpCodes) { - // We check this here and not scanner as when scanner makes these - // plugs may still have not yet been scanned that it will depend on. - // But by the time we make it here, they have to be resolved. - if (aMethod.Type == MethodInfo.TypeEnum.NeedsPlug && aMethod.PlugMethod == null) + try { - throw new Exception("Method needs plug, but no plug was assigned."); - } + // We check this here and not scanner as when scanner makes these + // plugs may still have not yet been scanned that it will depend on. + // But by the time we make it here, they have to be resolved. + if (aMethod.Type == MethodInfo.TypeEnum.NeedsPlug && aMethod.PlugMethod == null) + { + throw new Exception("Method needs plug, but no plug was assigned."); + } - // todo: MtW: how to do this? we need some extra space. - // see ConstructLabel for extra info - if (aMethod.UID > 0x00FFFFFF) - { - throw new Exception("Too many methods."); - } + // todo: MtW: how to do this? we need some extra space. + // see ConstructLabel for extra info + if (aMethod.UID > 0x00FFFFFF) + { + throw new Exception("Too many methods."); + } - MethodBegin(aMethod); - mLog.WriteLine("Method '{0}', ID = '{1}'", aMethod.MethodBase.GetFullName(), aMethod.UID); - mLog.Flush(); - if (aMethod.MethodAssembler != null) - { - var xAssembler = (AssemblerMethod)Activator.CreateInstance(aMethod.MethodAssembler); - xAssembler.AssembleNew(Assembler, aMethod.PluggedMethod); - } - else if (aMethod.IsInlineAssembler) - { - aMethod.MethodBase.Invoke("", new object[aMethod.MethodBase.GetParameters().Length]); - } - else - { - // now emit the actual assembler code for this method. + MethodBegin(aMethod); + mLog.WriteLine("Method '{0}', ID = '{1}'", aMethod.MethodBase.GetFullName(), aMethod.UID); + mLog.Flush(); + if (aMethod.MethodAssembler != null) + { + var xAssembler = (AssemblerMethod)Activator.CreateInstance(aMethod.MethodAssembler); + xAssembler.AssembleNew(Assembler, aMethod.PluggedMethod); + } + else if (aMethod.IsInlineAssembler) + { + aMethod.MethodBase.Invoke("", new object[aMethod.MethodBase.GetParameters().Length]); + } + else + { + // now emit the actual assembler code for this method. - //Conditions under which we should emit an INT3 instead of a plceholder NOP: - /* - First instruction in a Method / Loop / If / Else etc. + //Conditions under which we should emit an INT3 instead of a plceholder NOP: + /* - First instruction in a Method / Loop / If / Else etc. * -- In essence, whenever there is a opening { * -- C# Debug builds automatically insert NOPs at these locations (otherwise NOP is not used) * -- So only insert an INT3 when we are about to insert a NOP that came from IL code */ - /* We group opcodes together by logical statement. Each statement will have its logical stack cleared. + /* We group opcodes together by logical statement. Each statement will have its logical stack cleared. * Also, this lets us do optimizations later on. */ - bool emitINT3 = true; - DebugInfo.SequencePoint xPreviousSequencePoint = null; - var xCurrentGroup = new List(); - ILOpCode.ILInterpretationDebugLine(() => String.Format("Method: {0}", aMethod.MethodBase.GetFullName())); - foreach (var xRawOpcode in aOpCodes) - { - var xSP = mSequences.FirstOrDefault(q => q.Offset == xRawOpcode.Position && q.LineStart != 0xFEEFEE); - // detect if we're at a new statement. - if (xPreviousSequencePoint == null && xSP != null) + bool emitINT3 = true; + DebugInfo.SequencePoint xPreviousSequencePoint = null; + var xCurrentGroup = new List(); + ILOpCode.ILInterpretationDebugLine(() => String.Format("Method: {0}", aMethod.MethodBase.GetFullName())); + foreach (var xRawOpcode in aOpCodes) { + var xSP = mSequences.FirstOrDefault(q => q.Offset == xRawOpcode.Position && q.LineStart != 0xFEEFEE); + // detect if we're at a new statement. + if (xPreviousSequencePoint == null && xSP != null) + { + } + if (xSP != null && xCurrentGroup.Count > 0) + { + EmitInstructions(aMethod, xCurrentGroup, ref emitINT3); + xCurrentGroup.Clear(); + xPreviousSequencePoint = xSP; + } + xCurrentGroup.Add(xRawOpcode); } - if (xSP != null && xCurrentGroup.Count > 0) + if (xCurrentGroup.Count > 0) { EmitInstructions(aMethod, xCurrentGroup, ref emitINT3); - xCurrentGroup.Clear(); - xPreviousSequencePoint = xSP; } - xCurrentGroup.Add(xRawOpcode); - } - if (xCurrentGroup.Count > 0) - { - EmitInstructions(aMethod, xCurrentGroup, ref emitINT3); } + MethodEnd(aMethod); + } + catch (Exception E) + { + throw new Exception("Error compiling method '" + aMethod.MethodBase.GetFullName() + "': " + E.ToString(), E); } - MethodEnd(aMethod); } private void BeforeEmitInstructions(MethodInfo aMethod, List aCurrentGroup) diff --git a/source/Cosmos.IL2CPU/IL/Ldelem_Ref.cs b/source/Cosmos.IL2CPU/IL/Ldelem_Ref.cs index 1c29b9567..750848c00 100644 --- a/source/Cosmos.IL2CPU/IL/Ldelem_Ref.cs +++ b/source/Cosmos.IL2CPU/IL/Ldelem_Ref.cs @@ -14,8 +14,8 @@ namespace Cosmos.IL2CPU.X86.IL public static void Assemble(Cosmos.Assembler.Assembler aAssembler, uint aElementSize,bool isSigned , bool debugEnabled) { DoNullReferenceCheck(aAssembler, debugEnabled, 4); - if (aElementSize <= 0 || aElementSize > 8 || (aElementSize > 4 && aElementSize < 8)) - throw new Exception("Unsupported size for Ldelem_Ref: " + aElementSize); + if (aElementSize <= 0 || aElementSize > 8 || (aElementSize > 4 && aElementSize < 8)) + throw new Exception("Unsupported size for Ldelem_Ref: " + aElementSize); new CPUx86.Pop { DestinationReg = CPUx86.Registers.EAX }; new CPUx86.Mov { DestinationReg = CPUx86.Registers.EDX, SourceValue = aElementSize }; @@ -58,4 +58,4 @@ namespace Cosmos.IL2CPU.X86.IL Assemble( Assembler, 4, false, DebugEnabled); } } -} \ No newline at end of file +} diff --git a/source/Cosmos.IL2CPU/ILOpCode.cs b/source/Cosmos.IL2CPU/ILOpCode.cs index 022a9e32d..d5e14348e 100644 --- a/source/Cosmos.IL2CPU/ILOpCode.cs +++ b/source/Cosmos.IL2CPU/ILOpCode.cs @@ -370,7 +370,8 @@ namespace Cosmos.IL2CPU { aSituationChanged = true; } if (StackPopTypes[i] != xActualStackItem - && !StackPopTypes[i].IsAssignableFrom(xActualStackItem)) + && !StackPopTypes[i].IsAssignableFrom(xActualStackItem) + && !((StackPopTypes[i].IsPointer || StackPopTypes[i].IsByRef) && (xActualStackItem.IsPointer || xActualStackItem.IsByRef))) { throw new Exception(String.Format("OpCode {0} tries to pop item at stack position {1} with type {2}, but actual type is {3}", this, i, StackPopTypes[i], xActualStackItem)); diff --git a/source/Cosmos.System.Plugs/System/ConsoleImpl.cs b/source/Cosmos.System.Plugs/System/ConsoleImpl.cs index a90d3d6a3..3bd835006 100644 --- a/source/Cosmos.System.Plugs/System/ConsoleImpl.cs +++ b/source/Cosmos.System.Plugs/System/ConsoleImpl.cs @@ -303,25 +303,28 @@ namespace Cosmos.System.Plugs.System { public static int Read() { // TODO special cases, if needed, that returns -1 - return HAL.Global.Keyboard.ReadChar(); + ConsoleKeyInfo xResult; + + if (HAL.Global.Keyboard.TryReadKey(out xResult)) + { + return xResult.KeyChar; + } + else + { + return -1; + } } // ReadKey() pure CIL public static ConsoleKeyInfo ReadKey(Boolean intercept) { - var key = Cosmos.HAL.Global.Keyboard.ReadMapping(); - var returnValue = new ConsoleKeyInfo( - key.Value, - key.Key, - Cosmos.HAL.Global.Keyboard.ShiftPressed, - Cosmos.HAL.Global.Keyboard.AltPressed, - Cosmos.HAL.Global.Keyboard.CtrlPressed); + var key = Cosmos.HAL.Global.Keyboard.ReadKey(); if (false == intercept) { - Write(returnValue.KeyChar); + Write(key.KeyChar); } - return returnValue; + return key; } public static String ReadLine() { @@ -332,13 +335,13 @@ namespace Cosmos.System.Plugs.System { return null; } List chars = new List(32); - char current; + ConsoleKeyInfo current; int currentCount = 0; - while ((current = HAL.Global.Keyboard.ReadChar()) != '\n') + while ((current = HAL.Global.Keyboard.ReadKey()).KeyChar != '\n') { //Check for "special" keys - if (current == '\u0968') // Backspace + if (current.Key == ConsoleKey.Backspace) // Backspace { if (currentCount > 0) { @@ -360,7 +363,7 @@ namespace Cosmos.System.Plugs.System { } continue; } - else if (current == '\u2190') // Arrow Left + else if (current.Key == ConsoleKey.LeftArrow) { if (currentCount > 0) { @@ -369,7 +372,7 @@ namespace Cosmos.System.Plugs.System { } continue; } - else if (current == '\u2192') // Arrow Right + else if (current.Key == ConsoleKey.RightArrow) { if (currentCount < chars.Count) { @@ -382,7 +385,7 @@ namespace Cosmos.System.Plugs.System { //Write the character to the screen if (currentCount == chars.Count) { - chars.Add(current); + chars.Add(current.KeyChar); Write(current); currentCount++; } @@ -397,7 +400,7 @@ namespace Cosmos.System.Plugs.System { { if (x == currentCount) { - temp.Add(current); + temp.Add(current.KeyChar); } temp.Add(chars[x]); diff --git a/source/Cosmos.System/.editorconfig b/source/Cosmos.System/.editorconfig new file mode 100644 index 000000000..2788264c4 --- /dev/null +++ b/source/Cosmos.System/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +indent_size=4 diff --git a/source/Cosmos.System/Cosmos.System.csproj b/source/Cosmos.System/Cosmos.System.csproj index fc00459ca..496cfe0e7 100644 --- a/source/Cosmos.System/Cosmos.System.csproj +++ b/source/Cosmos.System/Cosmos.System.csproj @@ -119,6 +119,7 @@ + diff --git a/source/Cosmos.sln.DotSettings b/source/Cosmos.sln.DotSettings index 977e24a2c..77ee8b377 100644 --- a/source/Cosmos.sln.DotSettings +++ b/source/Cosmos.sln.DotSettings @@ -1,2 +1,3 @@  - GDB \ No newline at end of file + GDB + IRQ \ No newline at end of file