Skip to content
This repository has been archived by the owner on Jun 24, 2024. It is now read-only.

Console.Print, PrintLine and Clear methods #18

Merged
merged 3 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 228 additions & 26 deletions src/PatienceOS.Kernel.Tests/ConsoleTests.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,228 @@
using Xunit;

namespace PatienceOS.Kernel.Tests
{
unsafe public class ConsoleTests
{
[Fact]
public void Console_Should_Write_Hello()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
var frameBuffer = new FrameBuffer(buffer);
var console = new Console(80, 25, frameBuffer);

// When
console.Print("Hello");

// Then
Assert.Equal((byte)'H', frameBuffer.Fetch(0));
Assert.Equal((byte)'e', frameBuffer.Fetch(2));
Assert.Equal((byte)'l', frameBuffer.Fetch(4));
Assert.Equal((byte)'l', frameBuffer.Fetch(6));
Assert.Equal((byte)'o', frameBuffer.Fetch(8));
}
}
}
using Xunit;

namespace PatienceOS.Kernel.Tests
{
unsafe public class ConsoleTests
{
[Fact]
public void Should_Write_AB()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
var frameBuffer = new FrameBuffer(buffer);
var console = new Console(80, 25, frameBuffer);

// When
console.Print("AB");

// Then
Assert.Equal((byte)'A', buffer[0]);
Assert.Equal((byte)'B', buffer[2]);
}

[Fact]
public void Should_Clear_AB()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
var frameBuffer = new FrameBuffer(buffer);
var console = new Console(80, 25, frameBuffer);

// When
console.Print("AB");
console.Clear();

// Then
Assert.Equal((byte)' ', buffer[0]);
Assert.Equal((byte)' ', buffer[2]);
}

[Fact]
public void Should_Write_A_EOL_B()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
var frameBuffer = new FrameBuffer(buffer);
var console = new Console(80, 25, frameBuffer);

// When
console.Print("A\nB");

// Then
Assert.Equal((byte)'A', buffer[0]);
Assert.Equal((byte)'B', buffer[80 * 2 + 0]);
}

[Fact]
public void Should_Clear_A_EOL_B()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
var frameBuffer = new FrameBuffer(buffer);
var console = new Console(80, 25, frameBuffer);

// When
console.Print("A\nB");
console.Clear();

// Then
Assert.Equal((byte)' ', buffer[0]);
Assert.Equal((byte)' ', buffer[80 * 2 + 0]);
}

unsafe public class ThreeXThree
{
[Fact]
public void Should_Write_ABC_EOL_DEF_EOL_GHI()
{
// Given
byte* buffer = stackalloc byte[3 * 3 * 2];
var frameBuffer = new FrameBuffer(buffer);
var console = new Console(3, 3, frameBuffer);

// When
console.Print("ABCDEFGHI");

// Then
Assert.Equal((byte)'A', buffer[0]);
Assert.Equal((byte)'B', buffer[2]);
Assert.Equal((byte)'C', buffer[4]);
Assert.Equal((byte)'D', buffer[6]);
Assert.Equal((byte)'E', buffer[8]);
Assert.Equal((byte)'F', buffer[10]);
Assert.Equal((byte)'G', buffer[12]);
Assert.Equal((byte)'H', buffer[14]);
Assert.Equal((byte)'I', buffer[16]);
}
}

unsafe public class HelloWorld
{
[Fact]
public void Should_Write_Hello_Space_World_With_Print_Statement()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
var frameBuffer = new FrameBuffer(buffer);
var console = new Console(80, 25, frameBuffer);

// When
console.Print("Hello World");

// Then
Assert.Equal((byte)'H', buffer[0]);
Assert.Equal((byte)'e', buffer[2]);
Assert.Equal((byte)'l', buffer[4]);
Assert.Equal((byte)'l', buffer[6]);
Assert.Equal((byte)'o', buffer[8]);
Assert.Equal((byte)' ', buffer[10]);
Assert.Equal((byte)'W', buffer[12]);
Assert.Equal((byte)'o', buffer[14]);
Assert.Equal((byte)'r', buffer[16]);
Assert.Equal((byte)'l', buffer[18]);
Assert.Equal((byte)'d', buffer[20]);
}

[Fact]
public void Should_Write_Hello_Space_World_With_Print_Statements()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
var frameBuffer = new FrameBuffer(buffer);
var console = new Console(80, 25, frameBuffer);

// When
console.Print("Hello");
console.Print(" ");
console.Print("World");

// Then
Assert.Equal((byte)'H', buffer[0]);
Assert.Equal((byte)'e', buffer[2]);
Assert.Equal((byte)'l', buffer[4]);
Assert.Equal((byte)'l', buffer[6]);
Assert.Equal((byte)'o', buffer[8]);
Assert.Equal((byte)' ', buffer[10]);
Assert.Equal((byte)'W', buffer[12]);
Assert.Equal((byte)'o', buffer[14]);
Assert.Equal((byte)'r', buffer[16]);
Assert.Equal((byte)'l', buffer[18]);
Assert.Equal((byte)'d', buffer[20]);
}

[Fact]
public void Should_Write_Hello_EOL_World_EOL_With_Print_Statement()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
var frameBuffer = new FrameBuffer(buffer);
var console = new Console(80, 25, frameBuffer);

// When
console.Print("Hello\nWorld\n");

// Then
Assert.Equal((byte)'H', buffer[0]);
Assert.Equal((byte)'e', buffer[2]);
Assert.Equal((byte)'l', buffer[4]);
Assert.Equal((byte)'l', buffer[6]);
Assert.Equal((byte)'o', buffer[8]);
Assert.Equal((byte)'W', buffer[80 * 2 + 0]);
Assert.Equal((byte)'o', buffer[80 * 2 + 2]);
Assert.Equal((byte)'r', buffer[80 * 2 + 4]);
Assert.Equal((byte)'l', buffer[80 * 2 + 6]);
Assert.Equal((byte)'d', buffer[80 * 2 + 8]);
}

[Fact]
public void Should_Write_Hello_EOL_World_EOL_With_Print_Statements()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
var frameBuffer = new FrameBuffer(buffer);
var console = new Console(80, 25, frameBuffer);

// When
console.Print("Hello\n");
console.Print("World\n");

// Then
Assert.Equal((byte)'H', buffer[0]);
Assert.Equal((byte)'e', buffer[2]);
Assert.Equal((byte)'l', buffer[4]);
Assert.Equal((byte)'l', buffer[6]);
Assert.Equal((byte)'o', buffer[8]);
Assert.Equal((byte)'W', buffer[80 * 2 + 0]);
Assert.Equal((byte)'o', buffer[80 * 2 + 2]);
Assert.Equal((byte)'r', buffer[80 * 2 + 4]);
Assert.Equal((byte)'l', buffer[80 * 2 + 6]);
Assert.Equal((byte)'d', buffer[80 * 2 + 8]);
}

[Fact]
public void Should_Write_Hello_EOL_World_EOL_With_PrintLine_Statements()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
var frameBuffer = new FrameBuffer(buffer);
var console = new Console(80, 25, frameBuffer);

// When
console.PrintLine("Hello");
console.PrintLine("World");

// Then
Assert.Equal((byte)'H', buffer[0]);
Assert.Equal((byte)'e', buffer[2]);
Assert.Equal((byte)'l', buffer[4]);
Assert.Equal((byte)'l', buffer[6]);
Assert.Equal((byte)'o', buffer[8]);
Assert.Equal((byte)'W', buffer[80 * 2 + 0]);
Assert.Equal((byte)'o', buffer[80 * 2 + 2]);
Assert.Equal((byte)'r', buffer[80 * 2 + 4]);
Assert.Equal((byte)'l', buffer[80 * 2 + 6]);
Assert.Equal((byte)'d', buffer[80 * 2 + 8]);
}
}
}
}
10 changes: 5 additions & 5 deletions src/PatienceOS.Kernel.Tests/FrameBufferTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ public void FrameBuffer_Should_Contain_Hello()
frameBuffer.Write(4, (byte)'o');

// Then
Assert.Equal((byte)'H', frameBuffer.Fetch(0));
Assert.Equal((byte)'e', frameBuffer.Fetch(1));
Assert.Equal((byte)'l', frameBuffer.Fetch(2));
Assert.Equal((byte)'l', frameBuffer.Fetch(3));
Assert.Equal((byte)'o', frameBuffer.Fetch(4));
Assert.Equal((byte)'H', buffer[0]);
Assert.Equal((byte)'e', buffer[1]);
Assert.Equal((byte)'l', buffer[2]);
Assert.Equal((byte)'l', buffer[3]);
Assert.Equal((byte)'o', buffer[4]);
}
}
}
53 changes: 47 additions & 6 deletions src/PatienceOS.Kernel/Console.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ unsafe public struct Console

private FrameBuffer frameBuffer;

private int pos = 0;
private int column = 0; // nb. Incremented by two for each write, to account for the text colouring
private int row = 0;

private byte foregroundColor = 0x0F; // White

public Console(int width, int height, FrameBuffer frameBuffer)
Expand All @@ -25,27 +27,66 @@ public Console(int width, int height, FrameBuffer frameBuffer)
/// </summary>
public void Clear()
{
for (int i = 0; i < width * height * 2; i++)
for (int i = 0; i < width * height * 2; i += 2)
{
frameBuffer.Write(i, 0);
// Write directly to the video memory
frameBuffer.Write(i, (byte)' ');
frameBuffer.Write(i + 1, foregroundColor);
}

// Reset the cursor position
column = 0;
row = 0;
}

/// <summary>
/// Print a string to the current cursor position
/// </summary>
/// <remarks>
/// Assumes each screen character is represented by two bytes aligned as a 16-bit word,
/// see <see cref="https://en.wikipedia.org/wiki/VGA_text_mode#Data_arrangement"/>
/// </remarks>
public void Print(string s)
{
fixed (char* ps = s)
{
for (int i = 0; i < s.Length; i++)
{
frameBuffer.Write(pos, (byte)ps[i]);
frameBuffer.Write(pos + 1, foregroundColor);
// Perform a CRLF if we encounter a Newline character
if (ps[i] == '\n')
{
column = 0;
row++;

continue;
}

// Perform a CRLF when the cursor reaches the end of the terminal line
// eg.column is 0 to 79, width = 80
if (column >= width * 2)
{
column = 0;
row++;
}

// Write directly to the video memory
frameBuffer.Write(row * width * 2 + column, (byte)ps[i]);
frameBuffer.Write(row * width * 2 + column + 1, foregroundColor);

pos += 2;
// Move the cursor right by one
column += 2;
}
}
}

/// <summary>
/// Prints a string to the current cursor position
/// and then moves the cursor to the next line
/// </summary>
public void PrintLine(string s)
{
Print(s);
Print("\n");
}
}
}
5 changes: 0 additions & 5 deletions src/PatienceOS.Kernel/FrameBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ public FrameBuffer(byte* buffer)
this.buffer = buffer;
}

public byte Fetch(int position)
{
return buffer[position];
}

public void Write(int position, byte value)
{
buffer[position] = value;
Expand Down