diff options
Diffstat (limited to 'forth/foreforthfourth/forth.js')
-rw-r--r-- | forth/foreforthfourth/forth.js | 1973 |
1 files changed, 1973 insertions, 0 deletions
diff --git a/forth/foreforthfourth/forth.js b/forth/foreforthfourth/forth.js new file mode 100644 index 0000000..af133ea --- /dev/null +++ b/forth/foreforthfourth/forth.js @@ -0,0 +1,1973 @@ +// 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 <word>" 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 <word>" 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 <word>' 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 + }; +} |