Skip to content

Commit

Permalink
Maintain index AND line number
Browse files Browse the repository at this point in the history
So we can skip over non-code lines (like whitespace or comments).
  • Loading branch information
ahuth committed Feb 1, 2024
1 parent 8a211f7 commit 421fecb
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 54 deletions.
76 changes: 31 additions & 45 deletions src/cpu.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
type Stack = number[];
type Condition = '+' | '-';

export type Instruction =
| {op: 'push'; operand: number; cond?: Condition}
| {op: 'drop'; cond?: Condition}
| {op: 'eq'; cond?: Condition}
| {op: 'ne'; cond?: Condition}
| {op: 'gt'; cond?: Condition}
| {op: 'ge'; cond?: Condition}
| {op: 'lt'; cond?: Condition}
| {op: 'le'; cond?: Condition}
| {op: 'add'; cond?: Condition}
| {op: 'sub'; cond?: Condition}
| {op: 'skip'; cond?: Condition};
| {line: number; op: 'push'; operand: number; cond?: Condition}
| {line: number; op: 'drop'; cond?: Condition}
| {line: number; op: 'eq'; cond?: Condition}
| {line: number; op: 'ne'; cond?: Condition}
| {line: number; op: 'gt'; cond?: Condition}
| {line: number; op: 'ge'; cond?: Condition}
| {line: number; op: 'lt'; cond?: Condition}
| {line: number; op: 'le'; cond?: Condition}
| {line: number; op: 'add'; cond?: Condition}
| {line: number; op: 'sub'; cond?: Condition}
| {line: number; op: 'noop'; cond?: Condition};

// 1. Optional + or - for conditions
// 2. Instruction
Expand All @@ -25,11 +26,11 @@ export function parse(code: string): Instruction[] {
const lines = code.split('\n');
const output: Instruction[] = [];

for (const line of lines) {
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.replace(/#.+$/, '').trim();

if (!trimmed) {
output.push({op: 'skip'});
continue;
}

Expand All @@ -46,6 +47,7 @@ export function parse(code: string): Instruction[] {
switch (operator) {
case 'push': {
output.push({
line: i,
cond: cond as Condition,
op: operator,
operand: operands[0],
Expand All @@ -59,87 +61,71 @@ export function parse(code: string): Instruction[] {
case 'ne':
case 'gt':
case 'lt':
output.push({cond: cond as Condition, op: operator});
output.push({line: i, cond: cond as Condition, op: operator});
}
}

return output;
}

export function execute(
lineNumber: number,
instructions: Instruction[],
stack: number[],
): [nextStack: number[], nextLine: number] {
const instruction = instructions[lineNumber];

export function execute(instruction: Instruction, stack: Stack): Stack {
// Truthy conditional
if (instruction.cond === '+') {
// If falsey, don't execute the instruction.
if (stack[0] === 0) {
return [stack, lineNumber + 1];
return stack;
}
}

// Falsey conditional
if (instruction.cond === '-') {
// If truthy, don't execute the instruction.
if (stack[0] > 0) {
return [stack, lineNumber + 1];
return stack;
}
}

switch (instruction.op) {
case 'add': {
const [a, b, ...rest] = stack;
const nextStack = [a + b, ...rest];
return [nextStack, lineNumber + 1];
return [a + b, ...rest];
}
case 'drop': {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, ...rest] = stack;
return [rest, lineNumber + 1];
const [, ...rest] = stack;
return rest;
}
case 'push': {
const nextStack = [instruction.operand, ...stack];
return [nextStack, lineNumber + 1];
return [instruction.operand, ...stack];
}
case 'sub': {
const [a, b, ...rest] = stack;
const nextStack = [a - b, ...rest];
return [nextStack, lineNumber + 1];
return [a - b, ...rest];
}
case 'eq': {
const [a, b, ...rest] = stack;
const nextStack = [a === b ? 1 : 0, ...rest];
return [nextStack, lineNumber + 1];
return [a === b ? 1 : 0, ...rest];
}
case 'ne': {
const [a, b, ...rest] = stack;
const nextStack = [a !== b ? 1 : 0, ...rest];
return [nextStack, lineNumber + 1];
return [a !== b ? 1 : 0, ...rest];
}
case 'gt': {
const [a, b, ...rest] = stack;
const nextStack = [a > b ? 1 : 0, ...rest];
return [nextStack, lineNumber + 1];
return [a > b ? 1 : 0, ...rest];
}
case 'ge': {
const [a, b, ...rest] = stack;
const nextStack = [a >= b ? 1 : 0, ...rest];
return [nextStack, lineNumber + 1];
return [a >= b ? 1 : 0, ...rest];
}
case 'lt': {
const [a, b, ...rest] = stack;
const nextStack = [a < b ? 1 : 0, ...rest];
return [nextStack, lineNumber + 1];
return [a < b ? 1 : 0, ...rest];
}
case 'le': {
const [a, b, ...rest] = stack;
const nextStack = [a <= b ? 1 : 0, ...rest];
return [nextStack, lineNumber + 1];
return [a <= b ? 1 : 0, ...rest];
}
default:
return [stack, lineNumber + 1];
return stack;
}
}
16 changes: 7 additions & 9 deletions src/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {parse, execute, type Instruction} from './cpu';

export const initialState = {
code: 'push 1\npush 2\npush 3\nadd\npush 4\nsub\nadd',
index: 0,
instructions: [] as Instruction[],
onLine: null as number | null,
stack: [] as number[],
Expand All @@ -19,30 +20,27 @@ type Action =
export function reducer(state: State, action: Action) {
switch (action.type) {
case 'CODE_TYPED':
return {...state, onLine: null, code: action.value};
return {...state, index: 0, onLine: null, code: action.value};
case 'STEP_CLICKED': {
// First click, so no instructions are executed, yet.
if (state.onLine == null) {
return {...state, instructions: parse(state.code), onLine: 0};
}
// No more instructions to execute.
if (state.onLine === state.instructions.length) {
if (state.index === state.instructions.length) {
return state;
}
// Execute an instruction.
const [nextStack, nextLine] = execute(
state.onLine,
state.instructions,
state.stack,
);
const nextStack = execute(state.instructions[state.index], state.stack);
return {
...state,
onLine: nextLine,
index: state.index + 1,
onLine: state.instructions[state.index + 1]?.line ?? state.onLine,
stack: nextStack,
};
}
case 'STOP_CLICKED':
return {...state, onLine: null, stack: []};
return {...state, index: 0, onLine: null, stack: []};
default:
return state;
}
Expand Down

0 comments on commit 421fecb

Please sign in to comment.