// 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 }); // 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}`] }); }; // Built-in words with documentation const builtinWords = { // Stack manipulation for stack 1 (default) 'dup': { fn: (state) => { if (state.stacks[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on dup'] }); } const top = state.stacks[0][state.stacks[0].length - 1]; return updateState(state, { stacks: pushToStack(state.stacks, 0, top) }); }, doc: 'Duplicate the top item on the stack', stack: '( x -- x x )' }, 'swap': { fn: (state) => { if (state.stacks[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on swap'] }); } const newStacks = state.stacks.map((stack, i) => [...stack]); const a = newStacks[0].pop(); const b = newStacks[0].pop(); newStacks[0].push(a); newStacks[0].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[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on drop'] }); } const { stacks } = popFromStack(state.stacks, 0); return updateState(state, { stacks }); }, doc: 'Remove the top item from the stack', stack: '( x -- )' }, '2drop': { fn: (state) => { if (state.stacks[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on 2drop'] }); } const { stacks: stacks1 } = popFromStack(state.stacks, 0); 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[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on over'] }); } const second = state.stacks[0][state.stacks[0].length - 2]; return updateState(state, { stacks: pushToStack(state.stacks, 0, second) }); }, doc: 'Copy the second item on the stack to the top', stack: '( x1 x2 -- x1 x2 x1 )' }, '2dup': { fn: (state) => { if (state.stacks[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on 2dup'] }); } const top = state.stacks[0][state.stacks[0].length - 1]; const second = state.stacks[0][state.stacks[0].length - 2]; const newStacks = pushToStack(state.stacks, 0, second); return updateState(state, { stacks: pushToStack(newStacks, 0, top) }); }, doc: 'Duplicate the top two items on the stack', stack: '( x1 x2 -- x1 x2 x1 x2 )' }, 'rot': { fn: (state) => { if (state.stacks[0].length < 3) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on rot'] }); } const newStacks = state.stacks.map((stack, i) => [...stack]); const a = newStacks[0].pop(); const b = newStacks[0].pop(); const c = newStacks[0].pop(); newStacks[0].push(b); newStacks[0].push(a); newStacks[0].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[0].length < 3) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on -rot'] }); } const newStacks = state.stacks.map((stack, i) => [...stack]); const a = newStacks[0].pop(); const b = newStacks[0].pop(); const c = newStacks[0].pop(); newStacks[0].push(a); newStacks[0].push(c); newStacks[0].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 )' }, // Arithmetic operations on stack 1 '+': { fn: (state) => { if (state.stacks[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on +'] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: a } = popFromStack(stacks1, 0); return updateState(state, { stacks: pushToStack(stacks2, 0, a + b) }); }, doc: 'Add the top two numbers on the stack', stack: '( n1 n2 -- n3 )' }, '-': { fn: (state) => { if (state.stacks[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on -'] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: a } = popFromStack(stacks1, 0); return updateState(state, { stacks: pushToStack(stacks2, 0, a - b) }); }, doc: 'Subtract the top number from the second number on the stack', stack: '( n1 n2 -- n3 )' }, '*': { fn: (state) => { if (state.stacks[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on *'] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: a } = popFromStack(stacks1, 0); return updateState(state, { stacks: pushToStack(stacks2, 0, a * b) }); }, doc: 'Multiply the top two numbers on the stack', stack: '( n1 n2 -- n3 )' }, '/': { fn: (state) => { if (state.stacks[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on /'] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: a } = popFromStack(stacks1, 0); if (b === 0) { return updateState(state, { stacks: pushToStack(pushToStack(stacks2, 0, a), 0, b), output: [...state.output, 'Error: Division by zero'] }); } return updateState(state, { stacks: pushToStack(stacks2, 0, 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[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on mod'] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: a } = popFromStack(stacks1, 0); if (b === 0) { return updateState(state, { stacks: pushToStack(pushToStack(stacks2, 0, a), 0, b), output: [...state.output, 'Error: Modulo by zero'] }); } return updateState(state, { stacks: pushToStack(stacks2, 0, 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[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on abs'] }); } const { stacks, value } = popFromStack(state.stacks, 0); return updateState(state, { stacks: pushToStack(stacks, 0, Math.abs(value)) }); }, doc: 'Return the absolute value of the top number on the stack', stack: '( n -- |n| )' }, 'min': { fn: (state) => { if (state.stacks[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on min'] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: a } = popFromStack(stacks1, 0); return updateState(state, { stacks: pushToStack(stacks2, 0, 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[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on max'] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: a } = popFromStack(stacks1, 0); return updateState(state, { stacks: pushToStack(stacks2, 0, 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[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on ='] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: a } = popFromStack(stacks1, 0); return updateState(state, { stacks: pushToStack(stacks2, 0, 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[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on <'] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: a } = popFromStack(stacks1, 0); return updateState(state, { stacks: pushToStack(stacks2, 0, 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[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on >'] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: a } = popFromStack(stacks1, 0); return updateState(state, { stacks: pushToStack(stacks2, 0, 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[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on and'] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: a } = popFromStack(stacks1, 0); return updateState(state, { stacks: pushToStack(stacks2, 0, 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[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on or'] }); } const { stacks: stacks1, value: b } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: a } = popFromStack(stacks1, 0); return updateState(state, { stacks: pushToStack(stacks2, 0, 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[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on not'] }); } const { stacks, value } = popFromStack(state.stacks, 0); return updateState(state, { stacks: pushToStack(stacks, 0, 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[0].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, 0, state.stacks[0].length) }); }, doc: 'Push the number of items on the red stack', stack: '( -- n )' }, '.': { fn: (state) => { if (state.stacks[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on .'] }); } const { stacks, value } = popFromStack(state.stacks, 0); 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 'push.red': { fn: (state) => { if (state.stacks[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on push.red'] }); } const { stacks, value } = popFromStack(state.stacks, 0); return updateState(state, { stacks: pushToStack(stacks, 0, value) }); }, doc: 'Move top item from red stack to red stack (no-op, for consistency)', stack: '( x -- x )' }, 'push.teal': { fn: (state) => { if (state.stacks[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on push.teal'] }); } const { stacks, value } = popFromStack(state.stacks, 0); return updateState(state, { stacks: pushToStack(stacks, 1, value) }); }, doc: 'Move top item from red stack to teal stack', stack: '( x -- )' }, 'push.blue': { fn: (state) => { if (state.stacks[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on push.blue'] }); } const { stacks, value } = popFromStack(state.stacks, 0); return updateState(state, { stacks: pushToStack(stacks, 2, value) }); }, doc: 'Move top item from red stack to blue stack', stack: '( x -- )' }, 'push.yellow': { fn: (state) => { if (state.stacks[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on push.yellow'] }); } const { stacks, value } = popFromStack(state.stacks, 0); return updateState(state, { stacks: pushToStack(stacks, 3, value) }); }, doc: 'Move top item from red stack to yellow stack', stack: '( x -- )' }, 'pop.red': { fn: (state) => popAndPrint(state, 0), doc: 'Pop and print top item from red stack', stack: '( x -- )' }, 'pop.teal': { fn: (state) => popAndPrint(state, 1), doc: 'Pop and print top item from teal stack', stack: '( x -- )' }, 'pop.blue': { fn: (state) => popAndPrint(state, 2), doc: 'Pop and print top item from blue stack', stack: '( x -- )' }, 'pop.yellow': { fn: (state) => popAndPrint(state, 3), doc: 'Pop and print top item from yellow stack', stack: '( x -- )' }, // Utility words 'clear': { fn: (state) => updateState(state, { stacks: [[], [], [], []], output: [...state.output, 'All stacks cleared'] }), doc: 'Clear all four stacks', 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[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on type'] }); } const { stacks: stacks1, value: length } = popFromStack(state.stacks, 0); 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[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on count'] }); } const { stacks, value } = popFromStack(state.stacks, 0); if (typeof value === 'string') { const newStacks = pushToStack(stacks, 0, value.length); return updateState(state, { stacks: pushToStack(newStacks, 0, value) }); } else { const newStacks = pushToStack(stacks, 0, 0); return updateState(state, { stacks: pushToStack(newStacks, 0, '') }); } }, doc: 'Extract string info from counted string (returns length and address)', stack: '( c-addr -- c-addr u )' }, 'char+': { fn: (state) => { if (state.stacks[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on char+'] }); } const { stacks: stacks1, value: offset } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: string } = popFromStack(stacks1, 0); if (typeof string === 'string') { return updateState(state, { stacks: pushToStack(stacks2, 0, string + String.fromCharCode(offset)) }); } else { return updateState(state, { stacks: pushToStack(stacks2, 0, string), output: [...state.output, 'Error: char+ requires string on stack'] }); } }, doc: 'Add a character to a string using ASCII offset', stack: '( c-addr1 char -- c-addr2 )' }, 'strlen': { fn: (state) => { if (state.stacks[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on strlen'] }); } const { stacks, value } = popFromStack(state.stacks, 0); if (typeof value === 'string') { return updateState(state, { stacks: pushToStack(stacks, 0, value.length) }); } else { return updateState(state, { stacks: pushToStack(stacks, 0, 0), output: [...state.output, 'Error: strlen requires string on stack'] }); } }, doc: 'Get the length of a string on the stack', stack: '( str -- len )' }, 'strcat': { fn: (state) => { if (state.stacks[0].length < 2) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on strcat'] }); } const { stacks: stacks1, value: str2 } = popFromStack(state.stacks, 0); const { stacks: stacks2, value: str1 } = popFromStack(stacks1, 0); if (typeof str1 === 'string' && typeof str2 === 'string') { return updateState(state, { stacks: pushToStack(stacks2, 0, str1 + str2) }); } else { return updateState(state, { stacks: pushToStack(pushToStack(stacks2, 0, str1), 0, str2), output: [...state.output, `Error: strcat requires two strings, got ${typeof str1} and ${typeof str2}`] }); } }, doc: 'Concatenate two strings from the stack', stack: '( str1 str2 -- str3 )' }, // Control flow 'if': { fn: (state) => { if (state.stacks[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on if'] }); } const { stacks, value } = popFromStack(state.stacks, 0); 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[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on until'] }); } const { stacks, value } = popFromStack(state.stacks, 0); 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}`; }).join('\n'); 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[0].length === 0) { return updateState(state, { output: [...state.output, 'Error: Stack underflow on doc'] }); } const { stacks, value: wordName } = popFromStack(state.stacks, 0); // 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: ' + userList, 'Total words: ' + allWords.length ] }); }, doc: 'List all available words (built-in and user-defined)', stack: '( -- )' } }; // 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, 0, 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. Must be 1-4'] }); } if (state.stacks[from - 1].length === 0) { return updateState(state, { moveInProgress: false, moveFromStack: null, output: [...state.output, `Error: Stack ${from} is empty`] }); } 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. Must be 1-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 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, 0, num) }); } // Check if it's a move command if (token === 'move') { return updateState(state, { moveInProgress: true, moveFromStack: null }); } // Unknown token return updateState(state, { output: [...state.output, `Error: Unknown word '${token}'`] }); }; // 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 }; }