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