// Pure functional approach to Forth interpreter state const createInitialState = () => ({ stacks: [[], [], [], []], dictionary: new Map(), output: [], compilingWord: null, compilingDefinition: [], stringMode: false, currentString: '', stringPushMode: false, skipMode: false, skipCount: 0, loopStart: null, loopBack: false, focusedStack: 0, // New: 0=Red, 1=Teal, 2=Blue, 3=Yellow moveInProgress: false, // For move operation moveFromStack: null, // For move operation crossStackInProgress: false, // For cross-stack operations crossStackOperation: null, // Type of cross-stack operation crossStackData: null // Data for cross-stack operation }); // Pure function to update state const updateState = (state, updates) => ({ ...state, ...updates }); // Stack operations const pushToStack = (stacks, stackIndex, value) => { const newStacks = stacks.map((stack, i) => i === stackIndex ? [...stack, value] : stack ); return newStacks; }; const popFromStack = (stacks, stackIndex) => { const newStacks = stacks.map((stack, i) => i === stackIndex ? stack.slice(0, -1) : stack ); const value = stacks[stackIndex][stacks[stackIndex].length - 1]; return { stacks: newStacks, value }; }; const moveBetweenStacks = (stacks, fromStack, toStack) => { if (stacks[fromStack].length === 0) return { stacks, value: null }; const { stacks: newStacks, value } = popFromStack(stacks, fromStack); return { stacks: pushToStack(newStacks, toStack, value), value }; }; const popAndPrint = (state, stackIndex) => { if (state.stacks[stackIndex].length === 0) { return updateState(state, { output: [...state.output, `Stack ${stackIndex + 1} is empty`] }); } const { stacks, value } = popFromStack(state.stacks, stackIndex); return updateState(state, { stacks, output: [...state.output, `Stack ${stackIndex + 1}: ${value}`] }); }; // Helper function to get focused stack name const getFocusedStackName = (focusedStack) => { const stackNames = ['Red (1)', 'Teal (2)', 'Blue (3)', 'Yellow (4)']; return stackNames[focusedStack]; }; // Helper function to execute cross-stack operations const executeCrossStackOperation = (state, sourceIndex, targetIndex) => { const operation = state.crossStackOperation; const data = state.crossStackData; switch (operation) { case 'dup': return updateState(state, { stacks: pushToStack(state.stacks, targetIndex, data.top), output: [...state.output, `Duplicated ${data.top} from ${getFocusedStackName(sourceIndex)} to ${getFocusedStackName(targetIndex)}`] }); case 'over': return updateState(state, { stacks: pushToStack(state.stacks, targetIndex, data.second), output: [...state.output, `Copied ${data.second} from ${getFocusedStackName(sourceIndex)} to ${getFocusedStackName(targetIndex)}`] }); case 'swap': // Create new stacks array const newStacks = state.stacks.map((stack, i) => [...stack]); // Get top 2 items from source stack (don't remove) const sourceTop = newStacks[sourceIndex][newStacks[sourceIndex].length - 1]; const sourceSecond = newStacks[sourceIndex][newStacks[sourceIndex].length - 2]; // Add source items to target stack in order: top, second newStacks[targetIndex].push(sourceTop); newStacks[targetIndex].push(sourceSecond); // Source stack remains unchanged (no popping/pushing) return updateState(state, { stacks: newStacks, output: [...state.output, `Copied top 2 items from ${getFocusedStackName(sourceIndex)} to ${getFocusedStackName(targetIndex)}`] }); case 'nip': // Create new stacks array const nipStacks = state.stacks.map((stack, i) => [...stack]); // Remove second item from source stack nipStacks[sourceIndex].splice(-2, 1); // Add second item to target stack nipStacks[targetIndex].push(data.second); return updateState(state, { stacks: nipStacks, output: [...state.output, `Moved second item ${data.second} from ${getFocusedStackName(sourceIndex)} to ${getFocusedStackName(targetIndex)}`] }); case 'tuck': // Create new stacks array const tuckStacks = state.stacks.map((stack, i) => [...stack]); // Remove top item from source stack const tuckTop = tuckStacks[sourceIndex].pop(); // Add items to target stack in tuck order: top, second, top tuckStacks[targetIndex].push(tuckTop); tuckStacks[targetIndex].push(data.second); tuckStacks[targetIndex].push(tuckTop); // Don't add top item back to source stack - tuck removes it return updateState(state, { stacks: tuckStacks, output: [...state.output, `Tucked ${tuckTop} under ${data.second} on ${getFocusedStackName(targetIndex)}`] }); case 'rot': // Create new stacks array const rotStacks = state.stacks.map((stack, i) => [...stack]); // Remove top 3 items from source stack // For stack [1, 2, 3], top=3, second=2, third=1 const rotTop = rotStacks[sourceIndex].pop(); // 3 const rotSecond = rotStacks[sourceIndex].pop(); // 2 const rotThird = rotStacks[sourceIndex].pop(); // 1 // Add 3 items to target stack in rotated order: third, first, second rotStacks[targetIndex].push(rotThird); rotStacks[targetIndex].push(rotTop); rotStacks[targetIndex].push(rotSecond); // Add 3 items back to source stack in rotated order: third, first, second rotStacks[sourceIndex].push(rotThird); rotStacks[sourceIndex].push(rotTop); rotStacks[sourceIndex].push(rotSecond); return updateState(state, { stacks: rotStacks, output: [...state.output, `Rotated top 3 items between ${getFocusedStackName(sourceIndex)} and ${getFocusedStackName(targetIndex)}`] }); case '2dup': // Create new stacks array const dup2Stacks = state.stacks.map((stack, i) => [...stack]); // Get top 2 items from source stack (don't remove) const dup2Top = dup2Stacks[sourceIndex][dup2Stacks[sourceIndex].length - 1]; const dup2Second = dup2Stacks[sourceIndex][dup2Stacks[sourceIndex].length - 2]; // Add 2 items to target stack (preserve order: second, top) dup2Stacks[targetIndex].push(dup2Second); dup2Stacks[targetIndex].push(dup2Top); // Source stack remains unchanged (no popping/pushing) return updateState(state, { stacks: dup2Stacks, output: [...state.output, `Duplicated top 2 items from ${getFocusedStackName(sourceIndex)} to ${getFocusedStackName(targetIndex)}`] }); case '2over': // Create new stacks array const over2Stacks = state.stacks.map((stack, i) => [...stack]); // Get second pair of items from source stack (don't remove) // For stack [10, 20, 30, 40], second pair is [20, 30] const over2Second = over2Stacks[sourceIndex][over2Stacks[sourceIndex].length - 3]; const over2Third = over2Stacks[sourceIndex][over2Stacks[sourceIndex].length - 2]; // Add 2 items to target stack (preserve order: third, second) over2Stacks[targetIndex].push(over2Third); over2Stacks[targetIndex].push(over2Second); return updateState(state, { stacks: over2Stacks, output: [...state.output, `Copied second pair of items from ${getFocusedStackName(sourceIndex)} to ${getFocusedStackName(targetIndex)}`] }); case '2swap': // Create new stacks array const swap2Stacks = state.stacks.map((stack, i) => [...stack]); // Get top 4 items from source stack (don't remove) const sourceItems = [ swap2Stacks[sourceIndex][swap2Stacks[sourceIndex].length - 1], // top swap2Stacks[sourceIndex][swap2Stacks[sourceIndex].length - 2], // second swap2Stacks[sourceIndex][swap2Stacks[sourceIndex].length - 3], // third swap2Stacks[sourceIndex][swap2Stacks[sourceIndex].length - 4] // fourth ]; // Add source items to target stack in order: fourth, third, second, top swap2Stacks[targetIndex].push(sourceItems[3]); // fourth swap2Stacks[targetIndex].push(sourceItems[2]); // third swap2Stacks[targetIndex].push(sourceItems[1]); // second swap2Stacks[targetIndex].push(sourceItems[0]); // top // Source stack remains unchanged (no popping/pushing) return updateState(state, { stacks: swap2Stacks, output: [...state.output, `Copied top 2 pairs of items from ${getFocusedStackName(sourceIndex)} to ${getFocusedStackName(targetIndex)}`] }); default: return updateState(state, { output: [...state.output, `Error: Unknown cross-stack operation: ${operation}`] }); } }; // Built-in words with documentation const builtinWords = { // Stack focus commands 'focus.red': { fn: (state) => updateState(state, { focusedStack: 0, output: [...state.output, 'Focus set to Red stack (Stack 1)'] }), doc: 'Set focus to Red stack (Stack 1)', stack: '( -- )' }, 'focus.1': { fn: (state) => updateState(state, { focusedStack: 0, output: [...state.output, 'Focus set to Red stack (Stack 1)'] }), doc: 'Set focus to Red stack (Stack 1)', stack: '( -- )' }, 'focus.teal': { fn: (state) => updateState(state, { focusedStack: 1, output: [...state.output, 'Focus set to Teal stack (Stack 2)'] }), doc: 'Set focus to Teal stack (Stack 2)', stack: '( -- )' }, 'focus.2': { fn: (state) => updateState(state, { focusedStack: 1, output: [...state.output, 'Focus set to Teal stack (Stack 2)'] }), doc: 'Set focus to Teal stack (Stack 2)', stack: '( -- )' }, 'focus.blue': { fn: (state) => updateState(state, { focusedStack: 2, output: [...state.output, 'Focus set to Blue stack (Stack 3)'] }), doc: 'Set focus to Blue stack (Stack 3)', stack: '( -- )' }, 'focus.3': { fn: (state) => updateState(state, { focusedStack: 2, output: [...state.output, 'Focus set to Blue stack (Stack 3)'] }), doc: 'Set focus to Blue stack (Stack 3)', stack: '( -- )' }, 'focus.yellow': { fn: (state) => updateState(state, { focusedStack: 3, output: [...state.output, 'Focus set to Yellow stack (Stack 4)'] }), doc: 'Set focus to Yellow stack (Stack 4)', stack: '( -- )' }, 'focus.4': { fn: (state) => updateState(state, { focusedStack: 3, output: [...state.output, 'Focus set to Yellow stack (Stack 4)'] }), doc: 'Set focus to Yellow stack (Stack 4)', stack: '( -- )' }, 'focus.show': { fn: (state) => { const stackNames = ['Red (1)', 'Teal (2)', 'Blue (3)', 'Yellow (4)']; return updateState(state, { output: [...state.output, `Currently focused on: ${stackNames[state.focusedStack]}`] }); }, doc: 'Show which stack is currently focused', stack: '( -- )' }, // Stack manipulation for focused stack 'dup': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { const stackNames = ['Red (1)', 'Teal (2)', 'Blue (3)', 'Yellow (4)']; return updateState(state, { output: [...state.output, `Error: Stack underflow on dup - ${stackNames[state.focusedStack]} is empty. Use numbers or strings to add items first.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; return updateState(state, { stacks: pushToStack(state.stacks, state.focusedStack, top) }); }, doc: 'Duplicate the top item on the stack', stack: '( x -- x x )' }, 'swap': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { const stackNames = ['Red (1)', 'Teal (2)', 'Blue (3)', 'Yellow (4)']; return updateState(state, { output: [...state.output, `Error: Stack underflow on swap - ${stackNames[state.focusedStack]} needs 2 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const newStacks = state.stacks.map((stack, i) => [...stack]); const a = newStacks[state.focusedStack].pop(); const b = newStacks[state.focusedStack].pop(); newStacks[state.focusedStack].push(a); newStacks[state.focusedStack].push(b); return updateState(state, { stacks: newStacks }); }, doc: 'Exchange the top two items on the stack', stack: '( x1 x2 -- x2 x1 )' }, 'drop': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on drop - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to remove.`] }); } const { stacks } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks }); }, doc: 'Remove the top item from the stack', stack: '( x -- )' }, '2drop': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on 2drop - Stack 1 (Red) needs 2 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const { stacks: stacks1 } = popFromStack(state.stacks, state.focusedStack); const { stacks } = popFromStack(stacks1, 0); return updateState(state, { stacks }); }, doc: 'Remove the top two items from the stack', stack: '( x1 x2 -- )' }, 'over': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on over - Stack 1 (Red) needs 2 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const second = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 2]; return updateState(state, { stacks: pushToStack(state.stacks, state.focusedStack, second) }); }, doc: 'Copy the second item on the stack to the top', stack: '( x1 x2 -- x1 x2 x1 )' }, '2dup': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on 2dup - Stack 1 (Red) needs 2 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; const second = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 2]; const newStacks = pushToStack(state.stacks, state.focusedStack, second); return updateState(state, { stacks: pushToStack(newStacks, state.focusedStack, top) }); }, doc: 'Duplicate the top two items on the stack', stack: '( x1 x2 -- x1 x2 x1 x2 )' }, 'rot': { fn: (state) => { if (state.stacks[state.focusedStack].length < 3) { return updateState(state, { output: [...state.output, `Error: Stack underflow on rot - Stack 1 (Red) needs 3 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const newStacks = state.stacks.map((stack, i) => [...stack]); const a = newStacks[state.focusedStack].pop(); const b = newStacks[state.focusedStack].pop(); const c = newStacks[state.focusedStack].pop(); newStacks[state.focusedStack].push(b); newStacks[state.focusedStack].push(a); newStacks[state.focusedStack].push(c); return updateState(state, { stacks: newStacks }); }, doc: 'Rotate the top three items on the stack', stack: '( x1 x2 x3 -- x2 x3 x1 )' }, '-rot': { fn: (state) => { if (state.stacks[state.focusedStack].length < 3) { return updateState(state, { output: [...state.output, `Error: Stack underflow on -rot - Stack 1 (Red) needs 3 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const newStacks = state.stacks.map((stack, i) => [...stack]); const a = newStacks[state.focusedStack].pop(); const b = newStacks[state.focusedStack].pop(); const c = newStacks[state.focusedStack].pop(); newStacks[state.focusedStack].push(a); newStacks[state.focusedStack].push(c); newStacks[state.focusedStack].push(b); return updateState(state, { stacks: newStacks }); }, doc: 'Rotate the top three items on the stack (reverse of rot)', stack: '( x1 x2 x3 -- x3 x1 x2 )' }, // Cross-stack operations 'dup.stacks': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on dup.stacks - ${getFocusedStackName(state.focusedStack)} is empty. Add an item first.`] }); } return updateState(state, { crossStackInProgress: true, crossStackOperation: 'dup', crossStackData: { top: state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1] }, output: [...state.output, `Enter destination stack (1-4) to duplicate to:`] }); }, doc: 'Duplicate top item from focused stack to another stack', stack: '( x -- x x ) (interactive)' }, 'over.stacks': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on over.stacks - ${getFocusedStackName(state.focusedStack)} needs 2 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const second = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 2]; return updateState(state, { crossStackInProgress: true, crossStackOperation: 'over', crossStackData: { second }, output: [...state.output, `Enter destination stack (1-4) to copy second item to:`] }); }, doc: 'Copy second item from focused stack to another stack', stack: '( x1 x2 -- x1 x2 x1 ) (interactive)' }, 'swap.stacks': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on swap.stacks - ${getFocusedStackName(state.focusedStack)} needs 2 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; const second = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 2]; return updateState(state, { crossStackInProgress: true, crossStackOperation: 'swap', crossStackData: { top, second }, output: [...state.output, `Enter target stack (1-4) to swap with:`] }); }, doc: 'Swap top items between focused stack and another stack', stack: '( x1 x2 -- x2 x1 ) (interactive)' }, 'nip.stacks': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on nip.stacks - ${getFocusedStackName(state.focusedStack)} needs 2 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; const second = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 2]; return updateState(state, { crossStackInProgress: true, crossStackOperation: 'nip', crossStackData: { top, second }, output: [...state.output, `Enter destination stack (1-4) to move second item to:`] }); }, doc: 'Move second item from focused stack to another stack (remove from source)', stack: '( x1 x2 -- x1 ) (interactive)' }, 'tuck.stacks': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on tuck.stacks - ${getFocusedStackName(state.focusedStack)} needs 2 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; const second = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 2]; return updateState(state, { crossStackInProgress: true, crossStackOperation: 'tuck', crossStackData: { top, second }, output: [...state.output, `Enter destination stack (1-4) to tuck top item under second item:`] }); }, doc: 'Tuck top item under second item on another stack', stack: '( x1 x2 -- x2 x1 x2 ) (interactive)' }, 'rot.stacks': { fn: (state) => { if (state.stacks[state.focusedStack].length < 3) { return updateState(state, { output: [...state.output, `Error: Stack underflow on rot.stacks - ${getFocusedStackName(state.focusedStack)} needs 3 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const first = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 3]; const second = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 2]; const third = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; return updateState(state, { crossStackInProgress: true, crossStackOperation: 'rot', crossStackData: { first, second, third }, output: [...state.output, `Enter destination stack (1-4) to rotate with:`] }); }, doc: 'Rotate top 3 items between focused stack and another stack', stack: '( x1 x2 x3 -- x2 x3 x1 ) (interactive)' }, '2dup.stacks': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on 2dup.stacks - ${getFocusedStackName(state.focusedStack)} needs 2 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; const second = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 2]; return updateState(state, { crossStackInProgress: true, crossStackOperation: '2dup', crossStackData: { top, second }, output: [...state.output, `Enter destination stack (1-4) to duplicate top 2 items to:`] }); }, doc: 'Duplicate top 2 items from focused stack to another stack', stack: '( x1 x2 -- x1 x2 x1 x2 ) (interactive)' }, '2over.stacks': { fn: (state) => { if (state.stacks[state.focusedStack].length < 4) { return updateState(state, { output: [...state.output, `Error: Stack underflow on 2over.stacks - ${getFocusedStackName(state.focusedStack)} needs 4 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } return updateState(state, { crossStackInProgress: true, crossStackOperation: '2over', crossStackData: {}, output: [...state.output, `Enter destination stack (1-4) to copy second pair of items to:`] }); }, doc: 'Copy second pair of items from focused stack to another stack', stack: '( x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2 ) (interactive)' }, '2swap.stacks': { fn: (state) => { if (state.stacks[state.focusedStack].length < 4) { return updateState(state, { output: [...state.output, `Error: Stack underflow on 2swap.stacks - ${getFocusedStackName(state.focusedStack)} needs 4 items, but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const first = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 4]; const second = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 3]; const third = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 2]; const fourth = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; return updateState(state, { crossStackInProgress: true, crossStackOperation: '2swap', crossStackData: { first, second, third, fourth }, output: [...state.output, `Enter destination stack (1-4) to swap top 2 pairs with:`] }); }, doc: 'Swap top 2 pairs of items between focused stack and another stack', stack: '( x1 x2 x3 x4 -- x3 x4 x1 x2 ) (interactive)' }, // Arithmetic operations on stack 1 '+': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on + - ${getFocusedStackName(state.focusedStack)} needs 2 numbers, but has ${state.stacks[state.focusedStack].length}. Add more numbers first.`] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: a } = popFromStack(stacks1, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, a + b) }); }, doc: 'Add the top two numbers on the stack', stack: '( n1 n2 -- n3 )' }, '-': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on - - Stack 1 (Red) needs 2 numbers, but has ${state.stacks[state.focusedStack].length}. Add more numbers first.`] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: a } = popFromStack(stacks1, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, a - b) }); }, doc: 'Subtract the top number from the second number on the stack', stack: '( n1 n2 -- n3 )' }, '*': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on * - Stack 1 (Red) needs 2 numbers, but has ${state.stacks[state.focusedStack].length}. Add more numbers first.`] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: a } = popFromStack(stacks1, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, a * b) }); }, doc: 'Multiply the top two numbers on the stack', stack: '( n1 n2 -- n3 )' }, '/': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on / - Stack 1 (Red) needs 2 numbers, but has ${state.stacks[state.focusedStack].length}. Add more numbers first.`] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: a } = popFromStack(stacks1, state.focusedStack); if (b === 0) { return updateState(state, { stacks: pushToStack(pushToStack(stacks2, state.focusedStack, a), state.focusedStack, b), output: [...state.output, 'Error: Division by zero - Cannot divide by zero. Check your divisor before dividing.'] }); } return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, Math.floor(a / b)) }); }, doc: 'Divide the second number by the top number on the stack (integer division)', stack: '( n1 n2 -- n3 )' }, 'mod': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on mod - Stack 1 (Red) needs 2 numbers, but has ${state.stacks[state.focusedStack].length}. Add more numbers first.`] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: a } = popFromStack(stacks1, state.focusedStack); if (b === 0) { return updateState(state, { stacks: pushToStack(pushToStack(stacks2, state.focusedStack, a), state.focusedStack, b), output: [...state.output, 'Error: Modulo by zero - Cannot calculate remainder when dividing by zero. Check your divisor first.'] }); } return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, a % b) }); }, doc: 'Return the remainder of dividing the second number by the top number', stack: '( n1 n2 -- n3 )' }, 'abs': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on abs - Stack 1 (Red) is empty. Add a number first.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks, state.focusedStack, Math.abs(value)) }); }, doc: 'Return the absolute value of the top number on the stack', stack: '( n -- |n| )' }, 'negate': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on negate - ${getFocusedStackName(state.focusedStack)} is empty. Add a number first.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks, state.focusedStack, -value) }); }, doc: 'Return the negative of the top number on the stack', stack: '( n -- -n )' }, 'min': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on min - Stack 1 (Red) needs 2 numbers, but has ${state.stacks[state.focusedStack].length}. Add more numbers first.`] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: a } = popFromStack(stacks1, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, Math.min(a, b)) }); }, doc: 'Return the smaller of the top two numbers on the stack', stack: '( n1 n2 -- n3 )' }, 'max': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on max - Stack 1 (Red) needs 2 numbers, but has ${state.stacks[state.focusedStack].length}. Add more numbers first.`] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: a } = popFromStack(stacks1, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, Math.max(a, b)) }); }, doc: 'Return the larger of the top two numbers on the stack', stack: '( n1 n2 -- n3 )' }, // Comparison and logic '=': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on = - Stack 1 (Red) needs 2 values, but has ${state.stacks[state.focusedStack].length}. Add more values first.`] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: a } = popFromStack(stacks1, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, a === b ? -1 : 0) }); }, doc: 'Return true (-1) if the top two numbers are equal, false (0) otherwise', stack: '( n1 n2 -- flag )' }, '<': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on < - Stack 1 (Red) needs 2 values, but has ${state.stacks[state.focusedStack].length}. Add more values first.`] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: a } = popFromStack(stacks1, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, a < b ? -1 : 0) }); }, doc: 'Return true (-1) if the second number is less than the top number, false (0) otherwise', stack: '( n1 n2 -- flag )' }, '>': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on > - Stack 1 (Red) needs 2 values, but has ${state.stacks[state.focusedStack].length}. Add more values first.`] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: a } = popFromStack(stacks1, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, a > b ? -1 : 0) }); }, doc: 'Return true (-1) if the second number is greater than the top number, false (0) otherwise', stack: '( n1 n2 -- flag )' }, 'and': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on and - Stack 1 (Red) needs 2 values, but has ${state.stacks[state.focusedStack].length}. Add more values first.`] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: a } = popFromStack(stacks1, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, a && b ? -1 : 0) }); }, doc: 'Return true (-1) if both top two values are true, false (0) otherwise', stack: '( x1 x2 -- flag )' }, 'or': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on or - Stack 1 (Red) needs 2 values, but has ${state.stacks[state.focusedStack].length}. Add more values first.`] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: a } = popFromStack(stacks1, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, a || b ? -1 : 0) }); }, doc: 'Return true (-1) if either of the top two values is true, false (0) otherwise', stack: '( x1 x2 -- flag )' }, 'not': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on not - Stack 1 (Red) is empty. Add a value first.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks, state.focusedStack, value ? 0 : -1) }); }, doc: 'Return true (-1) if the top value is false, false (0) if it is true', stack: '( x -- flag )' }, // Stack inspection '.s': { fn: (state) => { const stackStr = state.stacks[state.focusedStack].length === 0 ? 'empty' : state.stacks[0].join(' '); return updateState(state, { output: [...state.output, `Stack 1 (red): ${stackStr}`] }); }, doc: 'Display the contents of the red stack (non-destructive)', stack: '( -- )' }, 'depth': { fn: (state) => { return updateState(state, { stacks: pushToStack(state.stacks, state.focusedStack, state.stacks[state.focusedStack].length) }); }, doc: 'Push the number of items on the red stack', stack: '( -- n )' }, '.': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on . - Stack 1 (Red) is empty. Nothing to print.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks, output: [...state.output, value.toString()] }); }, doc: 'Pop and print the top item from the red stack', stack: '( x -- )' }, // Multi-stack operations 'move.red': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on move.red - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to move.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks, 0, value) }); }, doc: 'Move top item from focused stack to red stack (removes from focused stack)', stack: '( x -- )' }, 'move.1': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on move.1 - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to move.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks, 0, value) }); }, doc: 'Move top item from focused stack to red stack (Stack 1) (removes from focused stack)', stack: '( x -- )' }, 'move.teal': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on move.teal - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to move.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks, 1, value) }); }, doc: 'Move top item from focused stack to teal stack (removes from focused stack)', stack: '( x -- )' }, 'move.2': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on move.2 - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to move.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks, 1, value) }); }, doc: 'Move top item from focused stack to teal stack (Stack 2) (removes from focused stack)', stack: '( x -- )' }, 'move.blue': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on move.blue - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to move.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks, 2, value) }); }, doc: 'Move top item from focused stack to blue stack (removes from focused stack)', stack: '( x -- )' }, 'move.3': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on move.3 - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to move.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks, 2, value) }); }, doc: 'Move top item from focused stack to blue stack (Stack 3) (removes from focused stack)', stack: '( x -- )' }, 'move.yellow': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on move.yellow - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to move.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks, 3, value) }); }, doc: 'Move top item from focused stack to yellow stack (removes from focused stack)', stack: '( x -- )' }, 'move.4': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on move.4 - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to move.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); return updateState(state, { stacks: pushToStack(stacks, 3, value) }); }, doc: 'Move top item from focused stack to yellow stack (Stack 4) (removes from focused stack)', stack: '( x -- )' }, // Copy operations (keep item in source stack) 'copy.red': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on copy.red - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to copy.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; return updateState(state, { stacks: pushToStack(state.stacks, 0, top) }); }, doc: 'Copy top item from focused stack to red stack (keeps item on focused stack)', stack: '( x -- x )' }, 'copy.1': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on copy.1 - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to copy.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; return updateState(state, { stacks: pushToStack(state.stacks, 0, top) }); }, doc: 'Copy top item from focused stack to red stack (Stack 1) (keeps item on focused stack)', stack: '( x -- x )' }, 'copy.teal': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on copy.teal - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to copy.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; return updateState(state, { stacks: pushToStack(state.stacks, 1, top) }); }, doc: 'Copy top item from focused stack to teal stack (keeps item on focused stack)', stack: '( x -- x )' }, 'copy.2': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on copy.2 - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to copy.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; return updateState(state, { stacks: pushToStack(state.stacks, 1, top) }); }, doc: 'Copy top item from focused stack to teal stack (Stack 2) (keeps item on focused stack)', stack: '( x -- x )' }, 'copy.blue': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on copy.blue - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to copy.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; return updateState(state, { stacks: pushToStack(state.stacks, 2, top) }); }, doc: 'Copy top item from focused stack to blue stack (keeps item on focused stack)', stack: '( x -- x )' }, 'copy.3': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on copy.3 - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to copy.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; return updateState(state, { stacks: pushToStack(state.stacks, 2, top) }); }, doc: 'Copy top item from focused stack to blue stack (Stack 3) (keeps item on focused stack)', stack: '( x -- x )' }, 'copy.yellow': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on copy.yellow - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to copy.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; return updateState(state, { stacks: pushToStack(state.stacks, 3, top) }); }, doc: 'Copy top item from focused stack to yellow stack (keeps item on focused stack)', stack: '( x -- x )' }, 'copy.4': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on copy.4 - ${getFocusedStackName(state.focusedStack)} is empty. Nothing to copy.`] }); } const top = state.stacks[state.focusedStack][state.stacks[state.focusedStack].length - 1]; return updateState(state, { stacks: pushToStack(state.stacks, 3, top) }); }, doc: 'Copy top item from focused stack to yellow stack (Stack 4) (keeps item on focused stack)', stack: '( x -- x )' }, 'pop.red': { fn: (state) => popAndPrint(state, 0), doc: 'Pop and print top item from red stack', stack: '( x -- )' }, 'pop.1': { fn: (state) => popAndPrint(state, 0), doc: 'Pop and print top item from red stack (Stack 1)', stack: '( x -- )' }, 'pop.teal': { fn: (state) => popAndPrint(state, 1), doc: 'Pop and print top item from teal stack', stack: '( x -- )' }, 'pop.2': { fn: (state) => popAndPrint(state, 1), doc: 'Pop and print top item from teal stack (Stack 2)', stack: '( x -- )' }, 'pop.blue': { fn: (state) => popAndPrint(state, 2), doc: 'Pop and print top item from blue stack', stack: '( x -- )' }, 'pop.3': { fn: (state) => popAndPrint(state, 2), doc: 'Pop and print top item from blue stack (Stack 3)', stack: '( x -- )' }, 'pop.yellow': { fn: (state) => popAndPrint(state, 3), doc: 'Pop and print top item from yellow stack', stack: '( x -- )' }, 'pop.4': { fn: (state) => popAndPrint(state, 3), doc: 'Pop and print top item from yellow stack (Stack 4)', stack: '( x -- )' }, // Move operations 'move': { fn: (state) => { return updateState(state, { moveInProgress: true, moveFromStack: null, output: [...state.output, 'Enter source stack (1-4) to move from:'] }); }, doc: 'Move top item from one stack to another (interactive: source, then destination)', stack: '( -- ) (interactive)' }, // Utility words 'clear': { fn: (state) => updateState(state, { stacks: [[], [], [], []], output: [...state.output, 'All stacks cleared'] }), doc: 'Clear all four stacks', stack: '( -- )' }, 'clear.all': { fn: (state) => updateState(state, { stacks: [[], [], [], []], output: [...state.output, 'All stacks cleared'] }), doc: 'Clear all four stacks (alias for clear)', stack: '( -- )' }, 'clear.focused': { fn: (state) => { const stackNames = ['Red (1)', 'Teal (2)', 'Blue (3)', 'Yellow (4)']; const newStacks = state.stacks.map((stack, i) => i === state.focusedStack ? [] : stack ); return updateState(state, { stacks: newStacks, output: [...state.output, `${stackNames[state.focusedStack]} cleared`] }); }, doc: 'Clear only the currently focused stack', stack: '( -- )' }, // String operations '."': { fn: (state) => { return updateState(state, { stringMode: true, currentString: '', stringPushMode: false }); }, doc: 'Begin a string literal that will be printed to output', stack: '( -- )' }, 's"': { fn: (state) => { return updateState(state, { stringMode: true, currentString: '', stringPushMode: true }); }, doc: 'Begin a string literal that will be pushed to the stack', stack: '( -- )' }, 'type': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on type - Stack 1 (Red) needs 2 items (address and length), but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const { stacks: stacks1, value: length } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: string } = popFromStack(stacks1, 0); return updateState(state, { stacks: stacks2, output: [...state.output, string.toString()] }); }, doc: 'Print a string from the stack (takes length and string address)', stack: '( addr len -- )' }, 'count': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on count - Stack 1 (Red) is empty. Add a string first.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); if (typeof value === 'string') { const newStacks = pushToStack(stacks, state.focusedStack, value.length); return updateState(state, { stacks: pushToStack(newStacks, state.focusedStack, value) }); } else { const newStacks = pushToStack(stacks, state.focusedStack, 0); return updateState(state, { stacks: pushToStack(newStacks, state.focusedStack, '') }); } }, doc: 'Extract string info from counted string (returns length and address)', stack: '( c-addr -- c-addr u )' }, 'char+': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on char+ - Stack 1 (Red) needs 2 items (string and offset), but has ${state.stacks[state.focusedStack].length}. Add more items first.`] }); } const { stacks: stacks1, value: offset } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: string } = popFromStack(stacks1, 0); if (typeof string === 'string') { return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, string + String.fromCharCode(offset)) }); } else { return updateState(state, { stacks: pushToStack(pushToStack(stacks2, state.focusedStack, string), 0, offset), output: [...state.output, `Error: char+ requires string on stack - Got ${typeof string}, expected string. Use s" to create strings.`] }); } }, doc: 'Add a character to a string using ASCII offset', stack: '( c-addr1 char -- c-addr2 )' }, 'strlen': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on strlen - Stack 1 (Red) is empty. Add a string first.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); if (typeof value === 'string') { return updateState(state, { stacks: pushToStack(stacks, state.focusedStack, value.length) }); } else { return updateState(state, { stacks: pushToStack(stacks, state.focusedStack, 0), output: [...state.output, `Error: strlen requires string on stack - Got ${typeof value}, expected string. Use s" to create strings.`] }); } }, doc: 'Get the length of a string on the stack', stack: '( str -- len )' }, 'strcat': { fn: (state) => { if (state.stacks[state.focusedStack].length < 2) { return updateState(state, { output: [...state.output, `Error: Stack underflow on strcat - Stack 1 (Red) needs 2 strings, but has ${state.stacks[state.focusedStack].length}. Add more strings first.`] }); } const { stacks: stacks1, value: str2 } = popFromStack(state.stacks, state.focusedStack); const { stacks: stacks2, value: str1 } = popFromStack(stacks1, 0); if (typeof str1 === 'string' && typeof str2 === 'string') { return updateState(state, { stacks: pushToStack(stacks2, state.focusedStack, str1 + str2) }); } else { return updateState(state, { stacks: pushToStack(pushToStack(stacks2, state.focusedStack, str1), 0, str2), output: [...state.output, `Error: strcat requires two strings - Got ${typeof str1} and ${typeof str2}. Use s" to create strings.`] }); } }, doc: 'Concatenate two strings from the stack', stack: '( str1 str2 -- str3 )' }, // Control flow 'if': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on if - Stack 1 (Red) is empty. Add a condition value first.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); if (value === 0) { // Skip until THEN or ELSE return updateState(state, { stacks, skipMode: true, skipCount: 0 }); } return updateState(state, { stacks }); }, doc: 'Begin conditional execution - if top of stack is false (0), skip to THEN', stack: '( flag -- )' }, 'else': { fn: (state) => { if (state.skipMode) { return updateState(state, { skipCount: state.skipCount + 1 }); } // Skip until THEN return updateState(state, { skipMode: true, skipCount: 0 }); }, doc: 'Begin alternative branch in conditional execution', stack: '( -- )' }, 'then': { fn: (state) => { if (state.skipMode && state.skipCount > 0) { return updateState(state, { skipCount: state.skipCount - 1 }); } else if (state.skipMode) { return updateState(state, { skipMode: false, skipCount: 0 }); } return state; }, doc: 'End conditional execution block', stack: '( -- )' }, 'begin': { fn: (state) => { return updateState(state, { loopStart: state.output.length }); }, doc: 'Mark the beginning of a loop', stack: '( -- )' }, 'until': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on until - Stack 1 (Red) is empty. Add a condition value first.`] }); } const { stacks, value } = popFromStack(state.stacks, state.focusedStack); if (value === 0) { // Loop back to BEGIN return updateState(state, { stacks, loopBack: true }); } return updateState(state, { stacks }); }, doc: 'End a loop - if top of stack is false (0), loop back to BEGIN', stack: '( flag -- )' }, // Help and documentation 'help': { fn: (state) => { const builtinWordNames = Object.keys(builtinWords); const userWords = Array.from(state.dictionary.keys()); const allWords = [...builtinWordNames, ...userWords]; const builtinList = builtinWordNames.map(name => { const word = builtinWords[name]; return `${name} ${word.stack} - ${word.doc}`; }); const userList = userWords.length > 0 ? userWords.join(' ') : 'none'; return updateState(state, { output: [ ...state.output, '=== 4-Stack Forth Interpreter Help ===', '', 'Built-in words:', ...builtinList, '', 'User defined words: ' + userList, '', 'Total words: ' + allWords.length, '', 'Use "doc " to get detailed help for a specific word', 'Use "words" to see just the word names' ] }); }, doc: 'Display comprehensive help information for all available words', stack: '( -- )' }, 'doc': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on doc - Stack 1 (Red) is empty. Add a word name first (e.g., s" dup" doc).`] }); } const { stacks, value: wordName } = popFromStack(state.stacks, state.focusedStack); // Check built-in words first if (builtinWords[wordName]) { const word = builtinWords[wordName]; return updateState(state, { stacks, output: [ ...state.output, `=== ${wordName} ===`, `Stack effect: ${word.stack}`, `Description: ${word.doc}`, `Type: Built-in word` ] }); } // Check user-defined words if (state.dictionary.has(wordName)) { const definition = state.dictionary.get(wordName); return updateState(state, { stacks, output: [ ...state.output, `=== ${wordName} ===`, `Definition: ${definition.join(' ')}`, `Type: User-defined word` ] }); } return updateState(state, { stacks, output: [...state.output, `Word '${wordName}' not found`] }); }, doc: 'Display documentation for a specific word', stack: '( "word" -- )' }, 'words': { fn: (state) => { const builtinWordNames = Object.keys(builtinWords); const userWords = Array.from(state.dictionary.keys()); const allWords = [...builtinWordNames, ...userWords]; const builtinList = builtinWordNames.join(' '); const userList = userWords.length > 0 ? userWords.join(' ') : 'none'; return updateState(state, { output: [ ...state.output, 'Built-in words: ' + builtinList, 'User defined words: ' + userWords.join(' '), 'Total words: ' + allWords.length ] }); }, doc: 'List all available words (built-in and user-defined)', stack: '( -- )' } }; // Help and documentation commands (defined outside builtinWords to avoid circular reference) const helpCommands = { 'help': { fn: (state) => { const builtinWordNames = Object.keys(builtinWords); const userWords = Array.from(state.dictionary.keys()); const allWords = [...builtinWordNames, ...userWords]; const builtinList = builtinWordNames.map(name => { const word = builtinWords[name]; return `${name} ${word.stack} - ${word.doc}`; }); const userList = userWords.length > 0 ? userWords.join(' ') : 'none'; return updateState(state, { output: [ ...state.output, '=== 4-Stack Forth Interpreter Help ===', '', 'Built-in words:', ...builtinList, '', 'User defined words: ' + userList, '', 'Total words: ' + allWords.length, '', 'Use "doc " to get detailed help for a specific word', 'Use "words" to see just the word names' ] }); }, doc: 'Display comprehensive help information for all available words', stack: '( -- )' }, 'doc': { fn: (state) => { if (state.stacks[state.focusedStack].length === 0) { return updateState(state, { output: [...state.output, `Error: Stack underflow on doc - Stack 1 (Red) is empty. Add a word name first (e.g., s" dup" doc).`] }); } const { stacks, value: wordName } = popFromStack(state.stacks, state.focusedStack); // Check built-in words first if (builtinWords[wordName]) { const word = builtinWords[wordName]; return updateState(state, { stacks, output: [ ...state.output, `=== ${wordName} ===`, `Stack effect: ${word.stack}`, `Description: ${word.doc}`, `Type: Built-in word` ] }); } // Check user-defined words if (state.dictionary.has(wordName)) { const definition = state.dictionary.get(wordName); return updateState(state, { stacks, output: [ ...state.output, `=== ${wordName} ===`, `Definition: ${definition.join(' ')}`, `Type: User-defined word` ] }); } return updateState(state, { stacks, output: [...state.output, `Word '${wordName}' not found`] }); }, doc: 'Display documentation for a specific word', stack: '( "word" -- )' } }; // Parse and execute Forth input const parseAndExecute = (state, input) => { const tokens = input.trim().split(/\s+/).filter(token => token.length > 0); return tokens.reduce(executeToken, state); }; // Execute a single token const executeToken = (state, token) => { // Handle string mode if (state.stringMode) { // Check if this token contains the closing quote const quoteIndex = token.indexOf('"'); if (quoteIndex !== -1) { // Token contains closing quote const beforeQuote = token.substring(0, quoteIndex); const afterQuote = token.substring(quoteIndex + 1); // Add the part before the quote to the string const finalString = state.currentString + (state.currentString ? ' ' : '') + beforeQuote; // End string mode and handle based on mode let newState; if (state.stringPushMode) { // Push mode: add string to stack newState = updateState(state, { stringMode: false, currentString: '', stringPushMode: false, stacks: pushToStack(state.stacks, state.focusedStack, finalString) }); } else { // Print mode: add to output newState = updateState(state, { stringMode: false, currentString: '', stringPushMode: false, output: [...state.output, finalString] }); } // If there's content after the quote, process it if (afterQuote.trim()) { newState = ForthInterpreter.parseAndExecute(newState, afterQuote); } return newState; } else { // Add to current string return updateState(state, { currentString: state.currentString + (state.currentString ? ' ' : '') + token }); } } // Handle skip mode (for control flow) if (state.skipMode) { if (token === 'if' || token === 'begin') { return updateState(state, { skipCount: state.skipCount + 1 }); } else if (token === 'then' || token === 'until') { if (state.skipCount > 0) { return updateState(state, { skipCount: state.skipCount - 1 }); } else { return updateState(state, { skipMode: false, skipCount: 0 }); } } else if (token === 'else') { if (state.skipCount === 0) { // Switch to skipping ELSE branch return updateState(state, { skipMode: true, skipCount: 0 }); } } // Skip this token return state; } // Handle move operation state machine if (state.moveInProgress) { if (state.moveFromStack === null) { // Expecting source stack number const from = parseInt(token); if (isNaN(from) || from < 1 || from > 4) { return updateState(state, { moveInProgress: false, moveFromStack: null, output: [...state.output, `Error: Invalid source stack ${from} - Must be 1, 2, 3, or 4.`] }); } if (state.stacks[from - 1].length === 0) { return updateState(state, { moveInProgress: false, moveFromStack: null, output: [...state.output, `Error: Stack ${from} is empty - Nothing to move. Add items to stack ${from} first.`] }); } return updateState(state, { moveInProgress: true, moveFromStack: from - 1, // Convert to 0-based index output: [...state.output, `Moving from stack ${from}. Enter destination stack (1-4):`] }); } else { // Expecting destination stack number const to = parseInt(token); if (isNaN(to) || to < 1 || to > 4) { return updateState(state, { moveInProgress: false, moveFromStack: null, output: [...state.output, `Error: Invalid destination stack ${to} - Must be 1, 2, 3, or 4.`] }); } const toIndex = to - 1; // Convert to 0-based index const fromIndex = state.moveFromStack; // Reset move state const newState = updateState(state, { moveInProgress: false, moveFromStack: null }); // Perform the move const { stacks, value } = popFromStack(newState.stacks, fromIndex); return updateState(newState, { stacks: pushToStack(stacks, toIndex, value), output: [...newState.output, `Moved ${value} from stack ${fromIndex + 1} to stack ${toIndex + 1}`] }); } } // Handle cross-stack operations state machine if (state.crossStackInProgress) { if (state.crossStackOperation === null) { // This shouldn't happen, but handle gracefully return updateState(state, { crossStackInProgress: false, crossStackOperation: null, crossStackData: null, output: [...state.output, 'Error: Cross-stack operation state corrupted'] }); } // Expecting target stack number const target = parseInt(token); if (isNaN(target) || target < 1 || target > 4) { return updateState(state, { crossStackInProgress: false, crossStackOperation: null, crossStackData: null, output: [...state.output, `Error: Invalid target stack ${target} - Must be 1, 2, 3, or 4.`] }); } const targetIndex = target - 1; // Convert to 0-based index const sourceIndex = state.focusedStack; // Execute the cross-stack operation (don't clear state yet) const result = executeCrossStackOperation(state, sourceIndex, targetIndex); // Clear cross-stack state after execution return updateState(result, { crossStackInProgress: false, crossStackOperation: null, crossStackData: null }); } // Handle word definition compilation if (state.compilingWord !== null) { if (token === ';') { const newDictionary = new Map(state.dictionary); newDictionary.set(state.compilingWord, [...state.compilingDefinition]); return updateState(state, { dictionary: newDictionary, compilingWord: null, compilingDefinition: [], output: [...state.output, `Word '${state.compilingWord}' defined`] }); } // If we're expecting a name, capture it if (state.compilingWord === 'EXPECTING_NAME') { return updateState(state, { compilingWord: token, compilingDefinition: [] }); } // Otherwise, add to definition return updateState(state, { compilingDefinition: [...state.compilingDefinition, token] }); } // Handle word definition start if (token === ':') { return updateState(state, { compilingWord: 'EXPECTING_NAME', compilingDefinition: [] }); } // Check if it's a built-in word if (builtinWords[token]) { return builtinWords[token].fn(state); } // Check if it's a user-defined word if (state.dictionary.has(token)) { const definition = state.dictionary.get(token); return definition.reduce(executeToken, state); } // Check if it's a number const num = parseFloat(token); if (!isNaN(num)) { return updateState(state, { stacks: pushToStack(state.stacks, state.focusedStack, num) }); } // Check if it's a cross-stack operation if (token.endsWith('.stacks')) { const baseOperation = token.replace('.stacks', ''); if (builtinWords[token]) { return builtinWords[token].fn(state); } } // Unknown token return updateState(state, { output: [...state.output, `Error: Unknown word '${token}' - Use 'help' to see all available words, or 'doc ' for specific help.`] }); }; // Export for use in other modules or testing if (typeof module !== 'undefined' && module.exports) { module.exports = { createInitialState, parseAndExecute, executeToken, builtinWords, updateState, pushToStack, popFromStack }; } else if (typeof window !== 'undefined') { // Browser environment window.ForthInterpreter = { createInitialState, parseAndExecute, executeToken, builtinWords, updateState, pushToStack, popFromStack }; }