diff --git a/.gitignore b/.gitignore
index fcd4f09..52ff572 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,5 @@ package-lock.json
assembler/**/*.js
!assembler/ldt/**
c/
+target/
+Cargo.lock
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..17c89cf
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "editor.rulers": [100]
+}
diff --git a/assembler/index.html b/assembler/index.html
index 11d8415..c09c2f7 100644
--- a/assembler/index.html
+++ b/assembler/index.html
@@ -10,8 +10,10 @@
-
@@ -24,6 +26,7 @@
diff --git a/assembler/main.ts b/assembler/main.ts
index c9c2a33..e88c1a0 100644
--- a/assembler/main.ts
+++ b/assembler/main.ts
@@ -2,13 +2,14 @@ import * as bitUtils from './bit_utils.js';
import targetV8 from './targets/v8.js';
import targetParva_0_1 from './targets/parva_0_1.js';
+import targetBitzzy from './targets/bitzzy.js';
import targetLodestar from './targets/lodestar.js';
import * as targetTest from './targets/test/test.js';
import { ArchName, ArchSpecification, AssemblyInput, AssemblyOutput, InstructionOutputLine } from './assembly.js';
import { toNote } from './util.js';
const CONFIG_VERSION: string = '1';
-const EMULATOR_SPEED = 30;
+const EMULATOR_SPEED = 70;
window.addEventListener('load', init);
@@ -194,6 +195,8 @@ function getArch(name: string): ArchSpecification {
return targetV8;
} else if (name === 'parva_0_1') {
return targetParva_0_1;
+ } else if (name === 'bitzzy') {
+ return targetBitzzy;
} else if (name === 'lodestar') {
return targetLodestar as any;
} else {
diff --git a/assembler/targets/bitzzy.ts b/assembler/targets/bitzzy.ts
new file mode 100644
index 0000000..d1eb494
--- /dev/null
+++ b/assembler/targets/bitzzy.ts
@@ -0,0 +1,792 @@
+import type { OutputLine, AssemblyInput, ArchSpecification, AssemblyOutput, InputError, LineSource, Emulator } from '../assembly';
+import { toBitString } from '../util.js';
+
+export type Reference = {
+ sourceAddress: number;
+ addressRelative: boolean;
+ bitCount: number;
+ value: string;
+};
+
+type ParsedLabel = {
+ tag: 'label';
+ source: LineSource,
+ name: string;
+};
+
+type ParsedInstruction = {
+ tag: 'instruction';
+ source: LineSource,
+ parts: Array;
+ address: number;
+};
+
+export type ParsedLinePart = string | Reference;
+type ParsedLine = ParsedInstruction | ParsedLabel;
+
+function isNumber(value: string): boolean {
+ return toNumber(value) !== null;
+}
+
+function toNumber(value: string): number {
+ let result = null;
+ if (value.startsWith('$')) {
+ result = parseInt(value.slice(1), 16);
+ } else if (value.startsWith('%')) {
+ result = parseInt(value.slice(1), 2);
+ } else if (value.startsWith('@')) {
+ result = parseInt(value.slice(1), 8);
+ } else {
+ result = parseInt(value, 10);
+ }
+ if (isNaN(result)) {
+ return null;
+ }
+ return result;
+}
+
+type Binding = { tag: 'binding', name: string };
+
+const NUMBER_RE = String.raw`%-?[01][01_]*|\$-?[\da-f][\da-f_]*|@-?[0-7][0-7_]*|-?\d[\d_]*`;
+const VALUE_ATOM_RE = String.raw`(?:${NUMBER_RE}|[a-z0-9_-]+)`;
+const VALUE_PAIR_RE = String.raw`\((${VALUE_ATOM_RE})\s*([\+\-])\s*(${VALUE_ATOM_RE})\)`;
+const VALUE_RE = String.raw`${VALUE_ATOM_RE}|${VALUE_PAIR_RE}`;
+const REGISTER_RE = 'x|y|z|X|Y|Z|YX|yx';
+
+class Program {
+ currentAddress: number;
+ output: Array;
+ labels: Map;
+ constants: Map;
+ outputEnabled: boolean | null;
+
+ constructor() {
+ this.currentAddress = 0;
+ this.output = [];
+ this.labels = new Map();
+ this.constants = new Map();
+ this.outputEnabled = null;
+ }
+
+ instruction(parts: Array, wordCount: number, source: LineSource, count: number) {
+ if (this.outputEnabled === null || this.outputEnabled) {
+ this.output.push({
+ tag: 'instruction',
+ source,
+ address: this.currentAddress,
+ parts,
+ });
+ }
+ this.currentAddress += wordCount * count;
+ }
+
+ label(name: string, source: LineSource) {
+ if (this.labels.has(name)) {
+ throw new Error(`Label '${name}' already defined`);
+ }
+ this.labels.set(name, this.currentAddress);
+ this.output.push({
+ tag: 'label',
+ source,
+ name,
+ });
+ }
+
+ address(value: number) {
+ this.currentAddress = value;
+ }
+
+ align(value: number) {
+ this.currentAddress = Math.ceil(this.currentAddress / value) * value;
+ }
+
+ enable() {
+ if (this.outputEnabled === null) {
+ this.output = [];
+ }
+ this.outputEnabled = true;
+ }
+
+ disable() {
+ this.outputEnabled = false;
+ }
+
+ data(values: Array, source: LineSource) {
+ const numbers: Array = values.map(n => this.value(n, 8, true));
+ for (const number of numbers) {
+ this.instruction([number], 1, { ...source, realInstruction: '(data)' }, 1);
+ }
+ return this;
+ }
+
+ constant(name: string, value: number) {
+ this.constants.set(name, value);
+ }
+
+ value(numberOrLabelText: string, bits: number, dynamic: boolean = false): ParsedLinePart {
+ if (isNumber(numberOrLabelText)) {
+ return toBitString(toNumber(numberOrLabelText), bits, false, 8);
+ } else {
+ return {
+ addressRelative: false,
+ sourceAddress: this.currentAddress,
+ bitCount: dynamic ? 16 : bits,
+ value: numberOrLabelText,
+ };
+ }
+ }
+
+ literal(numberText: string, bits: number, signed: boolean): number {
+ if (!isNumber(numberText)) {
+ throw new Error(`Expected number, got ${numberText}`);
+ } else {
+ return toNumber(numberText);
+ }
+ }
+
+ parseSourceLine(sourceLine: string, lineNumber: number) {
+ // Remove comments beginning with ; and #
+ let commentIndex = sourceLine.length;
+ if (sourceLine.indexOf(';') !== -1) {
+ commentIndex = Math.min(commentIndex, sourceLine.indexOf(';'));
+ }
+ if (sourceLine.indexOf('//') !== -1) {
+ commentIndex = Math.min(commentIndex, sourceLine.indexOf('//'));
+ }
+ const uncommented = sourceLine.slice(0, commentIndex).trim();
+ this.parse({
+ lineNumber,
+ realInstruction: uncommented,
+ sourceInstruction: uncommented,
+ sourceInstructionCommented: sourceLine,
+ });
+ }
+
+ parse(source: LineSource) {
+ const line = source.realInstruction;
+
+ if (line === '') {
+ return;
+ }
+
+ let matchFound = false;
+
+ const match = (...args: Array void)>) => {
+ if (matchFound) {
+ return;
+ }
+ const fn = args.pop() as any;
+ const expression = new RegExp('^' + args.join('') + '$', 'i');
+ const result = line.match(expression);
+ if (result === null) {
+ return;
+ }
+ fn(result.groups);
+ matchFound = true;
+ }
+ const i = (...parts: Array) => {
+ this.instruction(parts, 1, source, 1);
+ };
+ const i2 = (...parts: Array) => {
+ this.instruction(parts, 1, source, 2);
+ };
+ const i3 = (...parts: Array) => {
+ this.instruction(parts, 1, source, 3);
+ };
+ const i4 = (...parts: Array) => {
+ this.instruction(parts, 1, source, 4);
+ };
+ const pseudo = (realInstruction: string) => this.parse({ ...source, realInstruction });
+ const binding = (name: string): Binding => ({ tag: 'binding', name });
+ const r = binding('r');
+ const a = binding('a');
+ const b = binding('b');
+
+ const bindablePattern = (regex: string, prefix: string = '', suffix: string = '') => (binding: Binding) =>
+ `${prefix}(?<${binding.name}>${regex})${suffix}`;
+ const token = bindablePattern(VALUE_RE);
+ const register = bindablePattern(REGISTER_RE);
+ const immediate = bindablePattern(VALUE_RE, '#');
+ const remainder = bindablePattern('.*');
+
+ window['numberRegex'] = NUMBER_RE;
+
+ const s = String.raw`\s+`;
+ const so = String.raw`\s*`;
+ const sep = String.raw`\s*[,\s]\s*`;
+ const hardSep = String.raw`\s*,\s*`;
+
+ const addr = bindablePattern(VALUE_RE);
+ const registerAndA = bindablePattern(REGISTER_RE, '', `${so},${so}A`);
+ const valueAndA = bindablePattern(VALUE_RE, '', `${so},${so}A`);
+ const x = '[xX]';
+ const y = '[yY]';
+ const z = '[zZ]';
+ const yx = '[yY][xX]';
+
+ match('hlt', () => i('00000000'));
+ match('rti', () => i('00000010'));
+ match('eni', () => i('00000011'));
+ match('dsi', () => i('00000100'));
+ match('nop', () => i('00000101'));
+
+ match('rem', s, x, () => i('00001000'));
+ match('rem', s, y, () => i('00001001'));
+ match('rem', s, z, () => i('00001010'));
+ match('clr', () => i('00001011'));
+
+ match('jmp', s, addr(a), ({ a }) => { i3('00010000', this.value(a, 16)) });
+ match('jmp', s, addr(a), sep, x, ({ a }) => { i3('00010001', this.value(a, 16)) });
+ match('jmp', s, addr(a), sep, y, ({ a }) => { i3('00010010', this.value(a, 16)) });
+ match('jmp', s, addr(a), sep, z, ({ a }) => { i3('00010011', this.value(a, 16)) });
+
+ match('jsr', s, addr(a), ({ a }) => { i3('00010100', this.value(a, 16)) });
+ match('jsr', s, addr(a), sep, x, ({ a }) => { i3('00010101', this.value(a, 16)) });
+ match('jsr', s, addr(a), sep, y, ({ a }) => { i3('00010110', this.value(a, 16)) });
+ match('jsr', s, addr(a), sep, z, ({ a }) => { i3('00010111', this.value(a, 16)) });
+
+ match('swp', s, x, sep, y, () => { i('00011000') });
+ match('swp', s, y, sep, x, () => pseudo('SWP X, Y'));
+ match('swp', s, x, sep, z, () => { i('00011001') });
+ match('swp', s, z, sep, x, () => pseudo('SWP X, Z'));
+ match('swp', s, y, sep, z, () => { i('00011011') });
+ match('swp', s, z, sep, y, () => pseudo('SWP Y, Z'));
+
+ match('jmpez', s, x, sep, addr(a), ({ a }) => { i3('00100000', this.value(a, 16)) });
+ match('jmpez', s, z, sep, addr(a), ({ a }) => { i3('00100001', this.value(a, 16)) });
+ match('jmpez', s, register(r), sep, addr(a), () => { throw new Error('JMPEZ can only be used with registers X and Z') });
+ match('jmpgt', s, x, sep, y, sep, addr(a), ({ a }) => { i3('00100010', this.value(a, 16)) });
+ match('jmpgt', s, register(a), sep, register(b), sep, addr(r), () => { throw new Error('JMPGT can only be used with registers X and Y') });
+ match('jmpeq', s, x, sep, y, sep, addr(a), ({ a }) => { i3('00100011', this.value(a, 16)) });
+ match('jmpeq', s, y, sep, x, sep, addr(a), ({ a }) => pseudo(`JMPEQ X, Y, ${a}`));
+ match('jmpeq', s, x, sep, z, sep, addr(a), ({ a }) => { i3('11110010', this.value(a, 16)) });
+ match('jmpeq', s, z, sep, x, sep, addr(a), ({ a }) => pseudo(`JMPEQ X, Z, ${a}`));
+ match('jmpeq', s, y, sep, z, sep, addr(a), ({ a }) => { i3('11110011', this.value(a, 16)) });
+ match('jmpeq', s, z, sep, y, sep, addr(a), ({ a }) => pseudo(`JMPEQ Y, Z, ${a}`));
+ match('jmprez', s, addr(a), ({ a }) => { i3('11110000', this.value(a, 16)) });
+ match('jmprnz', s, addr(a), ({ a }) => { i3('11110001', this.value(a, 16)) });
+
+ match('jsrez', s, x, sep, addr(a), ({ a }) => { i3('00100100', this.value(a, 16)) });
+ match('jsrez', s, z, sep, addr(a), ({ a }) => { i3('00100101', this.value(a, 16)) });
+ match('jsrez', s, register(r), sep, addr(a), () => { throw new Error('JSREZ can only be used with registers X and Z') });
+ match('jsrgt', s, x, sep, y, sep, addr(a), ({ a }) => { i3('00100110', this.value(a, 16)) });
+ match('jsrgt', s, register(a), sep, register(b), sep, () => { throw new Error('JSRGT can only be used with registers X and Y') });
+ match('jsreq', s, x, sep, y, sep, addr(a),
+ ({ a }) => { i3('00100111', this.value(a, 16)) });
+ match('jsreq', s, y, sep, x, sep, addr(a), ({ a }) => pseudo(`JSREQ X, Y, ${a}`));
+ match('jsreq', s, register(a), sep, register(b), sep, addr(r), () => { throw new Error('JSREQ can only be used with registers X and Y') });
+
+ match('ret', () => i('00000001'));
+ match('retrez', () => i('11110100'));
+ match('retrnz', () => i('11110101'));
+
+ match('djnz', s, x, sep, addr(a), ({ a }) => i3('11100000', this.value(a, 16)));
+ match('djnz', s, y, sep, addr(a), ({ a }) => i3('11100001', this.value(a, 16)));
+ match('djnz', s, z, sep, addr(a), ({ a }) => i3('11100010', this.value(a, 16)));
+
+ match('lod', s, x, sep, immediate(a), ({ a }) => i2('01111100', this.value(a, 8)));
+ match('lod', s, y, sep, immediate(a), ({ a }) => i2('01111101', this.value(a, 8)));
+ match('lod', s, z, sep, immediate(a), ({ a }) => i2('01111110', this.value(a, 8)));
+ match('lod', s, y, sep, x, () => i('00101000'));
+ match('lod', s, z, sep, x, () => i('00101001'));
+ match('lod', s, x, sep, y, () => i('00101010'));
+ match('lod', s, z, sep, y, () => i('00101011'));
+ match('lod', s, x, sep, z, () => i('00101100'));
+ match('lod', s, y, sep, z, () => i('00101101'));
+
+ match('lod', s, x, sep, addr(a), ({ a }) => { i3('10100000', this.value(a, 16)) });
+ match('lod', s, y, sep, addr(a), ({ a }) => { i3('10110000', this.value(a, 16)) });
+ match('lod', s, z, sep, addr(a), ({ a }) => { i3('11000000', this.value(a, 16)) });
+ match('lod', s, x, sep, addr(a), sep, y,
+ ({ a }) => { i3('10100001', this.value(a, 16)) });
+ match('lod', s, x, sep, addr(a), sep, z,
+ ({ a }) => { i3('10100010', this.value(a, 16)) });
+ match('lod', s, y, sep, addr(a), sep, x,
+ ({ a }) => { i3('10110001', this.value(a, 16)) });
+ match('lod', s, y, sep, addr(a), sep, z,
+ ({ a }) => { i3('10110010', this.value(a, 16)) });
+ match('lod', s, z, sep, addr(a), sep, x,
+ ({ a }) => { i3('11000001', this.value(a, 16)) });
+ match('lod', s, z, sep, addr(a), sep, y,
+ ({ a }) => { i3('11000010', this.value(a, 16)) });
+
+ match('str', s, x, sep, addr(a), ({ a }) => { i3('10100100', this.value(a, 16)) });
+ match('str', s, y, sep, addr(a), ({ a }) => { i3('10110100', this.value(a, 16)) });
+ match('str', s, z, sep, addr(a), ({ a }) => { i3('11000100', this.value(a, 16)) });
+ match('str', s, x, sep, addr(a), sep, y,
+ ({ a }) => { i3('10100101', this.value(a, 16)) });
+ match('str', s, x, sep, addr(a), sep, z,
+ ({ a }) => { i3('10100110', this.value(a, 16)) });
+ match('str', s, y, sep, addr(a), sep, x,
+ ({ a }) => { i3('10110101', this.value(a, 16)) });
+ match('str', s, y, sep, addr(a), sep, z,
+ ({ a }) => { i3('10110110', this.value(a, 16)) });
+ match('str', s, z, sep, addr(a), sep, x,
+ ({ a }) => { i3('11000101', this.value(a, 16)) });
+ match('str', s, z, sep, addr(a), sep, y,
+ ({ a }) => { i3('11000110', this.value(a, 16)) });
+
+ match('str', s, immediate(a), sep, addr(b), sep, x,
+ ({ a, b }) => { i4('11010000', this.value(b, 16), this.value(a, 8)) });
+ match('str', s, immediate(a), sep, addr(b), sep, y,
+ ({ a, b }) => { i4('11010001', this.value(b, 16), this.value(a, 8)) });
+ match('str', s, immediate(a), sep, addr(b), sep, z,
+ ({ a, b }) => { i4('11010010', this.value(b, 16), this.value(a, 8)) });
+ match('str', s, immediate(a), sep, addr(b),
+ ({ a, b }) => { i4('11010011', this.value(b, 16), this.value(a, 8)) });
+ match('str', s, immediate(a), sep, addr(b), sep, yx,
+ ({ a, b }) => { i4('11010100', this.value(b, 16), this.value(a, 8)) });
+
+ match('lod', s, z, sep, addr(a), sep, yx,
+ ({ a }) => { i3('11000011', this.value(a, 16)) });
+ match('str', s, z, sep, addr(a), sep, yx,
+ ({ a }) => { i3('11000111', this.value(a, 16)) });
+
+ match('jmp', s, addr(a), ({ a }) => { i3('00010000', this.value(a, 16)) });
+
+ match('inc', s, x, () => i('00110000'));
+ match('inc', s, y, () => i('00110001'));
+ match('inc', s, z, () => i('00110010'));
+ match('inc', s, addr(a), ({ a }) => i3('00110011', this.value(a, 16)));
+
+ match('dec', s, x, () => i('00110100'));
+ match('dec', s, y, () => i('00110101'));
+ match('dec', s, z, () => i('00110110'));
+ match('dec', s, addr(a), ({ a }) => i3('00110111', this.value(a, 16)));
+
+ match('add', s, x, sep, immediate(a), ({ a }) => i2('01000000', this.value(a, 8)));
+ match('add', s, y, sep, immediate(a), ({ a }) => i2('01010000', this.value(a, 8)));
+ match('add', s, z, sep, immediate(a), ({ a }) => i2('01100000', this.value(a, 8)));
+ // TODO: Check where result is stored and maybe remove the duplicate encodings if needed
+ match('add', s, x, sep, y, () => i('01000100'));
+ match('add', s, y, sep, x, () => i('01000100'));
+ match('add', s, x, sep, z, () => i('01000101'));
+ match('add', s, z, sep, x, () => i('01000101'));
+ match('add', s, y, sep, z, () => i('01010101'));
+ match('add', s, z, sep, y, () => i('01010101'));
+
+ match('sub', s, x, sep, immediate(a), ({ a }) => i2('01000001', this.value(a, 8)));
+ match('sub', s, y, sep, immediate(a), ({ a }) => i2('01010001', this.value(a, 8)));
+ match('sub', s, z, sep, immediate(a), ({ a }) => i2('01100001', this.value(a, 8)));
+ match('sub', s, x, sep, y, () => i('01000110'));
+ match('sub', s, x, sep, z, () => i('01000111'));
+ match('sub', s, y, sep, x, () => i('01010110'));
+ match('sub', s, y, sep, z, () => i('01010111'));
+ match('sub', s, z, sep, x, () => i('01100110'));
+ match('sub', s, z, sep, y, () => i('01100111'));
+
+ match('mul', s, x, sep, immediate(a), ({ a }) => i2('01000010', this.value(a, 8)));
+ match('mul', s, y, sep, immediate(a), ({ a }) => i2('01010010', this.value(a, 8)));
+ match('mul', s, z, sep, immediate(a), ({ a }) => i2('01100010', this.value(a, 8)));
+ match('mul', s, x, sep, y, () => i('01001000'));
+ match('mul', s, y, sep, x, () => i('01001000'));
+ match('mul', s, x, sep, z, () => i('01001001'));
+ match('mul', s, z, sep, x, () => i('01001001'));
+ match('mul', s, y, sep, z, () => i('01011001'));
+ match('mul', s, z, sep, y, () => i('01011001'));
+
+ match('div', s, x, sep, immediate(a), ({ a }) => i2('01000011', this.value(a, 8)));
+ match('div', s, y, sep, immediate(a), ({ a }) => i2('01010011', this.value(a, 8)));
+ match('div', s, z, sep, immediate(a), ({ a }) => i2('01100011', this.value(a, 8)));
+ match('div', s, x, sep, y, () => i('01001010'));
+ match('div', s, x, sep, z, () => i('01001011'));
+ match('div', s, y, sep, x, () => i('01011010'));
+ match('div', s, y, sep, z, () => i('01011011'));
+ match('div', s, z, sep, x, () => i('01101010'));
+ match('div', s, z, sep, y, () => i('01101011'));
+
+ match('mod', s, x, sep, immediate(a), ({ a }) => i2('10011100', this.value(a, 8)));
+ match('mod', s, y, sep, immediate(a), ({ a }) => i2('10011101', this.value(a, 8)));
+ match('mod', s, z, sep, immediate(a), ({ a }) => i2('10011110', this.value(a, 8)));
+ match('mod', s, x, sep, y, () => i('01001100'));
+ match('mod', s, x, sep, z, () => i('01001101'));
+ match('mod', s, y, sep, x, () => i('01011100'));
+ match('mod', s, y, sep, z, () => i('01011101'));
+ match('mod', s, z, sep, x, () => i('01101100'));
+ match('mod', s, z, sep, y, () => i('01101101'));
+
+ match('lsl', s, x, () => i('01110000'));
+ match('lsl', s, y, () => i('01110001'));
+ match('lsl', s, z, () => i('01110010'));
+
+ match('lsr', s, x, () => i('01110100'));
+ match('lsr', s, y, () => i('01110101'));
+ match('lsr', s, z, () => i('01110110'));
+
+ match('not', s, x, () => i('01111000'));
+ match('not', s, y, () => i('01111001'));
+ match('not', s, z, () => i('01111010'));
+
+ match('and', s, x, sep, immediate(a), ({ a }) => i2('10010000', this.value(a, 8)));
+ match('and', s, y, sep, immediate(a), ({ a }) => i2('10010001', this.value(a, 8)));
+ match('and', s, z, sep, immediate(a), ({ a }) => i2('10010010', this.value(a, 8)));
+ match('and', s, x, sep, y, () => i('10000000'));
+ match('and', s, y, sep, x, () => i('10000000'));
+ match('and', s, x, sep, z, () => i('10000001'));
+ match('and', s, z, sep, x, () => i('10000001'));
+ match('and', s, y, sep, z, () => i('10000010'));
+ match('and', s, z, sep, y, () => i('10000010'));
+
+ match('xor', s, x, sep, immediate(a), ({ a }) => i2('10011000', this.value(a, 8)));
+ match('xor', s, y, sep, immediate(a), ({ a }) => i2('10011001', this.value(a, 8)));
+ match('xor', s, z, sep, immediate(a), ({ a }) => i2('10011010', this.value(a, 8)));
+ match('xor', s, x, sep, y, () => i('10000100'));
+ match('xor', s, y, sep, x, () => i('10000100'));
+ match('xor', s, x, sep, z, () => i('10000101'));
+ match('xor', s, z, sep, x, () => i('10000101'));
+ match('xor', s, y, sep, z, () => i('10000110'));
+ match('xor', s, z, sep, y, () => i('10000110'));
+
+ match('or', s, x, sep, immediate(a), ({ a }) => i2('10010100', this.value(a, 8)));
+ match('or', s, y, sep, immediate(a), ({ a }) => i2('10010101', this.value(a, 8)));
+ match('or', s, z, sep, immediate(a), ({ a }) => i2('10010110', this.value(a, 8)));
+ match('or', s, x, sep, y, () => i('10001000'));
+ match('or', s, y, sep, x, () => i('10001000'));
+ match('or', s, x, sep, z, () => i('10001001'));
+ match('or', s, z, sep, x, () => i('10001001'));
+ match('or', s, y, sep, z, () => i('10001010'));
+ match('or', s, z, sep, y, () => i('10001010'));
+
+ // Directives
+
+ match(token(a), ':', ({ a }) => this.label(a, source));
+ match('.data', s, remainder(a),
+ ({ a }) => this.data(a.split(new RegExp(hardSep)), { ...source, sourceInstruction: '.data' }));
+ match('.repeat', s, token(a), sep, token(b),
+ ({ a, b }) => this.data(new Array(this.literal(b, 8, false)).fill(a), { ...source, sourceInstruction: '.repeat' }));
+ match('.eq', s, token(a), sep, token(b), ({ a, b }) => this.constant(a, this.literal(b, 8, false)));
+ match('.address', s, token(a), ({ a }) => this.address(this.literal(a, 8, false)));
+ match('.align', s, token(a), ({ a }) => this.align(this.literal(a, 8, false)));
+ match('.start', () => this.enable());
+ match('.end', () => this.disable());
+
+ if (!matchFound) {
+ throw new Error(`Unknown instruction: ${line}`);
+ }
+ }
+
+ resolveParsedLine(instruction: ParsedLine): OutputLine {
+ if (instruction.tag === 'label') {
+ return {
+ tag: 'label',
+ name: instruction.name,
+ };
+ }
+
+ let bits = '';
+ for (const part of instruction.parts) {
+ bits += this.resolveParsedLinePart(part);
+ }
+ if (bits.length % 8 !== 0) {
+ throw new Error(`Instruction ${instruction.source.realInstruction} is ${bits.length} bits long, but should be a multiple of 8`);
+ }
+ return {
+ tag: 'instruction',
+ bits,
+ address: instruction.address,
+ source: instruction.source,
+ };
+ }
+
+ resolveParsedLinePart(part: ParsedLinePart, littleEndian: boolean = true): string {
+ if (typeof part === 'string') {
+ return part;
+ } else if (isNumber(part.value)) {
+ return toBitString(toNumber(part.value), part.bitCount, part.addressRelative, littleEndian ? 8 : null);
+ } else if (this.labels.has(part.value)) {
+ const targetLabelAddress = this.labels.get(part.value);
+ const value = part.addressRelative ? targetLabelAddress - part.sourceAddress : targetLabelAddress;
+ return toBitString(value, part.bitCount, part.addressRelative, littleEndian ? 8 : null);
+ }
+
+ const offsetMatch = part.value.match(new RegExp(VALUE_PAIR_RE, 'i'));
+ if (offsetMatch) {
+ const [base, operator, offset] = offsetMatch.slice(1);
+
+ const baseValue = parseInt(this.resolveParsedLinePart({
+ ...part,
+ value: base,
+ }, false), 2,);
+ const offsetValue = parseInt(this.resolveParsedLinePart({
+ ...part,
+ addressRelative: false,
+ value: offset,
+ }, false), 2) * (operator === '+' ? 1 : -1);
+ return toBitString(baseValue + offsetValue, part.bitCount, part.addressRelative, littleEndian ? 8 : null);
+ }
+
+ if (this.constants.has(part.value)) {
+ const value = this.constants.get(part.value);
+ return toBitString(value, part.bitCount, false, littleEndian ? 8 : null);
+ } else {
+ throw new Error(`Unknown label: ${part.value}`);
+ }
+ }
+}
+
+function assemble(input: AssemblyInput): AssemblyOutput {
+ const program = new Program();
+ const errors: Array = [];
+
+ for (const [lineNumber, line] of input.source.split('\n').entries()) {
+ try {
+ program.parseSourceLine(line, lineNumber);
+ } catch (e) {
+ errors.push({
+ line: lineNumber,
+ message: e.message,
+ });
+ }
+ }
+
+ const outputLines: Array = [];
+ for (const instruction of program.output) {
+ let resolved: OutputLine;
+ try {
+ resolved = program.resolveParsedLine(instruction);
+ outputLines.push(resolved);
+ } catch (e) {
+ errors.push({
+ line: instruction.source.lineNumber,
+ message: e.message,
+ });
+ }
+ }
+
+ return {
+ lines: outputLines,
+ errors,
+ message: '',
+ };
+}
+
+const STACK_POINTER_ADDRESS = 0x7f;
+const INTERRUPT_VECTOR_ADDRESS = 0xfe;
+const RESET_VECTOR_ADDRESS = 0xff;
+
+class V8Emulator implements Emulator {
+ memory: Array = [];
+ registers: Array = [];
+ pc: number;
+ cycle: number;
+ carryFlag: boolean;
+ zeroFlag: boolean;
+ interruptsEnabled: boolean;
+
+ constructor() {
+ this.init([]);
+ }
+
+ init(memory: Array) {
+ this.memory = memory;
+ this.memory[STACK_POINTER_ADDRESS] = STACK_POINTER_ADDRESS - 1;
+ this.registers = new Array(8).fill(0);
+ this.pc = this.memory[RESET_VECTOR_ADDRESS] ?? 0;
+ this.cycle = 0;
+ this.carryFlag = false;
+ this.zeroFlag = false;
+ this.interruptsEnabled = true;
+ }
+
+ step() {
+
+ }
+
+ printState(): string {
+ return 'Emulator not implemented';
+ }
+}
+
+const syntaxHighlighting = new window['Parser']({
+ whitespace: /\s+/,
+ number: /#?(\$-?[\dA-Fa-f_]+|-?(\d+)|%-?[01_]+|@-?[0-7_]+)/,
+ comment: /\/\/[^\r\n]*|;[^\r\n]*/,
+ directive: /\.[a-zA-Z0-9_]+/,
+ label: /[a-zA-Z0-9_]+:/,
+ string: /"(\\.|[^"\r\n])*"|'(\\.|[^'\r\n])*'/,
+ register: /\b([xyz]|yx)\b/i,
+ instruction: /^[a-zA-Z0-9\.]+/,
+ other: /\S/,
+});
+
+const archSpecification: ArchSpecification = {
+ documentation: '',
+ syntaxHighlighting,
+ assemble,
+ maxWordsPerInstruction: 4,
+ wordSize: 8,
+ emulator: new V8Emulator(),
+};
+export default archSpecification;
+
+archSpecification.documentation = `
+# Bitzzy
+
+An 8-bit CPU by Mazzetip with 16-bit addressing.
+
+
+## Registers
+
+There are three registers: X, Y, and Z. Z is the accumulator.
+
+
+## Assembler syntax
+
+Instructions and registers are case-insensitive. For example, \`INC X\` and \`inc x\` are both valid.
+
+Labels and constants are case-sensitive. For example, \`myLabel:\` and \`mylabel:\` are different labels.
+
+Comments begin with a semicolon. For example, \`; this is a comment\`.
+
+Labels can be offset by a value by placing them in parentheses. For example, \`(myLabel + 2)\` is two bytes after \`myLabel\`.
+
+<reg> indicates a register, i.e. X, Y, or Z. For example, the instruction \`INC \` can be used as \`INC X\`, \`INC Y\`, or \`INC Z\`.
+
+When an instruction takes two registers, they must not be the same. E.g. \`SWP x, y\` is okay but \`SWP x, x\` is illegal.
+
+<imm> indicates an 8-bit immediate value. This must be prefixed with '#' and can be signed or unsigned. For example, \`LOD X, #\$ff\` loads the value -1 into register X.
+
+<addr> indicates a 16-bit address. For example, \`JMP \$fa08\` jumps to address 0xfa08.
+
+When registers X and Y are used together to form a 16-bit address, they are written as YX. Y forms the upper 8 bits while X forms the lower 8 bits.
+
+
+## Instructions
+
+HLT: Halt the CPU.
+
+RTI: Return from interrupt.
+
+ENI: Enable interrupts.
+
+DSI: Disable interrupts.
+
+NOP: No operation.
+
+REM <reg>: Set register <reg> to the remainder (the carry/borrow flag).
+
+CLR: Clear remainder.
+
+JMP <addr>: Jump to the address <addr>.
+
+JMP <addr>, <reg>: Jump to the address <addr> + register <reg>.
+
+JMPEZ X, <addr>: Jump to the address <addr> if register X is zero.
+
+JMPEZ Z, <addr>: Jump to the address <addr> if register Z is zero.
+
+JMPGT X, Z, <addr>: Jump to the address <addr> if X is greater than Y.
+
+JMPEQ <reg-a>, <reg-b>, <addr>: Jump to the address <addr> if register <reg-a> is equal to register <reg-b>.
+
+JMPRNZ <addr>: Jump to the address <addr> if the remainder is not zero.
+
+JMPREZ <addr>: Jump to the address <addr> if the remainder is zero.
+
+JSR <addr>: Jump to the address <addr> and push the return address onto the stack.
+
+JSR <addr>, <reg>: Jump to the address <addr> + register <reg> and push the return address onto the stack.
+
+JSREZ X, <addr>: Jump to the address <addr> if register X is zero and push the return address onto the stack.
+
+JSREZ Z, <addr>: Jump to the address <addr> if register Z is zero and push the return address onto the stack.
+
+JSRGT X, Z, <addr>: Jump to the address <addr> if X is greater than Y and push the return address onto the stack.
+
+JSREQ <reg-a>, <reg-b>, <addr>: Jump to the address <addr> if register <reg-a> is equal to register <reg-b> and push the return address onto the stack.
+
+DJNZ <reg>, <addr>: Decrement register <reg> and jump to the address <addr> if it is now not zero.
+
+RET: Return from subroutine.
+
+RETRNZ: Return from subroutine if the remainder is not zero.
+
+RETREZ: Jump to the address <addr> if the remainder is zero.
+
+INC <reg>: Increment register <reg>.
+
+INC <addr>: Increment value at address <addr>.
+
+DEC <reg>: Decrement register <reg>.
+
+DEC <addr>: Decrement value at address <addr>.
+
+ADD <reg-a>, <reg-b>: Add register <reg-b> to register <reg-a> and store the result in Z.
+
+ADD <reg>, <imm>: Add the immediate value <imm> to register <reg> and store the result in register <reg>.
+
+SUB <reg-a>, <reg-b>: Subtract register <reg-b> from register <reg-a> and store the result in Z.
+
+SUB <reg>, <imm>: Subtract the immediate value <imm> from register <reg> and store the result in register <reg>.
+
+LSL <reg>: Logical shift left register <reg> by one bit.
+
+LSR <reg>: Logical shift right register <reg> by one bit.
+
+AND <reg-a>, <reg-b>: Bitwise AND register <reg-a> with register <reg-b> and store the result in Z.
+
+AND <reg>, <imm>: Bitwise AND register <reg> with the immediate value <imm> and store the result in register <reg>.
+
+OR <reg-a>, <reg-b>: Bitwise OR register <reg-a> with register <reg-b> and store the result in Z.
+
+OR <reg>, <imm>: Bitwise OR register <reg> with the immediate value <imm> and store the result in register <reg>.
+
+XOR <reg-a>, <reg-b>: Bitwise XOR register <reg-a> with register <reg-b> and store the result in Z.
+
+XOR <reg>, <imm>: Bitwise XOR register <reg> with the immediate value <imm> and store the result in register <reg>.
+
+LOD <reg>, <imm>: Load the immediate value <imm> into register <reg>.
+
+LOD <reg-a>, <reg-b>: Load the value in register <reg-b> into register <reg-a>.
+
+LOD <reg>, <addr>: Load the value at address <addr> into register <reg>.
+
+LOD <reg-a>, <addr>, <reg-b>: Load the value at address <addr> + register <reg-b> into register <reg-a>.
+
+LOD Z, <addr>, YX: Load the value at address <addr> + the combined value of registers X and Y ($YYXX) into register Z.
+
+SWP <reg-a>, <reg-b>: Swap the values in registers <reg-a> and <reg-b>.
+
+STR <reg>, <addr>: Store the value in register <reg> at address <addr>.
+
+STR <reg-a>, <addr>, <reg-b>: Store the value in register <reg-a> at address <addr> + register <reg-b>.
+
+STR <imm>, <addr>: Store the immediate value <imm> to address <addr>.
+
+STR <imm>, <addr>, <reg>: Store the immediate value <imm> to address <addr> + register <reg>.
+
+STR Z, <addr>, YX: Store the value in register Z at address <addr> + the combined value of registers X and Y ($YYXX).
+
+MUL <reg-a>, <reg-b>: Multiply register <reg-a> by register <reg-b> and store the result in in Z.
+
+MUL <reg>, <imm>: Multiply register <reg> by the immediate value <imm> and store the result in register <reg>.
+
+DIV <reg-a>, <reg-b>: Divide register <reg-a> by register <reg-b> and store the result in in Z.
+
+DIV <reg>, <imm>: Divide register <reg> by the immediate value <imm> and store the result in register <reg>.
+
+MOD <reg-a>, <reg-b>: Divide register <reg-a> by register <reg-b> and store the remainder in in Z.
+
+MOD <reg>, <imm>: Divide register <reg> by the immediate value <imm> and store the remainder in register <reg>.
+
+
+## Assembler directives
+
+\`:\` : Define a label.
+
+\`.data , , , ...\`: Store 8-bit values in memory. You can also use labels, in which case the 16-bit address of the label will be stored.
+
+\`.repeat , \`: Repeat in memory multiple times.
+
+\`.address \`: Set the current address to .
+
+\`.align \`: Align the current address to the next multiple of .
+
+\`.end\`: Ignore all following instructions.
+
+\`.start\`: Resume assembling instructions.
+
+\`.eq\ , \`: Define a constant.
+
+
+`;
diff --git a/assembler/targets/parva_0_1.ts b/assembler/targets/parva_0_1.ts
index 4a60c2c..199a1d0 100644
--- a/assembler/targets/parva_0_1.ts
+++ b/assembler/targets/parva_0_1.ts
@@ -633,7 +633,12 @@ class ParvaEmulator implements Emulator {
}
step() {
+ const instructionsFetched = (2 - (this.pc % 2)) + (this.previousFrameAccessedMemory ? 0 : 2);
+ this.previousFrameAccessedMemory = false;
+ for (let i = 0; i < instructionsFetched; i++) {
+ if (this.stepCore()) break;
+ }
}
stepCore(): boolean {
@@ -724,6 +729,7 @@ class ParvaEmulator implements Emulator {
}
this.previousFrameAccessedMemory = true;
+ coreTerminates = true;
} else if (operationType === '11') {
// branching
const special = condition.startsWith('11');
diff --git a/assembler/util.ts b/assembler/util.ts
index 3a9a786..350355c 100644
--- a/assembler/util.ts
+++ b/assembler/util.ts
@@ -1,4 +1,4 @@
-export function toBitString(n: number, bits: number, signed: boolean = false): string {
+export function toBitString(n: number, bits: number, signed: boolean = false, littleEndianByteSize: null | number = null): string {
const minimum = -(2 ** (bits - 1));
const maximum = signed ? (2 ** (bits - 1)) - 1 : 2 ** bits - 1;
@@ -18,6 +18,15 @@ export function toBitString(n: number, bits: number, signed: boolean = false): s
if (n !== 0) {
throw new Error(`Internal error: ${n} not zero after conversion to ${bits}-bit ${signed ? 'signed' : 'unsigned'} integer`);
}
+
+ if (littleEndianByteSize !== null) {
+ const bytes = [];
+ for (let i = 0; i < result.length / littleEndianByteSize; i++) {
+ bytes.push(result.substring(i * littleEndianByteSize, (i + 1) * littleEndianByteSize));
+ }
+ result = bytes.reverse().join('');
+ }
+
return result;
}
diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml
new file mode 100644
index 0000000..f067238
--- /dev/null
+++ b/compiler/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "compiler"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+nom = "7.1.3"
diff --git a/compiler/examples/calculator_1.parva b/compiler/examples/calculator_1.parva
new file mode 100644
index 0000000..fc24a8e
--- /dev/null
+++ b/compiler/examples/calculator_1.parva
@@ -0,0 +1,96 @@
+# target parva_0_1
+
+li sp, stack
+
+fn tokenize(input) {
+ let current_token_ptr = input - 1;
+ let temp = -1;
+ push(temp); // mark the beginning of the token stack
+
+ loop {
+ current_token_ptr += 1;
+ let current_token = rw(current_token_ptr);
+ if current_token == 0 {
+ goto(tokenize_end);
+ }
+ if current_token == '0' {
+ goto(tokenize_symbol);
+ }
+ if current_token == '9' {
+ goto(tokenize_symbol);
+ }
+ let number_start_addr = sp
+
+
+ current_token_ptr
+ let token_value = current_token - '0';
+
+ lw x3, x2
+ blt x3, x4, tokenize__number_gather_end
+ bgt x3, x5, tokenize__number_gather_end
+ b tokenize__number_gather
+tokenize__number_gather_end:
+ li x0, 0 # x0 = sum
+ li x7, 1 # x7 = power of 10
+.align 2
+tokenize__number_sum:
+ pop x3
+ mulu x3, x3, x7
+ add x0, x0, x3
+ mului x7, x7, 10
+ bne x6, sp, tokenize__number_sum
+
+ li x3, 1 # token type = 1 = number
+ push x3
+ push x0
+ b tokenize__loop
+
+tokenize__symbol:
+ xori x3, x3, 42 # todo, set to appropriate value for hashing symbols
+ lw x3, tokenize__symbol_hashtable(x3)
+ beqz x3, error_invalid_input
+ push x3
+ b tokenize__loop
+}
+
+tokenize__shunting_yard:
+ pop x3
+ bnez x3, 3
+
+}
+
+b end
+
+tokenize__output_queue:
+ .repeat 0 40
+
+tokenize__symbol_hashtable:
+ .data 0
+ .data 1
+ .repeat 0 30 # todo
+
+error_invalid_input:
+ li a0, error_message_invalid_input
+ li a1, 1
+ call print
+ b end
+
+# function
+print:
+ # todo
+ ret
+
+end:
+ wfi
+
+input_string:
+.string_encoding terminal
+.string "125+23*8"
+.string "\0"
+
+error_message_invalid_input:
+.string_encoding ascii # todo, use correct encoding
+.string "Invalid input\0"
+
+stack:
+
diff --git a/compiler/examples/test.parva b/compiler/examples/test.parva
new file mode 100644
index 0000000..090490e
--- /dev/null
+++ b/compiler/examples/test.parva
@@ -0,0 +1,6 @@
+x = (y + 3) + 5;
+y += x;
+z = foo;
+fn test() {
+ poop = moop;
+}
diff --git a/compiler/rustfmt.toml b/compiler/rustfmt.toml
new file mode 100644
index 0000000..218e203
--- /dev/null
+++ b/compiler/rustfmt.toml
@@ -0,0 +1 @@
+hard_tabs = true
diff --git a/compiler/src/compiler.rs b/compiler/src/compiler.rs
new file mode 100644
index 0000000..8741b9f
--- /dev/null
+++ b/compiler/src/compiler.rs
@@ -0,0 +1,137 @@
+use crate::{
+ parser::{Expression, Statement},
+ CompilationError,
+};
+
+struct Context {
+ temporary_counter: usize,
+}
+
+impl Context {
+ fn new() -> Self {
+ Self {
+ temporary_counter: 0,
+ }
+ }
+
+ fn new_temporary(&mut self) -> String {
+ let result = format!("__temp{}", self.temporary_counter);
+ self.temporary_counter += 1;
+ result
+ }
+}
+
+pub fn compile(ast: Statement) -> Result {
+ let mut context = Context::new();
+ println!("initial: {:#?}\n", ast);
+ let ast = ast_pass_0(ast)?;
+ println!("pass 0: {:#?}\n", ast);
+ let ast = ast_pass_1(&mut context, vec![ast])?;
+ println!("pass 1: {:#?}\n", ast);
+ Ok(format!("{:?}\n", ast))
+}
+
+/// Pass 0
+///
+/// Rewrites compound assignments into simple assignments, e.g. `a += 1` to `a = a + 1`
+fn ast_pass_0(ast: Statement) -> Result {
+ let result = match ast {
+ Statement::Block(inner) => Statement::Block(
+ inner
+ .into_iter()
+ .map(ast_pass_0)
+ .collect::, _>>()?,
+ ),
+ Statement::AddAssign(name, expr) => Statement::Assign(
+ name.clone(),
+ Expression::Add(Box::new(Expression::Identifier(name)), Box::new(expr)),
+ ),
+ statement => statement,
+ };
+ Ok(result)
+}
+
+/// Pass 1
+///
+/// Expands nested expressions into simple expressions,
+/// e.g. `a = (x + y) + z;` to `temp0 = x + y; a = temp0 + z;`
+fn ast_pass_1(
+ context: &mut Context,
+ statements: Vec,
+) -> Result, CompilationError> {
+ let mut statements_out = Vec::new();
+
+ for statement in statements {
+ match statement {
+ Statement::Block(inner) => {
+ statements_out.push(Statement::Block(ast_pass_1(context, inner)?));
+ }
+ Statement::Assign(name, expr) => {
+ let (mut expression_statements, expression) =
+ flatten_expression(context, expr.clone())?;
+
+ statements_out.extend(expression_statements);
+ statements_out.push(Statement::Assign(name.clone(), expression));
+ }
+ statement => statements_out.push(statement),
+ };
+ }
+ Ok(statements_out)
+}
+
+fn flatten_expression(
+ context: &mut Context,
+ expression: Expression,
+) -> Result<(Vec, Expression), CompilationError> {
+ let mut statements = Vec::new();
+
+ let result = match expression {
+ Expression::Identifier(name) => (vec![], Expression::Identifier(name)),
+ Expression::Number(name) => (vec![], Expression::Number(name)),
+ Expression::Add(left, right) => {
+ let (left_statements, left) = flatten_expression(context, *left)?;
+ statements.extend(left_statements);
+
+ let (right_statements, right) = flatten_expression(context, *right)?;
+ statements.extend(right_statements);
+
+ let temp_name = context.new_temporary();
+
+ let statement = Statement::Assign(
+ temp_name.clone(),
+ Expression::Add(Box::new(left), Box::new(right)),
+ );
+ statements.push(statement);
+ (statements, Expression::Identifier(temp_name))
+ }
+ expression => (vec![], expression),
+ };
+ Ok(result)
+}
+
+/// Pass 2
+///
+/// Convert to IR
+fn ast_pass_2(
+ context: &mut Context,
+ statements: Vec,
+) -> Result, CompilationError> {
+ let mut statements_out = Vec::new();
+
+ for statement in statements {
+ match statement {
+ Statement::Block(inner) => {
+ statements_out.push(Statement::Block(ast_pass_1(context, inner)?));
+ }
+ Statement::Assign(name, expr) => {
+ let (mut expression_statements, expression) =
+ flatten_expression(context, expr.clone())?;
+
+ statements_out.extend(expression_statements);
+ statements_out.push(Statement::Assign(name.clone(), expression));
+ }
+ statement => statements_out.push(statement),
+ };
+ }
+ Ok(statements_out)
+}
diff --git a/compiler/src/compiler/assemble.rs b/compiler/src/compiler/assemble.rs
new file mode 100644
index 0000000..02fabdb
--- /dev/null
+++ b/compiler/src/compiler/assemble.rs
@@ -0,0 +1,11 @@
+pub struct Variable {
+ name: String,
+}
+
+pub enum PseudoInstruction {
+ Call(Variable),
+ Return,
+ Add(Variable, Variable, Variable),
+ Sub(Variable, Variable, Variable),
+ Li(Variable, i32),
+}
diff --git a/compiler/src/lexer.rs b/compiler/src/lexer.rs
new file mode 100644
index 0000000..4281ba4
--- /dev/null
+++ b/compiler/src/lexer.rs
@@ -0,0 +1,186 @@
+use crate::CompilationError;
+
+#[derive(Debug, Clone)]
+pub enum Token {
+ Identifier(String),
+ Character(char),
+ Number(i32),
+ Operator(Operator),
+ Colon,
+ AtSign,
+ OpenParenthesis,
+ CloseParenthesis,
+ OpenBrace,
+ CloseBrace,
+ Assignment(Assignment),
+ Comparison(Comparison),
+ Semicolon,
+ Keyword(Keyword),
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum Operator {
+ Plus,
+ Minus,
+ Star,
+ Slash,
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum Keyword {
+ Let,
+ Return,
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum Assignment {
+ Assign,
+ AddAssign,
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum Comparison {
+ Equals,
+ GreaterThan,
+ LessThan,
+}
+
+#[derive(Debug, Clone)]
+pub struct LexerConfiguration {}
+
+struct Lexer {
+ configuration: LexerConfiguration,
+ input: Vec,
+ position: usize,
+ line: usize,
+ line_start_position: usize,
+}
+
+impl Lexer {
+ fn new(configuration: LexerConfiguration, input: &str) -> Self {
+ Self {
+ configuration: LexerConfiguration {},
+ input: input.chars().collect(),
+ position: 0,
+ line: 0,
+ line_start_position: 0,
+ }
+ }
+
+ fn next_token(&mut self) -> Result