diff --git a/src/Chip8.Core/Assembly/AsmException.cs b/src/Chip8.Core/Assembly/AsmException.cs new file mode 100644 index 0000000..b55d58e --- /dev/null +++ b/src/Chip8.Core/Assembly/AsmException.cs @@ -0,0 +1,16 @@ +using System; + +namespace Chip8.Core.Assembly; + +public sealed class AsmException : Exception +{ + public AsmException(string message, SourcePosition position) + : base("assembly error at " + position + ": " + message) + { + this.reason = message; + this.position = position; + } + + public string reason { get; } + public SourcePosition position { get; } +} diff --git a/src/Chip8.Core/Assembly/Operand.cs b/src/Chip8.Core/Assembly/Operand.cs new file mode 100644 index 0000000..fd8d256 --- /dev/null +++ b/src/Chip8.Core/Assembly/Operand.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; + +namespace Chip8.Core.Assembly; + +public enum OperandKind +{ + Register, + RegisterRange, + Immediate, + IndexRegister, + IndexIndirect, + DelayTimer, + SoundTimer, + KeyReg, + FontReg, + BigFontReg, + BcdReg, + RplReg, +} + +public sealed class Operand +{ + Operand(OperandKind kind, SourcePosition position) + { + this.kind = kind; + this.position = position; + } + + public OperandKind kind { get; } + public SourcePosition position { get; } + public int registerIndex { get; private init; } + public int rangeEnd { get; private init; } + public IReadOnlyList expressionTokens { get; private init; } = System.Array.Empty(); + + public static Operand register(int index, SourcePosition position) => + new(OperandKind.Register, position) { registerIndex = index }; + + public static Operand registerRange(int start, int end, SourcePosition position) => + new(OperandKind.RegisterRange, position) { registerIndex = start, rangeEnd = end }; + + public static Operand immediate(IReadOnlyList tokens, SourcePosition position) => + new(OperandKind.Immediate, position) { expressionTokens = tokens }; + + public static Operand special(OperandKind kind, SourcePosition position) => new(kind, position); + + public bool isRegister(int index) => kind == OperandKind.Register && registerIndex == index; + + public override string ToString() => kind switch + { + OperandKind.Register => "V" + registerIndex.ToString("X1"), + OperandKind.RegisterRange => "V" + registerIndex.ToString("X1") + "-V" + rangeEnd.ToString("X1"), + OperandKind.Immediate => "imm", + OperandKind.IndexIndirect => "[I]", + _ => kind.ToString(), + }; +} diff --git a/src/Chip8.Core/Assembly/Statement.cs b/src/Chip8.Core/Assembly/Statement.cs new file mode 100644 index 0000000..3405827 --- /dev/null +++ b/src/Chip8.Core/Assembly/Statement.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; + +namespace Chip8.Core.Assembly; + +public abstract class Statement +{ + protected Statement(SourcePosition position) + { + this.position = position; + } + + public SourcePosition position { get; } +} + +public sealed class LabelStatement : Statement +{ + public LabelStatement(string name, SourcePosition position) : base(position) + { + this.name = name; + } + + public string name { get; } +} + +public sealed class ConstantStatement : Statement +{ + public ConstantStatement(string name, IReadOnlyList expression, SourcePosition position) : base(position) + { + this.name = name; + this.expression = expression; + } + + public string name { get; } + public IReadOnlyList expression { get; } +} + +public sealed class OrgStatement : Statement +{ + public OrgStatement(IReadOnlyList expression, SourcePosition position) : base(position) + { + this.expression = expression; + } + + public IReadOnlyList expression { get; } +} + +public sealed class AlignStatement : Statement +{ + public AlignStatement(IReadOnlyList expression, SourcePosition position) : base(position) + { + this.expression = expression; + } + + public IReadOnlyList expression { get; } +} + +public sealed class DataStatement : Statement +{ + public DataStatement(bool word, IReadOnlyList> items, SourcePosition position) : base(position) + { + this.word = word; + this.items = items; + } + + public bool word { get; } + public IReadOnlyList> items { get; } +} + +public sealed class InstructionStatement : Statement +{ + public InstructionStatement(string mnemonic, IReadOnlyList operands, SourcePosition position) : base(position) + { + this.mnemonic = mnemonic; + this.operands = operands; + } + + public string mnemonic { get; } + public IReadOnlyList operands { get; } +} diff --git a/src/Chip8.Core/Assembly/Token.cs b/src/Chip8.Core/Assembly/Token.cs new file mode 100644 index 0000000..28365aa --- /dev/null +++ b/src/Chip8.Core/Assembly/Token.cs @@ -0,0 +1,61 @@ +namespace Chip8.Core.Assembly; + +public enum TokenKind +{ + Identifier, + Number, + Comma, + Colon, + Minus, + Plus, + Star, + Slash, + Percent, + Ampersand, + Pipe, + Caret, + Tilde, + ShiftLeft, + ShiftRight, + LeftParen, + RightParen, + LeftBracket, + RightBracket, + EndOfLine, + EndOfFile, +} + +public readonly struct SourcePosition +{ + public SourcePosition(int line, int column) + { + this.line = line; + this.column = column; + } + + public int line { get; } + public int column { get; } + + public override string ToString() => "line " + line + ", col " + column; +} + +public readonly struct Token +{ + public Token(TokenKind kind, string text, int value, SourcePosition position) + { + this.kind = kind; + this.text = text; + this.value = value; + this.position = position; + } + + public TokenKind kind { get; } + public string text { get; } + public int value { get; } + public SourcePosition position { get; } + + public bool isIdentifier(string name) => + kind == TokenKind.Identifier && string.Equals(text, name, System.StringComparison.OrdinalIgnoreCase); + + public override string ToString() => kind + "('" + text + "')"; +}