├── .gitignore ├── ans-forth.md ├── browser └── main.js ├── bundle.js ├── css └── forth.css ├── forth └── forth.fth ├── index.html ├── kernel ├── boolean-operations.js ├── control-structures.js ├── data.js ├── definitions.js ├── forth.js ├── include.js ├── input-exceptions.js ├── input.js ├── interpreter.js ├── js-interop.js ├── memory-operations.js ├── numeric-operations.js ├── output.js ├── stack-operations.js └── stack.js ├── package.json ├── readme.md ├── static └── bundle.js ├── test ├── run-ans-forth-tests.js ├── runtests.fth └── verbose-tester.fth ├── todo.md └── universal-stack-operations.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.map 3 | node_modules -------------------------------------------------------------------------------- /ans-forth.md: -------------------------------------------------------------------------------- 1 | # Categorised ANSI Forth 2 | 3 | Runtime 4 | ------- 5 | 6.1.2050 QUIT 6 | CORE 7 | ( -- ) ( R: i*x -- ) 8 | Empty the return stack, store zero in SOURCE-ID if it is present, make the user input device the input source, and enter interpretation state. Do not display a message. Repeat the following: 9 | 10 | Accept a line from the input source into the input buffer, set >IN to zero, and interpret. 11 | Display the implementation-defined system prompt if in interpretation state, all processing has been completed, and no ambiguous condition exists. 12 | See: 3.4 The Forth text interpreter 13 | 14 | 6.1.0670 ABORT 15 | CORE 16 | ( i*x -- ) ( R: j*x -- ) 17 | Empty the data stack and perform the function of QUIT, which includes emptying the return stack, without displaying a message. 18 | 19 | See: 9.6.2.0670 ABORT 20 | 21 | 6.1.0680 ABORT" 22 | abort-quote CORE 23 | Interpretation: Interpretation semantics for this word are undefined. 24 | Compilation: ( "ccc" -- ) 25 | Parse ccc delimited by a " (double-quote). Append the run-time semantics given below to the current definition. 26 | 27 | Run-time: ( i*x x1 -- | i*x ) ( R: j*x -- | j*x ) 28 | Remove x1 from the stack. If any bit of x1 is not zero, display ccc and perform an implementation-defined abort sequence that includes the function of ABORT. 29 | 30 | 6.1.1370 EXECUTE 31 | CORE 32 | ( i*x xt -- j*x ) 33 | Remove xt from the stack and perform the semantics identified by it. Other stack effects are due to the word EXECUTEd. 34 | 35 | 6.1.1360 EVALUATE 36 | CORE 37 | ( i*x c-addr u -- j*x ) 38 | Save the current input source specification. Store minus-one (-1) in SOURCE-ID if it is present. Make the string described by c-addr and u both the input source and input buffer, set >IN to zero, and interpret. When the parse area is empty, restore the prior input source specification. Other stack effects are due to the words EVALUATEd. 39 | 40 | 6.1.1345 ENVIRONMENT? 41 | environment-query CORE 42 | ( c-addr u -- false | i*x true ) 43 | c-addr is the address of a character string and u is the string's character count. u may have a value in the range from zero to an implementation-defined maximum which shall not be less than 31. The character string should contain a keyword from 3.2.6 Environmental queries or the optional word sets to be checked for correspondence with an attribute of the present environment. If the system treats the attribute as unknown, the returned flag is false; otherwise, the flag is true and the i*x returned is of the type specified in the table for the attribute queried. 44 | 45 | 46 | Compilation 47 | ----------- 48 | 6.1.0450 : 49 | colon CORE 50 | ( C: "name" -- colon-sys ) 51 | Skip leading space delimiters. Parse name delimited by a space. Create a definition for name, called a colon definition. Enter compilation state and start the current definition, producing colon-sys. Append the initiation semantics given below to the current definition. 52 | 53 | The execution semantics of name will be determined by the words compiled into the body of the definition. The current definition shall not be findable in the dictionary until it is ended (or until the execution of DOES> in some systems). 54 | 55 | Initiation: ( i*x -- i*x ) ( R: -- nest-sys ) 56 | Save implementation-dependent information nest-sys about the calling definition. The stack effects i*x represent arguments to name. 57 | 58 | name Execution: ( i*x -- j*x ) 59 | Execute the definition name. The stack effects i*x and j*x represent arguments to and results from name, respectively. 60 | 61 | 6.1.0460 ; 62 | semicolon CORE 63 | Interpretation: Interpretation semantics for this word are undefined. 64 | Compilation: ( C: colon-sys -- ) 65 | Append the run-time semantics below to the current definition. End the current definition, allow it to be found in the dictionary and enter interpretation state, consuming colon-sys. If the data-space pointer is not aligned, reserve enough data space to align it. 66 | 67 | Run-time: ( -- ) ( R: nest-sys -- ) 68 | Return to the calling definition specified by nest-sys. 69 | 70 | 6.1.1000 CREATE 71 | CORE 72 | ( "name" -- ) 73 | Skip leading space delimiters. Parse name delimited by a space. Create a definition for name with the execution semantics defined below. If the data-space pointer is not aligned, reserve enough data space to align it. The new data-space pointer defines name's data field. CREATE does not allocate data space in name's data field. 74 | 75 | name Execution: ( -- a-addr ) 76 | a-addr is the address of name's data field. The execution semantics of name may be extended by using DOES>. 77 | 78 | 6.1.1550 FIND 79 | CORE 80 | ( c-addr -- c-addr 0 | xt 1 | xt -1 ) 81 | Find the definition named in the counted string at c-addr. If the definition is not found, return c-addr and zero. If the definition is found, return its execution token xt. If the definition is immediate, also return one (1), otherwise also return minus-one (-1). For a given string, the values returned by FIND while compiling may differ from those returned while not compiling. 82 | 83 | 6.1.1710 IMMEDIATE 84 | CORE 85 | ( -- ) 86 | Make the most recent definition an immediate word. An ambiguous condition exists if the most recent definition does not have a name. 87 | 88 | See: A.6.1.1710 IMMEDIATE , D.6.7 Immediacy, RFI 0007 Distinction between immediacy and special compilation semantics. 89 | 90 | 6.1.1780 LITERAL 91 | CORE 92 | Interpretation: Interpretation semantics for this word are undefined. 93 | Compilation: ( x -- ) 94 | Append the run-time semantics given below to the current definition. 95 | 96 | Run-time: ( -- x ) 97 | Place x on the stack. 98 | 99 | 6.1.1250 DOES> 100 | does CORE 101 | Interpretation: Interpretation semantics for this word are undefined. 102 | Compilation: ( C: colon-sys1 -- colon-sys2 ) 103 | Append the run-time semantics below to the current definition. Whether or not the current definition is rendered findable in the dictionary by the compilation of DOES> is implementation defined. Consume colon-sys1 and produce colon-sys2. Append the initiation semantics given below to the current definition. 104 | 105 | Run-time: ( -- ) ( R: nest-sys1 -- ) 106 | Replace the execution semantics of the most recent definition, referred to as name, with the name execution semantics given below. Return control to the calling definition specified by nest-sys1. An ambiguous condition exists if name was not defined with CREATE or a user-defined word that calls CREATE. 107 | 108 | Initiation: ( i*x -- i*x a-addr ) ( R: -- nest-sys2 ) 109 | Save implementation-dependent information nest-sys2 about the calling definition. Place name's data field address on the stack. The stack effects i*x represent arguments to name. 110 | 111 | name Execution: ( i*x -- j*x ) 112 | Execute the portion of the definition that begins with the initiation semantics appended by the DOES> which modified name. The stack effects i*x and j*x represent arguments to and results from name, respectively. 113 | 114 | 6.1.2500 [ 115 | left-bracket CORE 116 | Interpretation: Interpretation semantics for this word are undefined. 117 | Compilation: Perform the execution semantics given below. 118 | Execution: ( -- ) 119 | Enter interpretation state. [ is an immediate word. 120 | 121 | 6.1.2540 ] 122 | right-bracket CORE 123 | ( -- ) 124 | Enter compilation state. 125 | 126 | 6.1.0150 , 127 | comma CORE 128 | ( x -- ) 129 | Reserve one cell of data space and store x in the cell. If the data-space pointer is aligned when , begins execution, it will remain aligned when , finishes execution. An ambiguous condition exists if the data-space pointer is not aligned prior to execution of ,. 130 | 131 | 6.1.2033 POSTPONE 132 | CORE 133 | Interpretation: Interpretation semantics for this word are undefined. 134 | Compilation: ( "name" -- ) 135 | Skip leading space delimiters. Parse name delimited by a space. Find name. Append the compilation semantics of name to the current definition. An ambiguous condition exists if name is not found. 136 | 137 | 6.1.0070 ' 138 | tick CORE 139 | ( "name" -- xt ) 140 | Skip leading space delimiters. Parse name delimited by a space. Find name and return xt, the execution token for name. An ambiguous condition exists if name is not found. 141 | 142 | When interpreting, ' xyz EXECUTE is equivalent to xyz. 143 | 144 | 6.1.2510 ['] 145 | bracket-tick CORE 146 | Interpretation: Interpretation semantics for this word are undefined. 147 | Compilation: ( "name" -- ) 148 | Skip leading space delimiters. Parse name delimited by a space. Find name. Append the run-time semantics given below to the current definition. 149 | 150 | An ambiguous condition exists if name is not found. 151 | 152 | Run-time: ( -- xt ) 153 | Place name's execution token xt on the stack. The execution token returned by the compiled phrase ['] X is the same value returned by ' X outside of compilation state. 154 | 155 | Control Structures 156 | ------------------ 157 | 6.1.1700 IF 158 | CORE 159 | Interpretation: Interpretation semantics for this word are undefined. 160 | Compilation: ( C: -- orig ) 161 | Put the location of a new unresolved forward reference orig onto the control flow stack. Append the run-time semantics given below to the current definition. The semantics are incomplete until orig is resolved, e.g., by THEN or ELSE. 162 | 163 | Run-time: ( x -- ) 164 | If all bits of x are zero, continue execution at the location specified by the resolution of orig. 165 | 166 | 6.1.2270 THEN 167 | CORE 168 | Interpretation: Interpretation semantics for this word are undefined. 169 | Compilation: ( C: orig -- ) 170 | Append the run-time semantics given below to the current definition. Resolve the forward reference orig using the location of the appended run-time semantics. 171 | 172 | Run-time: ( -- ) 173 | Continue execution. 174 | 175 | 6.1.1310 ELSE 176 | CORE 177 | Interpretation: Interpretation semantics for this word are undefined. 178 | Compilation: ( C: orig1 -- orig2 ) 179 | Put the location of a new unresolved forward reference orig2 onto the control flow stack. Append the run-time semantics given below to the current definition. The semantics will be incomplete until orig2 is resolved (e.g., by THEN). Resolve the forward reference orig1 using the location following the appended run-time semantics. 180 | 181 | Run-time: ( -- ) 182 | Continue execution at the location given by the resolution of orig2. 183 | 184 | 6.1.1240 DO 185 | CORE 186 | Interpretation: Interpretation semantics for this word are undefined. 187 | Compilation: ( C: -- do-sys ) 188 | Place do-sys onto the control-flow stack. Append the run-time semantics given below to the current definition. The semantics are incomplete until resolved by a consumer of do-sys such as LOOP. 189 | 190 | Run-time: ( n1|u1 n2|u2 -- ) ( R: -- loop-sys ) 191 | Set up loop control parameters with index n2|u2 and limit n1|u1. An ambiguous condition exists if n1|u1 and n2|u2 are not both the same type. Anything already on the return stack becomes unavailable until the loop-control parameters are discarded. 192 | 193 | 6.1.1800 LOOP 194 | CORE 195 | Interpretation: Interpretation semantics for this word are undefined. 196 | Compilation: ( C: do-sys -- ) 197 | Append the run-time semantics given below to the current definition. Resolve the destination of all unresolved occurrences of LEAVE between the location given by do-sys and the next location for a transfer of control, to execute the words following the LOOP. 198 | 199 | Run-time: ( -- ) ( R: loop-sys1 -- | loop-sys2 ) 200 | An ambiguous condition exists if the loop control parameters are unavailable. Add one to the loop index. If the loop index is then equal to the loop limit, discard the loop parameters and continue execution immediately following the loop. Otherwise continue execution at the beginning of the loop. 201 | 202 | 6.1.0140 +LOOP 203 | plus-loop CORE 204 | Interpretation: Interpretation semantics for this word are undefined. 205 | Compilation: ( C: do-sys -- ) 206 | Append the run-time semantics given below to the current definition. Resolve the destination of all unresolved occurrences of LEAVE between the location given by do-sys and the next location for a transfer of control, to execute the words following +LOOP. 207 | 208 | Run-time: ( n -- ) ( R: loop-sys1 -- | loop-sys2 ) 209 | An ambiguous condition exists if the loop control parameters are unavailable. Add n to the loop index. If the loop index did not cross the boundary between the loop limit minus one and the loop limit, continue execution at the beginning of the loop. Otherwise, discard the current loop control parameters and continue execution immediately following the loop. 210 | 211 | 6.1.2380 UNLOOP 212 | CORE 213 | Interpretation: Interpretation semantics for this word are undefined. 214 | Execution: ( -- ) ( R: loop-sys -- ) 215 | Discard the loop-control parameters for the current nesting level. An UNLOOP is required for each nesting level before the definition may be EXITed. An ambiguous condition exists if the loop-control parameters are unavailable. 216 | 217 | 6.1.1760 LEAVE 218 | CORE 219 | Interpretation: Interpretation semantics for this word are undefined. 220 | Execution: ( -- ) ( R: loop-sys -- ) 221 | Discard the current loop control parameters. An ambiguous condition exists if they are unavailable. Continue execution immediately following the innermost syntactically enclosing DO ... LOOP or DO ... +LOOP. 222 | 223 | 6.1.1680 I 224 | CORE 225 | Interpretation: Interpretation semantics for this word are undefined. 226 | Execution: ( -- n|u ) ( R: loop-sys -- loop-sys ) 227 | n|u is a copy of the current (innermost) loop index. An ambiguous condition exists if the loop control parameters are unavailable. 228 | 229 | 6.1.1730 J 230 | CORE 231 | Interpretation: Interpretation semantics for this word are undefined. 232 | Execution: ( -- n|u ) ( R: loop-sys1 loop-sys2 -- loop-sys1 loop-sys2 ) 233 | n|u is a copy of the next-outer loop index. An ambiguous condition exists if the loop control parameters of the next-outer loop, loop-sys1, are unavailable. 234 | 235 | 6.1.0760 BEGIN 236 | CORE 237 | Interpretation: Interpretation semantics for this word are undefined. 238 | Compilation: ( C: -- dest ) 239 | Put the next location for a transfer of control, dest, onto the control flow stack. Append the run-time semantics given below to the current definition. 240 | 241 | Run-time: ( -- ) 242 | Continue execution. 243 | 244 | 6.1.2390 UNTIL 245 | CORE 246 | Interpretation: Interpretation semantics for this word are undefined. 247 | Compilation: ( C: dest -- ) 248 | Append the run-time semantics given below to the current definition, resolving the backward reference dest. 249 | 250 | Run-time: ( x -- ) 251 | If all bits of x are zero, continue execution at the location specified by dest. 252 | 253 | 6.1.2430 WHILE 254 | CORE 255 | Interpretation: Interpretation semantics for this word are undefined. 256 | Compilation: ( C: dest -- orig dest ) 257 | Put the location of a new unresolved forward reference orig onto the control flow stack, under the existing dest. Append the run-time semantics given below to the current definition. The semantics are incomplete until orig and dest are resolved (e.g., by REPEAT). 258 | 259 | Run-time: ( x -- ) 260 | If all bits of x are zero, continue execution at the location specified by the resolution of orig. 261 | 262 | 6.1.2140 REPEAT 263 | CORE 264 | Interpretation: Interpretation semantics for this word are undefined. 265 | Compilation: ( C: orig dest -- ) 266 | Append the run-time semantics given below to the current definition, resolving the backward reference dest. Resolve the forward reference orig using the location following the appended run-time semantics. 267 | 268 | Run-time: ( -- ) 269 | Continue execution at the location given by dest. 270 | 271 | 6.1.1380 EXIT 272 | CORE 273 | Interpretation: Interpretation semantics for this word are undefined. 274 | Execution: ( -- ) ( R: nest-sys -- ) 275 | Return control to the calling definition specified by nest-sys. Before executing EXIT within a do-loop, a program shall discard the loop-control parameters by executing UNLOOP. 276 | 277 | 6.1.2120 RECURSE 278 | CORE 279 | Interpretation: Interpretation semantics for this word are undefined. 280 | Compilation: ( -- ) 281 | Append the execution semantics of the current definition to the current definition. An ambiguous condition exists if RECURSE appears in a definition after DOES>. 282 | 283 | Addresses 284 | --------- 285 | 286 | 6.1.0010 ! 287 | store CORE 288 | ( x a-addr -- ) 289 | Store x at a-addr. 290 | 291 | 6.1.0650 @ 292 | fetch CORE 293 | ( a-addr -- x ) 294 | x is the value stored at a-addr. 295 | 296 | 6.1.1900 MOVE 297 | CORE 298 | ( addr1 addr2 u -- ) 299 | If u is greater than zero, copy the contents of u consecutive address units at addr1 to the u consecutive address units at addr2. After MOVE completes, the u consecutive address units at addr2 contain exactly what the u consecutive address units at addr1 contained before the move. 300 | 301 | 6.1.2060 R> 302 | r-from CORE 303 | Interpretation: Interpretation semantics for this word are undefined. 304 | Execution: ( -- x ) ( R: x -- ) 305 | Move x from the return stack to the data stack. 306 | 307 | 6.1.0580 >R 308 | to-r CORE 309 | Interpretation: Interpretation semantics for this word are undefined. 310 | Execution: ( x -- ) ( R: -- x ) 311 | Move x to the return stack. 312 | 313 | 6.1.2070 R@ 314 | r-fetch CORE 315 | Interpretation: Interpretation semantics for this word are undefined. 316 | Execution: ( -- x ) ( R: x -- x ) 317 | Copy x from the return stack to the data stack. 318 | 319 | 6.1.1540 FILL 320 | CORE 321 | ( c-addr u char -- ) 322 | If u is greater than zero, store char in each of u consecutive characters of memory beginning at c-addr. 323 | 324 | 6.2.0340 2>R 325 | two-to-r CORE EXT 326 | Interpretation: Interpretation semantics for this word are undefined. 327 | Execution: ( x1 x2 -- ) ( R: -- x1 x2 ) 328 | Transfer cell pair x1 x2 to the return stack. Semantically equivalent to SWAP >R >R . 329 | 330 | 6.1.0550 >BODY 331 | to-body CORE 332 | ( xt -- a-addr ) 333 | a-addr is the data-field address corresponding to xt. An ambiguous condition exists if xt is not for a word defined via CREATE. 334 | 335 | 6.1.0560 >IN 336 | to-in CORE 337 | ( -- a-addr ) 338 | a-addr is the address of a cell containing the offset in characters from the start of the input buffer to the start of the parse area. 339 | 340 | 6.1.0705 ALIGN 341 | CORE 342 | ( -- ) 343 | If the data-space pointer is not aligned, reserve enough space to align it. 344 | 345 | See: 3.3.3 Data space, 3.3.3.1 Address alignment, A.6.1.0705 ALIGN 346 | 347 | 6.1.0706 ALIGNED 348 | CORE 349 | ( addr -- a-addr ) 350 | a-addr is the first aligned address greater than or equal to addr. 351 | 352 | See: 3.3.3.1 Address alignment, 6.1.0705 ALIGN 353 | 354 | 6.1.0710 ALLOT 355 | CORE 356 | ( n -- ) 357 | If n is greater than zero, reserve n address units of data space. If n is less than zero, release |n| address units of data space. If n is zero, leave the data-space pointer unchanged. 358 | 359 | If the data-space pointer is aligned and n is a multiple of the size of a cell when ALLOT begins execution, it will remain aligned when ALLOT finishes execution. 360 | 361 | If the data-space pointer is character aligned and n is a multiple of the size of a character when ALLOT begins execution, it will remain character aligned when ALLOT finishes execution. 362 | 363 | 6.2.0410 2R> 364 | two-r-from CORE EXT 365 | Interpretation: Interpretation semantics for this word are undefined. 366 | Execution: ( -- x1 x2 ) ( R: x1 x2 -- ) 367 | Transfer cell pair x1 x2 from the return stack. Semantically equivalent to R> R> SWAP . 368 | 369 | 6.2.0415 2R@ 370 | two-r-fetch CORE EXT 371 | Interpretation: Interpretation semantics for this word are undefined. 372 | Execution: ( -- x1 x2 ) ( R: x1 x2 -- x1 x2 ) 373 | Copy cell pair x1 x2 from the return stack. Semantically equivalent to R> R> 2DUP >R >R SWAP . 374 | 375 | 6.1.0860 C, 376 | c-comma CORE 377 | ( char -- ) 378 | Reserve space for one character in the data space and store char in the space. If the data-space pointer is character aligned when C, begins execution, it will remain character aligned when C, finishes execution. An ambiguous condition exists if the data-space pointer is not character-aligned prior to execution of C,. 379 | 380 | 6.1.0870 C@ 381 | c-fetch CORE 382 | ( c-addr -- char ) 383 | Fetch the character stored at c-addr. When the cell size is greater than character size, the unused high-order bits are all zeroes. 384 | 385 | 6.1.0850 C! 386 | c-store CORE 387 | ( char c-addr -- ) 388 | Store char at c-addr. When character size is smaller than cell size, only the number of low-order bits corresponding to character size are transferred. 389 | 390 | 6.1.0880 CELL+ 391 | CORE 392 | ( a-addr1 -- a-addr2 ) 393 | Add the size in address units of a cell to a-addr1, giving a-addr2. 394 | 395 | See: 3.3.3.1 Address alignment, A.6.1.0880 CELL+ 396 | 397 | 6.1.0890 CELLS 398 | CORE 399 | ( n1 -- n2 ) 400 | n2 is the size in address units of n1 cells. 401 | 402 | 6.1.0130 +! 403 | plus-store CORE 404 | ( n|u a-addr -- ) 405 | Add n|u to the single-cell number at a-addr. 406 | 407 | 6.1.0310 2! 408 | two-store CORE 409 | ( x1 x2 a-addr -- ) 410 | Store the cell pair x1 x2 at a-addr, with x2 at a-addr and x1 at the next consecutive cell. It is equivalent to the sequence SWAP OVER ! CELL+ ! . 411 | 412 | 6.1.0350 2@ 413 | two-fetch CORE 414 | ( a-addr -- x1 x2 ) 415 | Fetch the cell pair x1 x2 stored at a-addr. x2 is stored at a-addr and x1 at the next consecutive cell. It is equivalent to the sequence DUP CELL+ @ SWAP @ . 416 | 417 | Variables 418 | --------- 419 | 420 | 6.1.2410 VARIABLE 421 | CORE 422 | ( "name" -- ) 423 | Skip leading space delimiters. Parse name delimited by a space. Create a definition for name with the execution semantics defined below. Reserve one cell of data space at an aligned address. 424 | 425 | name is referred to as a variable. 426 | 427 | name Execution: ( -- a-addr ) 428 | a-addr is the address of the reserved cell. A program is responsible for initializing the contents of the reserved cell. 429 | 430 | 6.1.0950 CONSTANT 431 | CORE 432 | ( x "name" -- ) 433 | Skip leading space delimiters. Parse name delimited by a space. Create a definition for name with the execution semantics defined below. 434 | 435 | name is referred to as a constant. 436 | 437 | name Execution: ( -- x ) 438 | Place x on the stack. 439 | 440 | 6.1.1650 HERE 441 | CORE 442 | ( -- addr ) 443 | addr is the data-space pointer. 444 | 445 | See: 3.3.3.2 Contiguous regions 446 | 447 | 6.1.2216 SOURCE 448 | CORE 449 | ( -- c-addr u ) 450 | c-addr is the address of, and u is the number of characters in, the input buffer. 451 | 452 | See: A.6.1.2216 SOURCE , RFI 0006 Writing to Input Buffers. 453 | 454 | 6.1.2250 STATE 455 | CORE 456 | ( -- a-addr ) 457 | a-addr is the address of a cell containing the compilation-state flag. STATE is true when in compilation state, false otherwise. The true value in STATE is non-zero, but is otherwise implementation-defined. Only the following standard words alter the value in STATE: : (colon), ; (semicolon), ABORT, QUIT, :NONAME, [ (left-bracket), and ] (right-bracket). 458 | 459 | Note: A program shall not directly alter the contents of STATE. 460 | 461 | 462 | Stack Operations 463 | ---------------- 464 | 6.1.1260 DROP 465 | CORE 466 | ( x -- ) 467 | Remove x from the stack. 468 | 469 | 6.1.1290 DUP 470 | dupe CORE 471 | ( x -- x x ) 472 | Duplicate x. 473 | 474 | 6.1.1990 OVER 475 | CORE 476 | ( x1 x2 -- x1 x2 x1 ) 477 | Place a copy of x1 on top of the stack. 478 | 479 | 6.1.2260 SWAP 480 | CORE 481 | ( x1 x2 -- x2 x1 ) 482 | Exchange the top two stack items. 483 | 484 | 6.1.2160 ROT 485 | rote CORE 486 | ( x1 x2 x3 -- x2 x3 x1 ) 487 | Rotate the top three stack entries. 488 | 489 | 6.1.0370 2DROP 490 | two-drop CORE 491 | ( x1 x2 -- ) 492 | Drop cell pair x1 x2 from the stack. 493 | 494 | 6.1.0380 2DUP 495 | two-dupe CORE 496 | ( x1 x2 -- x1 x2 x1 x2 ) 497 | Duplicate cell pair x1 x2. 498 | 499 | 6.1.0400 2OVER 500 | two-over CORE 501 | ( x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2 ) 502 | Copy cell pair x1 x2 to the top of the stack. 503 | 504 | 6.1.0430 2SWAP 505 | two-swap CORE 506 | ( x1 x2 x3 x4 -- x3 x4 x1 x2 ) 507 | Exchange the top two cell pairs. 508 | 509 | 6.1.0630 ?DUP 510 | question-dupe CORE 511 | ( x -- 0 | x x ) 512 | Duplicate x if it is non-zero. 513 | 514 | 6.1.1200 DEPTH 515 | CORE 516 | ( -- +n ) 517 | +n is the number of single-cell values contained in the data stack before +n was placed on the stack. 518 | 519 | 520 | Bitwise Operations 521 | ------------------ 522 | 523 | 6.1.0720 AND 524 | CORE 525 | ( x1 x2 -- x3 ) 526 | x3 is the bit-by-bit logical and of x1 with x2. 527 | 528 | 6.1.1980 OR 529 | CORE 530 | ( x1 x2 -- x3 ) 531 | x3 is the bit-by-bit inclusive-or of x1 with x2. 532 | 533 | 534 | 6.1.1720 INVERT 535 | CORE 536 | ( x1 -- x2 ) 537 | Invert all bits of x1, giving its logical inverse x2. 538 | 539 | 6.1.2490 XOR 540 | x-or CORE 541 | ( x1 x2 -- x3 ) 542 | x3 is the bit-by-bit exclusive-or of x1 with x2. 543 | 544 | Comparisons 545 | ----------- 546 | 547 | 6.1.0480 < 548 | less-than CORE 549 | ( n1 n2 -- flag ) 550 | flag is true if and only if n1 is less than n2. 551 | 552 | 6.1.0530 = 553 | equals CORE 554 | ( x1 x2 -- flag ) 555 | flag is true if and only if x1 is bit-for-bit the same as x2. 556 | 557 | 6.1.0540 > 558 | greater-than CORE 559 | ( n1 n2 -- flag ) 560 | flag is true if and only if n1 is greater than n2. 561 | 562 | 6.1.0250 0< 563 | zero-less CORE 564 | ( n -- flag ) 565 | flag is true if and only if n is less than zero. 566 | 567 | 6.1.0270 0= 568 | zero-equals CORE 569 | ( x -- flag ) 570 | flag is true if and only if x is equal to zero. 571 | 572 | ### Ignore 573 | Unsigned comparisons U> U< 574 | 575 | Numeric Operations 576 | ------------------ 577 | 6.1.0120 + 578 | plus CORE 579 | ( n1|u1 n2|u2 -- n3|u3 ) 580 | Add n2|u2 to n1|u1, giving the sum n3|u3. 581 | 582 | 6.1.0160 - 583 | minus CORE 584 | ( n1|u1 n2|u2 -- n3|u3 ) 585 | Subtract n2|u2 from n1|u1, giving the difference n3|u3. 586 | 587 | 6.1.0090 * 588 | star CORE 589 | ( n1|u1 n2|u2 -- n3|u3 ) 590 | Multiply n1|u1 by n2|u2 giving the product n3|u3. 591 | 592 | 6.1.0230 / 593 | slash CORE 594 | ( n1 n2 -- n3 ) 595 | Divide n1 by n2, giving the single-cell quotient n3. An ambiguous condition exists if n2 is zero. If n1 and n2 differ in sign, the implementation-defined result returned will be the same as that returned by either the phrase >R S>D R> FM/MOD SWAP DROP or the phrase >R S>D R> SM/REM SWAP DROP . 596 | 597 | 6.1.0240 /MOD 598 | slash-mod CORE 599 | ( n1 n2 -- n3 n4 ) 600 | Divide n1 by n2, giving the single-cell remainder n3 and the single-cell quotient n4. An ambiguous condition exists if n2 is zero. If n1 and n2 differ in sign, the implementation-defined result returned will be the same as that returned by either the phrase >R S>D R> FM/MOD or the phrase >R S>D R> SM/REM . 601 | 602 | 6.1.0100 */ 603 | star-slash CORE 604 | ( n1 n2 n3 -- n4 ) 605 | Multiply n1 by n2 producing the intermediate double-cell result d. Divide d by n3 giving the single-cell quotient n4. An ambiguous condition exists if n3 is zero or if the quotient n4 lies outside the range of a signed number. If d and n3 differ in sign, the implementation-defined result returned will be the same as that returned by either the phrase >R M* R> FM/MOD SWAP DROP or the phrase >R M* R> SM/REM SWAP DROP . 606 | 607 | 6.1.0110 */MOD 608 | star-slash-mod CORE 609 | ( n1 n2 n3 -- n4 n5 ) 610 | Multiply n1 by n2 producing the intermediate double-cell result d. Divide d by n3 producing the single-cell remainder n4 and the single-cell quotient n5. An ambiguous condition exists if n3 is zero, or if the quotient n5 lies outside the range of a single-cell signed integer. If d and n3 differ in sign, the implementation-defined result returned will be the same as that returned by either the phrase >R M* R> FM/MOD or the phrase >R M* R> SM/REM . 611 | 612 | 6.1.1890 MOD 613 | CORE 614 | ( n1 n2 -- n3 ) 615 | Divide n1 by n2, giving the single-cell remainder n3. An ambiguous condition exists if n2 is zero. If n1 and n2 differ in sign, the implementation-defined result returned will be the same as that returned by either the phrase >R S>D R> FM/MOD DROP or the phrase >R S>D R> SM/REM DROP. 616 | 617 | 6.1.0690 ABS 618 | abs CORE 619 | ( n -- u ) 620 | u is the absolute value of n. 621 | 622 | 6.1.1805 LSHIFT 623 | l-shift CORE 624 | ( x1 u -- x2 ) 625 | Perform a logical left shift of u bit-places on x1, giving x2. Put zeroes into the least significant bits vacated by the shift. An ambiguous condition exists if u is greater than or equal to the number of bits in a cell. 626 | 627 | 6.1.2162 RSHIFT 628 | r-shift CORE 629 | ( x1 u -- x2 ) 630 | Perform a logical right shift of u bit-places on x1, giving x2. Put zeroes into the most significant bits vacated by the shift. An ambiguous condition exists if u is greater than or equal to the number of bits in a cell. 631 | 632 | 6.1.1870 MAX 633 | CORE 634 | ( n1 n2 -- n3 ) 635 | n3 is the greater of n1 and n2. 636 | 637 | 6.1.1880 MIN 638 | CORE 639 | ( n1 n2 -- n3 ) 640 | n3 is the lesser of n1 and n2. 641 | 642 | 6.1.1910 NEGATE 643 | CORE 644 | ( n1 -- n2 ) 645 | Negate n1, giving its arithmetic inverse n2. 646 | 647 | 6.1.0290 1+ 648 | one-plus CORE 649 | ( n1|u1 -- n2|u2 ) 650 | Add one (1) to n1|u1 giving the sum n2|u2. 651 | 652 | 6.1.0300 1- 653 | one-minus CORE 654 | ( n1|u1 -- n2|u2 ) 655 | Subtract one (1) from n1|u1 giving the difference n2|u2. 656 | 657 | 6.1.0320 2* 658 | two-star CORE 659 | ( x1 -- x2 ) 660 | x2 is the result of shifting x1 one bit toward the most-significant bit, filling the vacated least-significant bit with zero. 661 | 662 | 663 | 6.1.0330 2/ 664 | two-slash CORE 665 | ( x1 -- x2 ) 666 | x2 is the result of shifting x1 one bit toward the least-significant bit, leaving the most-significant bit unchanged. 667 | 668 | ### Ignore? 669 | Signed/unsigned operations? 670 | 671 | 6.1.2170 S>D 672 | s-to-d CORE 673 | ( n -- d ) 674 | Convert the number n to the double-cell number d with the same numerical value. 675 | 676 | 6.1.2214 SM/REM 677 | s-m-slash-rem CORE 678 | ( d1 n1 -- n2 n3 ) 679 | Divide d1 by n1, giving the symmetric quotient n3 and the remainder n2. Input and output stack arguments are signed. An ambiguous condition exists if n1 is zero or if the quotient lies outside the range of a single-cell signed integer. 680 | 681 | 6.1.1561 FM/MOD 682 | f-m-slash-mod CORE 683 | ( d1 n1 -- n2 n3 ) 684 | Divide d1 by n1, giving the floored quotient n3 and the remainder n2. Input and output stack arguments are signed. An ambiguous condition exists if n1 is zero or if the quotient lies outside the range of a single-cell signed integer. 685 | 686 | 6.1.1810 M* 687 | m-star CORE 688 | ( n1 n2 -- d ) 689 | d is the signed product of n1 times n2. 690 | 691 | 6.1.2360 UM* 692 | u-m-star CORE 693 | ( u1 u2 -- ud ) 694 | Multiply u1 by u2, giving the unsigned double-cell product ud. All values and arithmetic are unsigned. 695 | 696 | 6.1.2370 UM/MOD 697 | u-m-slash-mod CORE 698 | ( ud u1 -- u2 u3 ) 699 | Divide ud by u1, giving the quotient u3 and the remainder u2. All values and arithmetic are unsigned. An ambiguous condition exists if u1 is zero or if the quotient lies outside the range of a single-cell unsigned integer. 700 | 701 | 6.1.0570 >NUMBER 702 | to-number CORE 703 | ( ud1 c-addr1 u1 -- ud2 c-addr2 u2 ) 704 | ud2 is the unsigned result of converting the characters within the string specified by c-addr1 u1 into digits, using the number in BASE, and adding each into ud1 after multiplying ud1 by the number in BASE. Conversion continues left-to-right until a character that is not convertible, including any + or -, is encountered or the string is entirely converted. c-addr2 is the location of the first unconverted character or the first character past the end of the string if the string was entirely converted. u2 is the number of unconverted characters in the string. An ambiguous condition exists if ud2 overflows during the conversion. 705 | 706 | Input 707 | ----- 708 | 709 | 6.1.1750 KEY 710 | CORE 711 | ( -- char ) 712 | Receive one character char, a member of the implementation-defined character set. Keyboard events that do not correspond to such characters are discarded until a valid character is received, and those events are subsequently unavailable. 713 | 714 | All standard characters can be received. Characters received by KEY are not displayed. 715 | 716 | Any standard character returned by KEY has the numeric value specified in 3.1.2.1 Graphic characters. Programs that require the ability to receive control characters have an environmental dependency. 717 | 718 | 6.1.2450 WORD 719 | CORE 720 | ( char "ccc" -- c-addr ) 721 | Skip leading delimiters. Parse characters ccc delimited by char. An ambiguous condition exists if the length of the parsed string is greater than the implementation-defined length of a counted string. 722 | 723 | c-addr is the address of a transient region containing the parsed word as a counted string. If the parse area was empty or contained no characters other than the delimiter, the resulting string has a zero length. A space, not included in the length, follows the string. A program may replace characters within the string. 724 | 725 | Note: The requirement to follow the string with a space is obsolescent and is included as a concession to existing programs that use CONVERT. A program shall not depend on the existence of the space. 726 | 727 | 6.1.0895 CHAR 728 | char CORE 729 | ( "name" -- char ) 730 | Skip leading space delimiters. Parse name delimited by a space. Put the value of its first character onto the stack. 731 | 732 | 6.1.2520 [CHAR] 733 | bracket-char CORE 734 | Interpretation: Interpretation semantics for this word are undefined. 735 | Compilation: ( "name" -- ) 736 | Skip leading space delimiters. Parse name delimited by a space. Append the run-time semantics given below to the current definition. 737 | 738 | Run-time: ( -- char ) 739 | Place char, the value of the first character of name, on the stack. 740 | 741 | 6.1.0695 ACCEPT 742 | CORE 743 | ( c-addr +n1 -- +n2 ) 744 | Receive a string of at most +n1 characters. An ambiguous condition exists if +n1 is zero or greater than 32,767. Display graphic characters as they are received. A program that depends on the presence or absence of non-graphic characters in the string has an environmental dependency. The editing functions, if any, that the system performs in order to construct the string are implementation-defined. 745 | 746 | Input terminates when an implementation-defined line terminator is received. When input terminates, nothing is appended to the string, and the display is maintained in an implementation-defined way. 747 | 748 | +n2 is the length of the string stored at c-addr. 749 | 750 | 6.1.0190 ." 751 | dot-quote CORE 752 | Interpretation: Interpretation semantics for this word are undefined. 753 | Compilation: ( "ccc" -- ) 754 | Parse ccc delimited by " (double-quote). Append the run-time semantics given below to the current definition. 755 | 756 | Run-time: ( -- ) 757 | 758 | Display ccc. 759 | 760 | 6.1.2165 S" 761 | s-quote CORE 762 | Interpretation: Interpretation semantics for this word are undefined. 763 | Compilation: ( "ccc" -- ) 764 | Parse ccc delimited by " (double-quote). Append the run-time semantics given below to the current definition. 765 | 766 | Run-time: ( -- c-addr u ) 767 | Return c-addr and u describing a string consisting of the characters ccc. A program shall not alter the returned string. 768 | 769 | 6.1.0080 ( 770 | paren CORE 771 | Compilation: Perform the execution semantics given below. 772 | Execution: ( "ccc" -- ) 773 | Parse ccc delimited by ) (right parenthesis). ( is an immediate word. 774 | 775 | The number of characters in ccc may be zero to the number of characters in the parse area. 776 | 777 | 6.1.0897 CHAR+ 778 | char-plus CORE 779 | ( c-addr1 -- c-addr2 ) 780 | Add the size in address units of a character to c-addr1, giving c-addr2. 781 | 782 | 6.1.0898 CHARS 783 | chars CORE 784 | ( n1 -- n2 ) 785 | n2 is the size in address units of n1 characters. 786 | 787 | 6.1.0980 COUNT 788 | CORE 789 | ( c-addr1 -- c-addr2 u ) 790 | Return the character string specification for the counted string stored at c-addr1. c-addr2 is the address of the first character after c-addr1. u is the contents of the character at c-addr1, which is the length in characters of the string at c-addr2. 791 | 792 | Output 793 | ------ 794 | 795 | 6.1.0180 . 796 | dot CORE 797 | ( n -- ) 798 | Display n in free field format. 799 | 800 | 6.1.1320 EMIT 801 | CORE 802 | ( x -- ) 803 | If x is a graphic character in the implementation-defined character set, display x. The effect of EMIT for all other values of x is implementation-defined. 804 | 805 | When passed a character whose character-defining bits have a value between hex 20 and 7E inclusive, the corresponding standard character, specified by 3.1.2.1 Graphic characters, is displayed. Because different output devices can respond differently to control characters, programs that use control characters to perform specific functions have an environmental dependency. Each EMIT deals with only one character. 806 | 807 | 6.1.0770 BL 808 | b-l CORE 809 | ( -- char ) 810 | char is the character value for a space. 811 | 812 | 6.1.0990 CR 813 | c-r CORE 814 | ( -- ) 815 | Cause subsequent output to appear at the beginning of the next line. 816 | 817 | 6.1.2220 SPACE 818 | CORE 819 | ( -- ) 820 | Display one space. 821 | 822 | 6.1.2230 SPACES 823 | CORE 824 | ( n -- ) 825 | If n is greater than zero, display n spaces. 826 | 827 | 6.1.2320 U. 828 | u-dot CORE 829 | ( u -- ) 830 | Display u in free field format. 831 | 832 | 6.1.2310 TYPE 833 | CORE 834 | ( c-addr u -- ) 835 | If u is greater than zero, display the character string specified by c-addr and u. 836 | 837 | When passed a character in a character string whose character-defining bits have a value between hex 20 and 7E inclusive, the corresponding standard character, specified by 3.1.2.1 graphic characters, is displayed. Because different output devices can respond differently to control characters, programs that use control characters to perform specific functions have an environmental dependency. 838 | 839 | 840 | Number formatting 841 | ----------------- 842 | 843 | 6.1.0750 BASE 844 | CORE 845 | ( -- a-addr ) 846 | a-addr is the address of a cell containing the current number-conversion radix {{2...36}}. 847 | 848 | 6.1.1170 DECIMAL 849 | CORE 850 | ( -- ) 851 | Set the numeric conversion radix to ten (decimal). 852 | 853 | 6.2.1660 HEX 854 | CORE EXT 855 | ( -- ) 856 | Set contents of BASE to sixteen. 857 | 858 | 6.1.0030 # 859 | number-sign CORE 860 | ( ud1 -- ud2 ) 861 | Divide ud1 by the number in BASE giving the quotient ud2 and the remainder n. (n is the least-significant digit of ud1.) Convert n to external form and add the resulting character to the beginning of the pictured numeric output string. An ambiguous condition exists if # executes outside of a <# #> delimited number conversion. 862 | 863 | 6.1.0040 #> 864 | number-sign-greater CORE 865 | ( xd -- c-addr u ) 866 | Drop xd. Make the pictured numeric output string available as a character string. c-addr and u specify the resulting character string. A program may replace characters within the string. 867 | 868 | 6.1.0050 #S 869 | number-sign-s CORE 870 | ( ud1 -- ud2 ) 871 | Convert one digit of ud1 according to the rule for #. Continue conversion until the quotient is zero. ud2 is zero. An ambiguous condition exists if #S executes outside of a <# #> delimited number conversion. 872 | 873 | 6.1.0490 <# 874 | less-number-sign CORE 875 | ( -- ) 876 | Initialize the pictured numeric output conversion process. 877 | 878 | 6.1.1670 HOLD 879 | CORE 880 | ( char -- ) 881 | Add char to the beginning of the pictured numeric output string. An ambiguous condition exists if HOLD executes outside of a <# #> delimited number conversion. 882 | 883 | 6.1.2210 SIGN 884 | CORE 885 | ( n -- ) 886 | If n is negative, add a minus sign to the beginning of the pictured numeric output string. An ambiguous condition exists if SIGN executes outside of a <# #> delimited number conversion. 887 | 888 | 889 | Core Extension Words 890 | -------------------- 891 | 6.2 Core extension words 892 | 893 | See: A.6.2 Core extension words 894 | 895 | 6.2.0060 #TIB 896 | number-t-i-b CORE EXT 897 | ( -- a-addr ) 898 | a-addr is the address of a cell containing the number of characters in the terminal input buffer. 899 | 900 | Note: This word is obsolescent and is included as a concession to existing implementations. 901 | 902 | See: A.6.2.0060 #TIB 903 | 904 | 6.2.0200 .( 905 | dot-paren CORE EXT 906 | Compilation: Perform the execution semantics given below. 907 | Execution: ( "ccc" -- ) 908 | Parse and display ccc delimited by ) (right parenthesis). .( is an immediate word. 909 | 910 | See: 3.4.1 Parsing, 6.1.0190 ." , A.6.2.0200 .( 911 | 912 | 6.2.0210 .R 913 | dot-r CORE EXT 914 | ( n1 n2 -- ) 915 | Display n1 right aligned in a field n2 characters wide. If the number of characters required to display n1 is greater than n2, all digits are displayed with no leading spaces in a field as wide as necessary. 916 | 917 | See: A.6.2.0210 .R 918 | 919 | 6.2.0260 0<> 920 | zero-not-equals CORE EXT 921 | ( x -- flag ) 922 | flag is true if and only if x is not equal to zero. 923 | 924 | 6.2.0280 0> 925 | zero-greater CORE EXT 926 | ( n -- flag ) 927 | flag is true if and only if n is greater than zero. 928 | 929 | 6.2.0340 2>R 930 | two-to-r CORE EXT 931 | Interpretation: Interpretation semantics for this word are undefined. 932 | Execution: ( x1 x2 -- ) ( R: -- x1 x2 ) 933 | Transfer cell pair x1 x2 to the return stack. Semantically equivalent to SWAP >R >R . 934 | 935 | See: 3.2.3.3 Return stack, 6.1.0580 >R , 6.1.2060 R> , 6.1.2070 R@ , 6.2.0410 2R> , 6.2.0415 2R@ , A.6.2.0340 2>R 936 | 937 | 6.2.0410 2R> 938 | two-r-from CORE EXT 939 | Interpretation: Interpretation semantics for this word are undefined. 940 | Execution: ( -- x1 x2 ) ( R: x1 x2 -- ) 941 | Transfer cell pair x1 x2 from the return stack. Semantically equivalent to R> R> SWAP . 942 | 943 | See: 3.2.3.3 Return stack, 6.1.0580 >R , 6.1.2060 R> , 6.1.2070 R@ , 6.2.0340 2>R , 6.2.0415 2R@ , A.6.2.0410 2R> 944 | 945 | 6.2.0415 2R@ 946 | two-r-fetch CORE EXT 947 | Interpretation: Interpretation semantics for this word are undefined. 948 | Execution: ( -- x1 x2 ) ( R: x1 x2 -- x1 x2 ) 949 | Copy cell pair x1 x2 from the return stack. Semantically equivalent to R> R> 2DUP >R >R SWAP . 950 | 951 | See: 3.2.3.3 Return stack, 6.1.0580 >R , 6.1.2060 R> , 6.1.2070 R@ , 6.2.0340 2>R , 6.2.0410 2R> 952 | 953 | 6.2.0455 :NONAME 954 | colon-no-name CORE EXT 955 | ( C: -- colon-sys ) ( S: -- xt ) 956 | Create an execution token xt, enter compilation state and start the current definition, producing colon-sys. Append the initiation semantics given below to the current definition. 957 | 958 | The execution semantics of xt will be determined by the words compiled into the body of the definition. This definition can be executed later by using xt EXECUTE. 959 | 960 | If the control-flow stack is implemented using the data stack, colon-sys shall be the topmost item on the data stack. 961 | 962 | Initiation: ( i*x -- i*x ) ( R: -- nest-sys ) 963 | Save implementation-dependent information nest-sys about the calling definition. The stack effects i*x represent arguments to xt. 964 | 965 | xt Execution: ( i*x -- j*x ) 966 | Execute the definition specified by xt. The stack effects i*x and j*x represent arguments to and results from xt, respectively. 967 | 968 | See: A.6.2.0455 :NONAME , 3.2.3.2 Control-flow stack. 969 | 970 | 6.2.0500 <> 971 | not-equals CORE EXT 972 | ( x1 x2 -- flag ) 973 | flag is true if and only if x1 is not bit-for-bit the same as x2. 974 | 975 | 6.2.0620 ?DO 976 | question-do CORE EXT 977 | Interpretation: Interpretation semantics for this word are undefined. 978 | Compilation: ( C: -- do-sys ) 979 | Put do-sys onto the control-flow stack. Append the run-time semantics given below to the current definition. The semantics are incomplete until resolved by a consumer of do-sys such as LOOP. 980 | 981 | Run-time: ( n1|u1 n2|u2 -- ) ( R: -- | loop-sys ) 982 | If n1|u1 is equal to n2|u2, continue execution at the location given by the consumer of do-sys. Otherwise set up loop control parameters with index n2|u2 and limit n1|u1 and continue executing immediately following ?DO. Anything already on the return stack becomes unavailable until the loop control parameters are discarded. An ambiguous condition exists if n1|u1 and n2|u2 are not both of the same type. 983 | 984 | See: 3.2.3.2 Control-flow stack, 6.1.0140 +LOOP , 6.1.1240 DO , 6.1.1680 I , 6.1.1760 LEAVE , 6.1.2380 UNLOOP , A.6.2.0620 ?DO 985 | 986 | 6.2.0700 AGAIN 987 | CORE EXT 988 | Interpretation: Interpretation semantics for this word are undefined. 989 | Compilation: ( C: dest -- ) 990 | Append the run-time semantics given below to the current definition, resolving the backward reference dest. 991 | 992 | Run-time: ( -- ) 993 | Continue execution at the location specified by dest. If no other control flow words are used, any program code after AGAIN will not be executed. 994 | 995 | See: 6.1.0760 BEGIN , A.6.2.0700 AGAIN 996 | 997 | 6.2.0855 C" 998 | c-quote CORE EXT 999 | Interpretation: Interpretation semantics for this word are undefined. 1000 | Compilation: ( "ccc" -- ) 1001 | Parse ccc delimited by " (double-quote) and append the run-time semantics given below to the current definition. 1002 | 1003 | Run-time: ( -- c-addr ) 1004 | Return c-addr, a counted string consisting of the characters ccc. A program shall not alter the returned string. 1005 | 1006 | See: 3.4.1 Parsing, 6.1.2165 S" , 11.6.1.2165 S" , A.6.2.0855 C" 1007 | 1008 | 6.2.0873 CASE 1009 | CORE EXT 1010 | Interpretation: Interpretation semantics for this word are undefined. 1011 | Compilation: ( C: -- case-sys ) 1012 | Mark the start of the CASE ... OF ... ENDOF ... ENDCASE structure. Append the run-time semantics given below to the current definition. 1013 | 1014 | Run-time: ( -- ) 1015 | Continue execution. 1016 | 1017 | See: A.6.2.0873 CASE 1018 | 1019 | 6.2.0945 COMPILE, 1020 | compile-comma CORE EXT 1021 | Interpretation: Interpretation semantics for this word are undefined. 1022 | Execution: ( xt -- ) 1023 | Append the execution semantics of the definition represented by xt to the execution semantics of the current definition. 1024 | 1025 | See: A.6.2.0945 COMPILE, 1026 | 1027 | 6.2.0970 CONVERT 1028 | CORE EXT 1029 | ( ud1 c-addr1 -- ud2 c-addr2 ) 1030 | ud2 is the result of converting the characters within the text beginning at the first character after c-addr1 into digits, using the number in BASE, and adding each digit to ud1 after multiplying ud1 by the number in BASE. Conversion continues until a character that is not convertible is encountered. c-addr2 is the location of the first unconverted character. An ambiguous condition exists if ud2 overflows. 1031 | 1032 | Note: This word is obsolescent and is included as a concession to existing implementations. Its function is superseded by 6.1.0570 >NUMBER. 1033 | 1034 | See: 3.2.1.2 Digit conversion, A.6.2.0970 CONVERT 1035 | 1036 | 6.2.1342 ENDCASE 1037 | end-case CORE EXT 1038 | Interpretation: Interpretation semantics for this word are undefined. 1039 | Compilation: ( C: case-sys -- ) 1040 | Mark the end of the CASE ... OF ... ENDOF ... ENDCASE structure. Use case-sys to resolve the entire structure. Append the run-time semantics given below to the current definition. 1041 | 1042 | Run-time: ( x -- ) 1043 | Discard the case selector x and continue execution. 1044 | 1045 | See: A.6.2.1342 ENDCASE 1046 | 1047 | 6.2.1343 ENDOF 1048 | end-of CORE EXT 1049 | Interpretation: Interpretation semantics for this word are undefined. 1050 | Compilation: ( C: case-sys1 of-sys -- case-sys2 ) 1051 | Mark the end of the OF ... ENDOF part of the CASE structure. The next location for a transfer of control resolves the reference given by of-sys. Append the run-time semantics given below to the current definition. Replace case-sys1 with case-sys2 on the control-flow stack, to be resolved by ENDCASE. 1052 | 1053 | Run-time: ( -- ) 1054 | Continue execution at the location specified by the consumer of case-sys2. 1055 | 1056 | See: A.6.2.1343 ENDOF 1057 | 1058 | 6.2.1350 ERASE 1059 | CORE EXT 1060 | ( addr u -- ) 1061 | If u is greater than zero, clear all bits in each of u consecutive address units of memory beginning at addr . 1062 | 1063 | 6.2.1390 EXPECT 1064 | CORE EXT 1065 | ( c-addr +n -- ) 1066 | Receive a string of at most +n characters. Display graphic characters as they are received. A program that depends on the presence or absence of non-graphic characters in the string has an environmental dependency. The editing functions, if any, that the system performs in order to construct the string of characters are implementation-defined. 1067 | 1068 | Input terminates when an implementation-defined line terminator is received or when the string is +n characters long. When input terminates, nothing is appended to the string and the display is maintained in an implementation-defined way. 1069 | 1070 | Store the string at c-addr and its length in SPAN. 1071 | 1072 | Note: This word is obsolescent and is included as a concession to existing implementations. Its function is superseded by 6.1.0695 ACCEPT. 1073 | 1074 | See: A.6.2.1390 EXPECT 1075 | 1076 | 6.2.1485 FALSE 1077 | CORE EXT 1078 | ( -- false ) 1079 | Return a false flag. 1080 | 1081 | See: 3.1.3.1 Flags 1082 | 1083 | 6.2.1660 HEX 1084 | CORE EXT 1085 | ( -- ) 1086 | Set contents of BASE to sixteen. 1087 | 1088 | 6.2.1850 MARKER 1089 | CORE EXT 1090 | ( "name" -- ) 1091 | Skip leading space delimiters. Parse name delimited by a space. Create a definition for name with the execution semantics defined below. 1092 | 1093 | name Execution: ( -- ) 1094 | Restore all dictionary allocation and search order pointers to the state they had just prior to the definition of name. Remove the definition of name and all subsequent definitions. Restoration of any structures still existing that could refer to deleted definitions or deallocated data space is not necessarily provided. No other contextual information such as numeric base is affected. 1095 | 1096 | See: 3.4.1 Parsing, 15.6.2.1580 FORGET , A.6.2.1850 MARKER 1097 | 1098 | 6.2.1930 NIP 1099 | CORE EXT 1100 | ( x1 x2 -- x2 ) 1101 | Drop the first item below the top of stack. 1102 | 1103 | 6.2.1950 OF 1104 | CORE EXT 1105 | Interpretation: Interpretation semantics for this word are undefined. 1106 | Compilation: ( C: -- of-sys ) 1107 | Put of-sys onto the control flow stack. Append the run-time semantics given below to the current definition. The semantics are incomplete until resolved by a consumer of of-sys such as ENDOF. 1108 | 1109 | Run-time: ( x1 x2 -- | x1 ) 1110 | If the two values on the stack are not equal, discard the top value and continue execution at the location specified by the consumer of of-sys, e.g., following the next ENDOF. Otherwise, discard both values and continue execution in line. 1111 | 1112 | See: 6.2.0873 CASE , 6.2.1342 ENDCASE , A.6.2.1950 OF 1113 | 1114 | 6.2.2000 PAD 1115 | CORE EXT 1116 | ( -- c-addr ) 1117 | c-addr is the address of a transient region that can be used to hold data for intermediate processing. 1118 | 1119 | See: 3.3.3.6 Other transient regions, A.6.2.2000 PAD 1120 | 1121 | 6.2.2008 PARSE 1122 | CORE EXT 1123 | ( char "ccc" -- c-addr u ) 1124 | Parse ccc delimited by the delimiter char. 1125 | 1126 | c-addr is the address (within the input buffer) and u is the length of the parsed string. If the parse area was empty, the resulting string has a zero length. 1127 | 1128 | See: 3.4.1 Parsing, A.6.2.2008 PARSE 1129 | 1130 | 6.2.2030 PICK 1131 | CORE EXT 1132 | ( xu ... x1 x0 u -- xu ... x1 x0 xu ) 1133 | Remove u. Copy the xu to the top of the stack. An ambiguous condition exists if there are less than u+2 items on the stack before PICK is executed. 1134 | 1135 | See: A.6.2.2030 PICK 1136 | 1137 | 6.2.2040 QUERY 1138 | CORE EXT 1139 | ( -- ) 1140 | Make the user input device the input source. Receive input into the terminal input buffer, replacing any previous contents. Make the result, whose address is returned by TIB, the input buffer. Set >IN to zero. 1141 | 1142 | Note: This word is obsolescent and is included as a concession to existing implementations. 1143 | 1144 | See: A.6.2.2040 QUERY , RFI 0006. 1145 | 1146 | 6.2.2125 REFILL 1147 | CORE EXT 1148 | ( -- flag ) 1149 | Attempt to fill the input buffer from the input source, returning a true flag if successful. 1150 | 1151 | When the input source is the user input device, attempt to receive input into the terminal input buffer. If successful, make the result the input buffer, set >IN to zero, and return true. Receipt of a line containing no characters is considered successful. If there is no input available from the current input source, return false. 1152 | 1153 | When the input source is a string from EVALUATE, return false and perform no other action. 1154 | 1155 | See: 7.6.2.2125 REFILL , 11.6.2.2125 REFILL , A.6.2.2125 REFILL 1156 | 1157 | 6.2.2150 ROLL 1158 | CORE EXT 1159 | ( xu xu-1 ... x0 u -- xu-1 ... x0 xu ) 1160 | Remove u. Rotate u+1 items on the top of the stack. An ambiguous condition exists if there are less than u+2 items on the stack before ROLL is executed. 1161 | 1162 | See: A.6.2.2150 ROLL 1163 | 1164 | 6.2.2148 RESTORE-INPUT 1165 | CORE EXT 1166 | ( xn ... x1 n -- flag ) 1167 | Attempt to restore the input source specification to the state described by x1 through xn. flag is true if the input source specification cannot be so restored. 1168 | 1169 | An ambiguous condition exists if the input source represented by the arguments is not the same as the current input source. 1170 | 1171 | 6.2.2182 SAVE-INPUT 1172 | CORE EXT 1173 | ( -- xn ... x1 n ) 1174 | x1 through xn describe the current state of the input source specification for later use by RESTORE-INPUT. 1175 | 1176 | 6.2.2218 SOURCE-ID 1177 | source-i-d CORE EXT 1178 | ( -- 0 | -1 ) 1179 | Identifies the input source as follows: 1180 | 1181 | SOURCE-ID Input source 1182 | -1 String (via EVALUATE) 1183 | 0 User input device 1184 | See: 11.6.1.2218 SOURCE-ID 1185 | 1186 | 6.2.2240 SPAN 1187 | CORE EXT 1188 | ( -- a-addr ) 1189 | a-addr is the address of a cell containing the count of characters stored by the last execution of EXPECT. 1190 | 1191 | Note: This word is obsolescent and is included as a concession to existing implementations. 1192 | 1193 | 6.2.2290 TIB 1194 | t-i-b CORE EXT 1195 | ( -- c-addr ) 1196 | c-addr is the address of the terminal input buffer. 1197 | 1198 | Note: This word is obsolescent and is included as a concession to existing implementations. 1199 | 1200 | See: A.6.2.2290 TIB , RFI 0006. 1201 | 1202 | 6.2.2295 TO 1203 | CORE EXT 1204 | Interpretation: ( x "name" -- ) 1205 | Skip leading spaces and parse name delimited by a space. Store x in name. An ambiguous condition exists if name was not defined by VALUE. 1206 | 1207 | Compilation: ( "name" -- ) 1208 | Skip leading spaces and parse name delimited by a space. Append the run-time semantics given below to the current definition. An ambiguous condition exists if name was not defined by VALUE. 1209 | 1210 | Run-time: ( x -- ) 1211 | Store x in name. 1212 | 1213 | Note: An ambiguous condition exists if either POSTPONE or [COMPILE] is applied to TO. 1214 | 1215 | See: 13.6.1.2295 TO , A.6.2.2295 TO 1216 | 1217 | 6.2.2298 TRUE 1218 | CORE EXT 1219 | ( -- true ) 1220 | Return a true flag, a single-cell value with all bits set. 1221 | 1222 | See: 3.1.3.1 Flags, A.6.2.2298 TRUE 1223 | 1224 | 6.2.2300 TUCK 1225 | CORE EXT 1226 | ( x1 x2 -- x2 x1 x2 ) 1227 | Copy the first (top) stack item below the second stack item. 1228 | 1229 | 6.2.2330 U.R 1230 | u-dot-r CORE EXT 1231 | ( u n -- ) 1232 | Display u right aligned in a field n characters wide. If the number of characters required to display u is greater than n, all digits are displayed with no leading spaces in a field as wide as necessary. 1233 | 1234 | 6.2.2350 U> 1235 | u-greater-than CORE EXT 1236 | ( u1 u2 -- flag ) 1237 | flag is true if and only if u1 is greater than u2. 1238 | 1239 | See: 6.1.0540 > 1240 | 1241 | 6.2.2395 UNUSED 1242 | CORE EXT 1243 | ( -- u ) 1244 | u is the amount of space remaining in the region addressed by HERE , in address units. 1245 | 1246 | 6.2.2405 VALUE 1247 | CORE EXT 1248 | ( x "name" -- ) 1249 | Skip leading space delimiters. Parse name delimited by a space. Create a definition for name with the execution semantics defined below, with an initial value equal to x. 1250 | 1251 | name is referred to as a value. 1252 | 1253 | name Execution: ( -- x ) 1254 | Place x on the stack. The value of x is that given when name was created, until the phrase x TO name is executed, causing a new value of x to be associated with name. 1255 | 1256 | See: 3.4.1 Parsing, A.6.2.2405 VALUE , 6.2.2295 TO 1257 | 1258 | 6.2.2440 WITHIN 1259 | CORE EXT 1260 | ( n1|u1 n2|u2 n3|u3 -- flag ) 1261 | Perform a comparison of a test value n1|u1 with a lower limit n2|u2 and an upper limit n3|u3, returning true if either (n2|u2 < n3|u3 and (n2|u2 <= n1|u1 and n1|u1 < n3|u3)) or (n2|u2 > n3|u3 and (n2|u2 <= n1|u1 or n1|u1 < n3|u3)) is true, returning false otherwise. An ambiguous condition exists if n1|u1, n2|u2, and n3|u3 are not all the same type. 1262 | 1263 | See: A.6.2.2440 WITHIN 1264 | 1265 | 6.2.2530 [COMPILE] 1266 | bracket-compile CORE EXT 1267 | Intrepretation: Interpretation semantics for this word are undefined. 1268 | Compilation: ( "name" -- ) 1269 | Skip leading space delimiters. Parse name delimited by a space. Find name. If name has other than default compilation semantics, append them to the current definition; otherwise append the execution semantics of name. An ambiguous condition exists if name is not found. 1270 | 1271 | See: 3.4.1 Parsing, 6.1.2033 POSTPONE , A.6.2.2530 [COMPILE] 1272 | 1273 | 6.2.2535 \ 1274 | backslash CORE EXT 1275 | Compilation: Perform the execution semantics given below. 1276 | Execution: ( "ccc"-- ) 1277 | Parse and discard the remainder of the parse area. \ is an immediate word. 1278 | 1279 | See: 7.6.2.2535 \ , A.6.2.2535 \ 1280 | -------------------------------------------------------------------------------- /browser/main.js: -------------------------------------------------------------------------------- 1 | var Forth = require("../kernel/forth.js"); 2 | 3 | function Repl() { 4 | var forth = Forth(); 5 | 6 | function loadForth(file) { 7 | var xmlhttp = new XMLHttpRequest(); 8 | xmlhttp.onreadystatechange = function() { 9 | if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { 10 | forth.run(xmlhttp.responseText, onForthOutput); 11 | } 12 | }; 13 | xmlhttp.open("GET", file, true); 14 | xmlhttp.send(); 15 | } 16 | loadForth("forth/forth.fth"); 17 | 18 | var inputHistory = [""]; 19 | var historyCount = 0; 20 | var historySelection = 0; 21 | 22 | function useHistory(selection) { 23 | var inputNode = document.getElementById("input"); 24 | 25 | if (inputNode.value !== inputHistory[historySelection]) { 26 | historySelection = historyCount - 1; 27 | inputHistory[historyCount] = inputNode.value; 28 | } else { 29 | historySelection = Math.min(Math.max(selection, 0), inputHistory.length - 1); 30 | } 31 | 32 | inputNode.value = inputHistory[historySelection]; 33 | inputNode.selectionStart = inputNode.value.length; 34 | } 35 | 36 | function updateHistory(input) { 37 | // Remove duplicates 38 | for (var i = inputHistory.length - 1; i >= 0; i--) { 39 | if (inputHistory[i] === input) { 40 | inputHistory.splice(i, 1); 41 | historyCount--; 42 | } 43 | } 44 | inputHistory[historyCount] = input; 45 | historyCount = inputHistory.length; 46 | historySelection = inputHistory.length; 47 | inputHistory.push(""); 48 | } 49 | 50 | function createReplNode(icon, text, className) { 51 | if (!text) return; 52 | 53 | var textNode = document.createElement("textarea"); 54 | textNode.className = className; 55 | textNode.readOnly = true; 56 | textNode.cols = 80; 57 | textNode.value = icon + " " + text; 58 | 59 | var replNode = document.createElement("div"); 60 | replNode.appendChild(textNode); 61 | 62 | var outputNode = document.getElementById("output"); 63 | outputNode.appendChild(replNode); 64 | 65 | setTimeout(function() { 66 | textNode.style.height = textNode.scrollHeight + "px"; 67 | outputNode.scrollTop = outputNode.scrollHeight - outputNode.clientHeight; 68 | }, 0); 69 | } 70 | 71 | function onForthOutput(error, output) { 72 | createReplNode("\u2190", output, "forth-output"); 73 | if (error) { 74 | createReplNode("X", error, "error"); 75 | } 76 | showStack(); 77 | } 78 | 79 | function runforth() { 80 | var inputNode = document.getElementById("input"); 81 | var input = inputNode.value.trim(); 82 | if (input) { 83 | updateHistory(input); 84 | createReplNode("\u2192", input, "user-output"); 85 | inputNode.value = ""; 86 | forth.run(input, onForthOutput); 87 | } 88 | } 89 | 90 | function showStack() { 91 | var stack = forth.stack; 92 | var stackNode = document.getElementById("stack"); 93 | // Clear stack 94 | while (stackNode.firstChild) stackNode.removeChild(stackNode.firstChild); 95 | 96 | for (var i = 1; i <= stack.length(); i++) { 97 | var element = document.createElement("span"); 98 | element.className = "stack-element"; 99 | element.textContent = String(stack.peek(i)); 100 | stackNode.appendChild(element); 101 | } 102 | } 103 | 104 | return { 105 | interpret: function(event) { 106 | if (event.keyCode == 13 && !event.shiftKey) 107 | runforth(); 108 | else if (event.keyCode == 80 && event.ctrlKey) 109 | useHistory(historySelection - 1); 110 | else if (event.keyCode == 78 && event.ctrlKey) 111 | useHistory(historySelection + 1); 112 | } 113 | }; 114 | } 115 | 116 | global.repl = Repl(); -------------------------------------------------------------------------------- /css/forth.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 62.5%; 3 | } 4 | 5 | body { 6 | font-size: 1.6rem; 7 | } 8 | 9 | * { 10 | -webkit-box-sizing: border-box; 11 | -moz-box-sizing: border-box; 12 | box-sizing: border-box; 13 | font-family: consolas; 14 | } 15 | 16 | #main { 17 | /*display: inline;*/ 18 | max-width: 800px; 19 | margin: 0 auto; 20 | position: relative; 21 | } 22 | #repl { 23 | position: absolute; 24 | left: 0; 25 | max-width: 600px; 26 | height: 100vh; 27 | max-height: 100vh; 28 | } 29 | .icon { 30 | display: inline; 31 | width: 100px; 32 | } 33 | .user-output { 34 | display: inline; 35 | border: none; 36 | resize: none; 37 | background-color: #ccffff; 38 | } 39 | .forth-output { 40 | border: none; 41 | resize: none; 42 | background-color: #ccffcc; 43 | } 44 | .error { 45 | /*display: block;*/ 46 | border: none; 47 | resize: none; 48 | color: red; 49 | background-color: #ffeeee; 50 | } 51 | #output { 52 | max-height: 70vh; 53 | overflow-y: scroll; 54 | } 55 | #stack { 56 | position: absolute; 57 | left: 600px; 58 | width: 200px; 59 | max-height: 70vh; 60 | overflow-y: scroll; 61 | } 62 | .stack-element { 63 | display: block; 64 | } -------------------------------------------------------------------------------- /forth/forth.fth: -------------------------------------------------------------------------------- 1 | : \ #10 parse 2drop ; immediate \ Single line comments 2 | 3 | \ Built in numeric radices 4 | : binary #2 base ! ; 5 | : octal #8 base ! ; 6 | : decimal #10 base ! ; 7 | : hex #16 base ! ; 8 | 9 | \ Control structures 10 | : if ['] jumpIfFalse compile, here 0 , ; immediate 11 | : then dup here swap - swap ! ; immediate 12 | : begin here ; immediate 13 | : again ['] jump compile, here - , ; immediate 14 | : until ['] jumpIfFalse compile, here - , ; immediate 15 | : ahead ['] jump compile, here 0 , ; immediate 16 | : cs-pick pick ; 17 | : cs-roll roll ; 18 | 19 | \ Compilation: ( "name" -- ) 20 | \ Parse name delimited by a space. Find name. 21 | \ Append the compilation semantics of name to the current definition. 22 | : postpone #32 word find dup 0 = abort" Word not found" 23 | 0 > if 24 | compile, 25 | [ ahead 1 cs-roll then ] \ inline else - it's not defined yet 26 | ['] lit compile, , \ executionToken literal 27 | ['] compile, compile, \ compile the executionToken 28 | then 29 | ; immediate 30 | 31 | : else postpone ahead 1 cs-roll postpone then ; immediate 32 | : while postpone if 1 cs-roll ; immediate 33 | : repeat postpone again postpone then ; immediate 34 | 35 | : case 0 ; immediate \ init count of ofs 36 | 37 | : of 38 | 1 + \ increment count ofs 39 | >r \ move off the stack to check the case 40 | postpone over postpone = \ copy and test case value 41 | postpone if \ add orig to control flow stack 42 | postpone drop \ discards case value if = 43 | r> \ we can bring count back now 44 | ; immediate 45 | 46 | : endof \ orig1 #of -- orig2 #of 47 | >r \ move off the stack 48 | postpone else 49 | r> \ we can bring count back now 50 | ; immediate 51 | 52 | : endcase \ orig1..orign #of -- 53 | postpone drop \ discard case value 54 | 0 ?do 55 | postpone then 56 | loop 57 | ; immediate 58 | 59 | 60 | \ ( ... ) Comments 61 | : ( begin key ')' = until ; immediate 62 | 63 | 64 | \ Spaces 65 | : bl ( -- char ) #32 ; 66 | : space ( -- ) bl emit ; 67 | : spaces ( n -- ) dup 0 > if 0 do bl emit loop else drop then ; 68 | 69 | \ Literals 70 | \ Compilation: Append the run-time semantics given below to the current definition. 71 | \ Run-time: ( -- x ) Place x on the stack. 72 | : literal ['] lit compile, , ; immediate 73 | : sliteral ( c-addr1 u -- ) swap postpone literal postpone literal ; immediate 74 | : [char] char postpone literal ; immediate 75 | 76 | \ Strings 77 | : .( ( display "ccc" -- ) ')' parse type ; immediate 78 | : s" ( "ccc" -- ) ( -- c-addr u ) '"' parse postpone sliteral ; immediate 79 | : ." ( "ccc" -- ) ( display ) '"' parse postpone sliteral postpone type ; immediate 80 | 81 | \ Addresses 82 | : cell ( -- n ) 1 ; 83 | : cell+ ( addr1 -- add2 ) 1 + ; 84 | : cells ( n1 -- n2) ; 85 | : char+ ( addr1 -- add2) 1 + ; 86 | : chars ( n1 -- n2 ) ; 87 | : c, ( char -- ) , ; 88 | : c@ ( addr -- char ) @ ; 89 | : c! ( char addr -- ) ! ; 90 | : count ( c-addr1 -- caddr2 u ) dup 1 + swap c@ ; 91 | : 2@ ( addr - x1 x2 ) dup cell+ @ swap @ ; 92 | : 2! ( x1 x2 addr -- ) swap over ! cell+ ! ; 93 | : align ( -- ) ; 94 | : aligned ( addr -- addr ) ; 95 | : fill ( addr u char -- ) -rot dup 0 > if 0 do 2dup i + ! loop 2drop else drop drop drop then ; 96 | : erase ( addr u -- ) 0 fill ; 97 | : move ( addr1 addr2 u -- ) 98 | dup 0 > if 99 | -rot 2dup > if \ Choose direction to avoid overwriting source data before it has been copied 100 | rot 0 do over i + @ over i + ! loop 101 | else 102 | rot 0 swap 1 - do over i + @ over i + ! -1 +loop 103 | then 104 | 2drop 105 | else 106 | drop drop drop 107 | then 108 | ; 109 | : c" 110 | '"' parse \ parse string 111 | postpone ahead \ jump over counted string 112 | here 2>r \ save counted string pointer 113 | dup here ! \ write count 114 | here swap move \ write string 115 | 2r> swap 116 | postpone then 117 | postpone literal 118 | ; immediate 119 | 120 | \ Stack operations 121 | : nip ( x1 x2 -- x2 ) swap drop ; 122 | : tuck ( x1 x2 -- x2 x1 x2 ) dup -rot ; 123 | 124 | \ Numeric operations 125 | : 0= ( x -- boolean ) 0 = ; 126 | : 0<> ( x -- boolean ) 0 <> ; 127 | : 0< ( x -- boolean ) 0 < ; 128 | : 0> ( x -- boolean ) 0 > ; 129 | : 0<= ( x -- boolean ) 0 <= ; 130 | : 0>= ( x -- boolean ) 0 >= ; 131 | : 1+ ( u1 -- u2 ) 1 + ; 132 | : 1- ( u1 -- u2 ) 1 - ; 133 | : holds ( addr u -- ) 134 | begin 135 | dup 136 | while 137 | 1- 2dup + c@ hold 138 | repeat 139 | 2drop 140 | ; 141 | : /mod ( n1 n2 -- n3 n4 ) 2dup mod -rot / ; 142 | : unsigned ( n -- u ) 0 rshift ; 143 | : u< ( n1 n2 -- boolean ) unsigned swap unsigned > ; 144 | : u> ( n1 n2 -- boolean ) unsigned swap unsigned < ; 145 | : u. ( u -- ) unsigned . ; 146 | : u.r ( u w -- ) swap unsigned swap .r ; 147 | 148 | : environment? 2drop false ; \ Dummy implementation 149 | : unused $ffffff here - ; 150 | : buffer: ( u "" – ; – addr ) \ Create a buffer of u address units whose address is returned at run time. 151 | create allot 152 | ; 153 | 154 | \ Constants and variables 155 | : constant ( x "name" -- ) create , does> @ ; 156 | : variable ( "name" -- ) create cell allot ; 157 | : value ( "name" -- ) create , does> @ ; 158 | : to ( x1 "name" -- ) 159 | ' >body 160 | state @ if 161 | postpone literal postpone ! 162 | else 163 | ! 164 | then 165 | ; immediate 166 | 167 | \ Defer, defer!, defer@, is, action-of 168 | : defer ( "name" -- ) 169 | create ['] abort , 170 | does> ( ... -- ... ) 171 | @ execute 172 | ; 173 | 174 | : defer! ( xt2 xt1 -- ) 175 | >body ! 176 | ; 177 | 178 | : defer@ ( xt1 -- xt2 ) 179 | >body @ 180 | ; 181 | 182 | : is 183 | state @ if 184 | postpone ['] postpone defer! 185 | else 186 | ' defer! 187 | then 188 | ; immediate 189 | 190 | : action-of 191 | state @ if 192 | postpone ['] postpone defer@ 193 | else 194 | ' defer@ 195 | then 196 | ; immediate 197 | 198 | \ String operations 199 | : -trailing ( c-addr u1 -- c-addr u2 ) 200 | begin 201 | 2dup + 1- @ bl = over 0> and 202 | while 203 | 1- 204 | repeat 205 | ; 206 | 207 | : /string ( c-addr1 u1 n -- c-addr2 u2 ) 208 | dup -rot - -rot + swap 209 | ; 210 | 211 | : blank ( c-addr u -- ) 212 | dup 0 > if 213 | 0 do 214 | bl over i + ! 215 | loop 216 | drop 217 | else 218 | 2drop 219 | then 220 | ; 221 | 222 | : cmove ( c-addr1 c-addr2 u -- ) 223 | dup 0 > if 224 | 0 do 225 | over i + @ over i + ! 226 | loop 227 | else 228 | drop 229 | then 230 | 2drop 231 | ; 232 | 233 | : cmove> ( c-addr1 c-addr2 u -- ) 234 | dup 0 > if 235 | 0 swap 1 - do 236 | over i + @ over i + ! -1 237 | +loop 238 | else 239 | drop 240 | then 241 | 2drop 242 | ; 243 | 244 | : compare ( c-addr1 u1 c-addr2 u2 -- n ) 245 | rot 2swap 2over min 246 | dup 0= if 247 | drop 248 | else 249 | 0 do 2dup i + @ swap i + @ 2dup = if 250 | 2drop 251 | else 252 | < if 1 else -1 then 253 | -rot 2drop -rot 2drop unloop exit 254 | then 255 | loop 256 | then 257 | 2drop 2dup = if 258 | 2drop 0 259 | else 260 | < if 1 else -1 then 261 | then 262 | ; 263 | 264 | : search ( c-addr1 u1 c-addr2 u2 -- c-addr3 u3 boolean ) 265 | 2over 266 | begin ( c-addr1 u1 c-addr2 u2 c-addr3 u3 ) 267 | 2over 2over drop over compare 0= if \ found string 268 | 2swap 2drop 2swap 2drop true exit 269 | else dup 4 pick >= if \ still enough string left 270 | 1 /string 271 | else 272 | 2drop 2drop false exit 273 | then then 274 | again 275 | ; 276 | 277 | \ Create the pad 278 | create pad 1000 cells allot 279 | 280 | 281 | \ [IF] [THEN] [ELSE] compile directives 282 | : [ELSE] ( -- ) 283 | 1 begin 284 | begin bl word count dup while \ level adr len 285 | 2dup s" [IF]" compare 0= if \ level adr len 286 | 2drop 1 + \ level' 287 | else \ level adr len 288 | 2dup s" [ELSE]" compare 0= if \ level adr len 289 | 2drop 1- dup if 1+ then \ level' 290 | else \ level adr len 291 | s" [THEN]" compare 0= if \ level 292 | 1- \ level' 293 | then 294 | then 295 | then ?dup 0= if exit then \ level' 296 | repeat 2drop \ level 297 | refill 0= until 298 | drop 299 | ; immediate 300 | 301 | : [IF] ( boolean -- ) 302 | 0= if postpone [ELSE] then 303 | ; immediate 304 | 305 | : [THEN] ( -- ) ; immediate 306 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | js forth fun! 5 | 6 | 7 | 8 | 9 |
10 |

js forth fun!

11 | Learn forth here 12 |
13 | 14 |
15 |
16 |
17 |
18 | 19 |
20 | Ctrl+p and Ctrl+n for repl history 21 |
22 | 23 |
24 |
25 | 26 | Fork me on GitHub 27 | 28 | 29 | -------------------------------------------------------------------------------- /kernel/boolean-operations.js: -------------------------------------------------------------------------------- 1 | function ComparisonOperations(f) { 2 | f.defjs("true", function _true() { 3 | f.stack.push(-1); 4 | }); 5 | 6 | f.defjs("false", function _false() { 7 | f.stack.push(0); 8 | }); 9 | 10 | f.defjs("and", function and() { 11 | var first = f.stack.pop(); 12 | f.stack.push(f.stack.pop() & first); 13 | }); 14 | 15 | f.defjs("or", function or() { 16 | var first = f.stack.pop(); 17 | f.stack.push(f.stack.pop() | first); 18 | }); 19 | 20 | f.defjs("xor", function xor() { 21 | var first = f.stack.pop(); 22 | f.stack.push(f.stack.pop() ^ first); 23 | }); 24 | 25 | f.defjs("invert", function invert() { 26 | f.stack.push(~f.stack.pop()); 27 | }); 28 | 29 | f.defjs("=", function equal() { 30 | var first = f.stack.pop(); 31 | f.stack.push((f.stack.pop() == first) ? -1 : 0); 32 | }); 33 | 34 | f.defjs("<>", function notEqual() { 35 | var first = f.stack.pop(); 36 | f.stack.push((f.stack.pop() != first) ? -1 : 0); 37 | }); 38 | 39 | f.defjs("<", function lessThan() { 40 | var first = f.stack.pop(); 41 | f.stack.push((f.stack.pop() < first) ? -1 : 0); 42 | }); 43 | 44 | f.defjs(">", function greaterThan() { 45 | var first = f.stack.pop(); 46 | f.stack.push((f.stack.pop() > first) ? -1 : 0); 47 | }); 48 | 49 | f.defjs("<=", function lessThanEqual() { 50 | var first = f.stack.pop(); 51 | f.stack.push((f.stack.pop() <= first) ? -1 : 0); 52 | }); 53 | 54 | f.defjs(">=", function greaterThanEqual() { 55 | var first = f.stack.pop(); 56 | f.stack.push((f.stack.pop() >= first) ? -1 : 0); 57 | }); 58 | 59 | f.defjs("within", function within() { 60 | var upperLimit = f.stack.pop(); 61 | var lowerLimit = f.stack.pop(); 62 | var value = f.stack.pop(); 63 | var result = (lowerLimit < upperLimit && lowerLimit <= value && value < upperLimit || 64 | lowerLimit > upperLimit && (lowerLimit <= value || value < upperLimit)); 65 | f.stack.push(result ? -1 : 0); 66 | }); 67 | 68 | return f; 69 | } 70 | 71 | module.exports = ComparisonOperations; -------------------------------------------------------------------------------- /kernel/control-structures.js: -------------------------------------------------------------------------------- 1 | function ControlStructures(f) { 2 | // if, else, then 3 | f.defjs("jump", function jump() { 4 | f.instructionPointer += f.dataSpace[f.instructionPointer]; 5 | }); 6 | 7 | f.defjs("jumpIfFalse", function jumpIfFalse() { 8 | if (!f.stack.pop()) { 9 | f.instructionPointer += f.dataSpace[f.instructionPointer]; 10 | } else { 11 | f.instructionPointer++; // Skip the offset 12 | } 13 | }); 14 | 15 | 16 | // do, loop, +loop, unloop, leave, i, j 17 | function _do() { 18 | f.returnStack.push(f.dataSpace[f.instructionPointer++]); 19 | var top = f.stack.pop(); 20 | f.returnStack.push(f.stack.pop()); 21 | f.returnStack.push(top); 22 | } 23 | 24 | f.defjs("do", function compileDo() { 25 | f.dataSpace.push(_do); 26 | f.dataSpace.push(0); // Dummy endLoop 27 | f.stack.push(f.dataSpace.length - 1); 28 | }, true); // Immediate 29 | 30 | function questionDo() { 31 | if (f.stack.peek(1) !== f.stack.peek(2)) { 32 | _do(); 33 | } else { 34 | f.stack.pop(); 35 | f.stack.pop(); 36 | f.instructionPointer = f.dataSpace[f.instructionPointer]; 37 | } 38 | } 39 | 40 | f.defjs("?do", function compileQuestionDo() { 41 | f.dataSpace.push(questionDo); 42 | f.dataSpace.push(0); // Dummy endLoop 43 | f.stack.push(f.dataSpace.length - 1); 44 | }, true); // Immediate 45 | 46 | function plusLoop() { 47 | var step = f.stack.pop(); 48 | var index = f.returnStack.pop() | 0; 49 | var limit = f.returnStack.pop() | 0; 50 | 51 | var exitLoop; 52 | if (step > 0) { 53 | if (index > limit) { // Overflow, so do unsigned 54 | limit = limit >>> 0; 55 | index = index >>> 0; 56 | } 57 | exitLoop = index < limit && index + step >= limit; 58 | } else if (step < 0) { 59 | if (index < limit) { 60 | index = index >>> 0; 61 | limit = limit >>> 0; 62 | } 63 | exitLoop = index >= limit && index + step < limit; 64 | } else { 65 | exitLoop = false; 66 | } 67 | 68 | if (exitLoop) { 69 | f.returnStack.pop(); 70 | f.instructionPointer++; 71 | } else { 72 | f.returnStack.push(limit | 0); 73 | f.returnStack.push(index + step | 0); 74 | f.instructionPointer += f.dataSpace[f.instructionPointer]; 75 | } 76 | } 77 | 78 | var compilePlusLoop = f.defjs("+loop", function compilePlusLoop() { 79 | f.dataSpace.push(plusLoop); 80 | var doPosition = f.stack.pop(); 81 | f.dataSpace.push(doPosition - f.dataSpace.length + 1); 82 | f.dataSpace[doPosition] = f.dataSpace.length; 83 | }, true); // Immediate 84 | 85 | f.defjs("loop", function loop() { 86 | f.dataSpace.push(f._lit); 87 | f.dataSpace.push(1); 88 | compilePlusLoop(); 89 | }, true); // Immediate 90 | 91 | f.defjs("unloop", function unloop() { 92 | f.returnStack.pop(); 93 | f.returnStack.pop(); 94 | f.returnStack.pop(); 95 | }); 96 | 97 | f.defjs("leave", function leave() { 98 | f.returnStack.pop(); 99 | f.returnStack.pop(); 100 | f.instructionPointer = f.returnStack.pop(); 101 | }); 102 | 103 | f.defjs("i", function i() { 104 | f.stack.push(f.returnStack.peek()); 105 | }); 106 | 107 | f.defjs("j", function j() { 108 | f.stack.push(f.returnStack.peek(4)); 109 | }); 110 | 111 | 112 | // recurse 113 | f.defjs("recurse", function recurse() { 114 | f.dataSpace.push(f.dataSpace[f._latest() + 1]); 115 | }, true); // Immediate 116 | 117 | 118 | // does 119 | function _does() { 120 | var wordPosition = f._latest(); 121 | var doDoesPosition = f.instructionPointer; 122 | 123 | f.dataSpace[wordPosition + 1] = function doDoes() { 124 | f.stack.push(wordPosition + 2); 125 | f.returnStack.push(f.instructionPointer); 126 | f.instructionPointer = doDoesPosition; 127 | }; 128 | 129 | f.instructionPointer = f.returnStack.pop(); 130 | } 131 | 132 | f.defjs("does>", function compileDoes() { 133 | f.dataSpace.push(_does); 134 | }, true); // Immediate 135 | 136 | return f; 137 | } 138 | 139 | module.exports = ControlStructures; -------------------------------------------------------------------------------- /kernel/data.js: -------------------------------------------------------------------------------- 1 | var Stack = require("./stack.js"); 2 | 3 | function Data(f) { 4 | f.instructionPointer = 0; 5 | f.dataSpace = []; 6 | f.returnStack = new Stack("Return Stack"); 7 | f.stack = new Stack("Stack"); 8 | 9 | return f; 10 | } 11 | 12 | module.exports = Data; -------------------------------------------------------------------------------- /kernel/definitions.js: -------------------------------------------------------------------------------- 1 | function Header(link, name, immediate, hidden, executionToken) { 2 | this.link = link; 3 | this.name = name; 4 | this.immediate = immediate || false; 5 | this.hidden = hidden || false; 6 | this.executionToken = executionToken; 7 | } 8 | 9 | function Definitions(f) { 10 | 11 | // Temporary definition until latest is defined as a variable 12 | var latest = function latest() { 13 | return null; 14 | }; 15 | 16 | function defheader(name, immediate, hidden) { 17 | f.dataSpace.push(new Header(latest(), name, immediate, hidden, f.dataSpace.length + 1)); 18 | latest(f.dataSpace.length - 1); 19 | } 20 | 21 | f.defjs = function defjs(name, fn, immediate, displayName) { 22 | defheader(displayName || name, immediate); 23 | f.dataSpace.push(fn); 24 | return fn; 25 | }; 26 | 27 | f.defvar = function defvar(name, initial) { 28 | defheader(name); 29 | var varAddress = f.dataSpace.length + 1; 30 | f.dataSpace.push(function variable() { 31 | f.stack.push(varAddress); 32 | }); 33 | f.dataSpace.push(initial); 34 | 35 | return function(value) { 36 | if (value !== undefined) 37 | f.dataSpace[varAddress] = value; 38 | else 39 | return f.dataSpace[varAddress]; 40 | }; 41 | }; 42 | 43 | latest = f.defvar("latest", f.dataSpace.length); // Replace existing function definition 44 | f.compiling = f.defvar("state", 0); 45 | 46 | f.compileEnter = function compileEnter(name) { 47 | var instruction = f.dataSpace.length + 1; 48 | 49 | var enter; 50 | try { 51 | enter = eval(`( 52 | function ${name}() { 53 | f.returnStack.push(f.instructionPointer); 54 | f.instructionPointer = instruction; 55 | }) 56 | `); 57 | } catch (e) { 58 | // Failback for names that are invalid identifiers 59 | enter = function enter() { 60 | f.returnStack.push(f.instructionPointer); 61 | f.instructionPointer = instruction; 62 | }; 63 | } 64 | 65 | f.dataSpace.push(enter); 66 | return enter; 67 | }; 68 | 69 | f.findDefinition = function findDefinition(word) { 70 | var current = latest(); 71 | while (current !== null) { 72 | var wordDefinition = f.dataSpace[current]; 73 | // Case insensitive 74 | if (wordDefinition.name && wordDefinition.name.toLowerCase() == word.toLowerCase() && !wordDefinition.hidden) 75 | return wordDefinition; 76 | current = wordDefinition.link; 77 | } 78 | return current; 79 | }; 80 | 81 | f.defjs(":", function colon() { 82 | var name = f._readWord(); 83 | defheader(name, false, true); 84 | f.compileEnter(name); 85 | f.compiling(true); 86 | }); 87 | 88 | f.defjs(":noname", function noname() { 89 | defheader(null, false, true); 90 | f.stack.push(f.dataSpace.length); 91 | f.compileEnter("_noname_"); 92 | f.compiling(true); 93 | }); 94 | 95 | var exit = f.defjs("exit", function exit() { 96 | f.instructionPointer = f.returnStack.pop(); 97 | }); 98 | 99 | f.defjs(";", function semicolon() { 100 | f.dataSpace.push(exit); 101 | f.dataSpace[latest()].hidden = false; 102 | f.compiling(false); 103 | }, true); // Immediate 104 | 105 | f.defjs("find", function find() { 106 | var input = f.stack.pop(); 107 | var word = input; 108 | if (typeof input === "number") { 109 | var startPosition = input; 110 | var length = f._getAddress(startPosition); 111 | word = ""; 112 | for (var i = 1; i <= length; i++) { 113 | word += String.fromCharCode(f._getAddress(startPosition + i)); 114 | } 115 | } 116 | var definition = f.findDefinition(word); 117 | if (definition) { 118 | f.stack.push(definition.executionToken); 119 | f.stack.push(definition.immediate ? 1 : -1); 120 | } else { 121 | f.stack.push(input); 122 | f.stack.push(0); 123 | } 124 | }); 125 | 126 | // Converts an execution token into the data field address 127 | f.defjs(">body", function dataFieldAddress() { 128 | f.stack.push(f.stack.pop() + 1); 129 | }); 130 | 131 | f.defjs("create", function create() { 132 | defheader(f._readWord()); 133 | var dataFieldAddress = f.dataSpace.length + 1; 134 | f.dataSpace.push(function pushDataFieldAddress() { 135 | f.stack.push(dataFieldAddress); 136 | }); 137 | }); 138 | 139 | f.defjs("allot", function allot() { 140 | f.dataSpace.length += f.stack.pop(); 141 | }); 142 | 143 | f.defjs(",", function comma() { 144 | f.dataSpace.push(f.stack.pop()); 145 | }); 146 | 147 | f.defjs("compile,", function compileComma() { 148 | f.dataSpace.push(f.dataSpace[f.stack.pop()]); 149 | }); 150 | 151 | f.defjs("[", function lbrac() { 152 | f.compiling(false); // Immediate 153 | }, true); // Immediate 154 | 155 | f.defjs("]", function rbrac() { 156 | f.compiling(true); // Compile 157 | }); 158 | 159 | f.defjs("immediate", function immediate() { 160 | var wordDefinition = f.dataSpace[latest()]; 161 | wordDefinition.immediate = true; 162 | }); 163 | 164 | f.defjs("hidden", function hidden() { 165 | var wordDefinition = f.dataSpace[f.stack.pop()]; 166 | wordDefinition.hidden = !wordDefinition.hidden; 167 | }); 168 | 169 | f.defjs("'", function tick() { 170 | f.stack.push(f.findDefinition(f._readWord()).executionToken); 171 | }); 172 | 173 | var _lit = f.defjs("lit", function lit() { 174 | f.stack.push(f.dataSpace[f.instructionPointer]); 175 | f.instructionPointer++; 176 | }); 177 | 178 | f.defjs("[']", function bracketTick() { 179 | f.dataSpace.push(f._lit); 180 | f.dataSpace.push(f.findDefinition(f._readWord()).executionToken); 181 | }, true); 182 | 183 | f.defjs("marker", function marker() { 184 | var savedLatest = latest(); 185 | var savedLength = f.dataSpace.length; 186 | 187 | defheader(f._readWord()); 188 | f.dataSpace.push(function marker() { 189 | latest(savedLatest); 190 | f.dataSpace.length = savedLength; 191 | }); 192 | }); 193 | 194 | f._latest = latest; 195 | f._lit = _lit; 196 | return f; 197 | } 198 | 199 | module.exports = Definitions; -------------------------------------------------------------------------------- /kernel/forth.js: -------------------------------------------------------------------------------- 1 | var Data = require("./data.js"); 2 | var Definitions = require("./definitions.js"); 3 | var NumericOperations = require("./numeric-operations.js"); 4 | var BooleanOperations = require("./boolean-operations.js"); 5 | var StackOperations = require("./stack-operations.js"); 6 | var MemoryOperations = require("./memory-operations.js"); 7 | var ControlStructures = require("./control-structures.js"); 8 | var JsInterop = require("./js-interop.js"); 9 | var Input = require("./input.js"); 10 | var Output = require("./output.js"); 11 | var Include = require("./include.js"); 12 | var Interpreter = require("./interpreter.js"); 13 | 14 | function Forth() { 15 | var forth = {}; 16 | 17 | Data(forth); 18 | Definitions(forth); 19 | Input(forth); 20 | NumericOperations(forth); 21 | BooleanOperations(forth); 22 | StackOperations(forth); 23 | MemoryOperations(forth); 24 | ControlStructures(forth); 25 | Output(forth); 26 | JsInterop(forth); 27 | Include(forth); 28 | Interpreter(forth); 29 | 30 | return forth; 31 | } 32 | 33 | module.exports = Forth; -------------------------------------------------------------------------------- /kernel/include.js: -------------------------------------------------------------------------------- 1 | var request = require("request"); 2 | var url = require("url"); 3 | var fs = require("fs"); 4 | var InputExceptions = require("./input-exceptions.js"); 5 | 6 | function Include(f) { 7 | f.defjs("include", function() { 8 | var outputCallback = f.outputCallback; 9 | 10 | var file = f._readWord(); 11 | if (process.browser || file.match(/^http/)) { 12 | if (process.browser) file = url.resolve(location.href, file); 13 | request.get(file, function(error, response, body) { 14 | if (!error && response.statusCode == 200) { 15 | f.run(body, outputCallback, file.toString()); 16 | } else { 17 | console.error("Failed to load http file " + file + ". " + error); 18 | } 19 | }); 20 | } else { 21 | fs.readFile(file, "utf8", function(error, body) { 22 | if (!error) { 23 | f.run(body, outputCallback, file); 24 | } else { 25 | console.error("Failed to load file " + file + ". " + error); 26 | } 27 | }); 28 | } 29 | throw InputExceptions.WaitingOnInput; 30 | }); 31 | 32 | return f; 33 | } 34 | 35 | module.exports = Include; -------------------------------------------------------------------------------- /kernel/input-exceptions.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | EndOfInput: {}, 3 | WaitingOnInput: {} 4 | }; -------------------------------------------------------------------------------- /kernel/input.js: -------------------------------------------------------------------------------- 1 | var InputExceptions = require("./input-exceptions.js"); 2 | 3 | function InputWindow(input, startPosition, endPosition, toIn, sourceId) { 4 | var inputBufferPosition = startPosition; 5 | var inputBufferLength = -1; 6 | 7 | function refill() { 8 | inputBufferPosition += inputBufferLength + 1; 9 | 10 | inputBufferLength = input.substring(inputBufferPosition).search(/\n/); 11 | if (inputBufferLength == -1 || inputBufferPosition + inputBufferLength > endPosition) 12 | inputBufferLength = endPosition - inputBufferPosition; 13 | 14 | toIn(0); 15 | return inputBufferPosition < endPosition; 16 | } 17 | 18 | function readKey() { 19 | var keyPosition = inputBufferPosition + toIn(); 20 | if (keyPosition < endPosition) { 21 | toIn(toIn() + 1); 22 | return input.charAt(keyPosition); 23 | } else { 24 | return null; 25 | } 26 | } 27 | 28 | function parse(delimiter, skipLeading) { 29 | delimiter = delimiter || " ".charCodeAt(0); 30 | var inputBuf = inputBuffer(); 31 | 32 | var startPosition = toIn(); 33 | if (skipLeading) { 34 | while (inputBuf.charCodeAt(startPosition) === delimiter && startPosition < inputBuf.length) { 35 | startPosition++; 36 | } 37 | } 38 | 39 | var endPosition = startPosition; 40 | while (inputBuf.charCodeAt(endPosition) !== delimiter && endPosition < inputBuf.length) { 41 | endPosition++; 42 | } 43 | 44 | toIn(endPosition + 1); 45 | var result = inputBuf.substring(startPosition, endPosition); 46 | return [inputBufferPosition + startPosition, result.length, result]; 47 | } 48 | 49 | function readWord(delimiter) { 50 | return parse(delimiter, true)[2]; 51 | } 52 | 53 | function sBackslashQuote() { 54 | var string = ""; 55 | 56 | while (true) { 57 | var char = readKey(); 58 | 59 | if (char === "\"") { 60 | break; 61 | } else if (char === "\\") { 62 | var nextChar = readKey(); 63 | switch (nextChar) { 64 | case "a": 65 | string += String.fromCharCode(7); 66 | break; 67 | case "b": 68 | string += String.fromCharCode(8); 69 | break; 70 | case "e": 71 | string += String.fromCharCode(27); 72 | break; 73 | case "f": 74 | string += String.fromCharCode(12); 75 | break; 76 | case "l": 77 | string += String.fromCharCode(10); 78 | break; 79 | case "m": 80 | string += String.fromCharCode(13) + String.fromCharCode(10); 81 | break; 82 | case "n": 83 | string += String.fromCharCode(10); 84 | break; 85 | case "q": 86 | string += String.fromCharCode(34); 87 | break; 88 | case "r": 89 | string += String.fromCharCode(13); 90 | break; 91 | case "t": 92 | string += String.fromCharCode(9); 93 | break; 94 | case "v": 95 | string += String.fromCharCode(11); 96 | break; 97 | case "z": 98 | string += String.fromCharCode(0); 99 | break; 100 | case "\"": 101 | string += String.fromCharCode(34); 102 | break; 103 | case "x": 104 | string += String.fromCharCode(parseInt(readKey() + readKey(), 16)); 105 | break; 106 | case "\\": 107 | string += String.fromCharCode(92); 108 | break; 109 | default: 110 | // Be lenient 111 | string += nextChar; 112 | } 113 | } else { 114 | string += char; 115 | } 116 | } 117 | 118 | return string; 119 | } 120 | 121 | function source() { 122 | return [inputBufferPosition, inputBufferLength]; 123 | } 124 | 125 | function inputBuffer() { 126 | if (inputBufferLength > 0) 127 | return input.substring(inputBufferPosition, inputBufferPosition + inputBufferLength); 128 | else 129 | return ""; 130 | } 131 | 132 | function subInput(position, length) { 133 | return InputWindow(input, position, position + length, toIn, -1); 134 | } 135 | 136 | function charCodeAt(index) { 137 | return input.charCodeAt(index); 138 | } 139 | 140 | return { 141 | readWord: readWord, 142 | readKey: readKey, 143 | parse: parse, 144 | refill: refill, 145 | inputBuffer: inputBuffer, 146 | source: source, 147 | charCodeAt: charCodeAt, 148 | subInput: subInput, 149 | sBackslashQuote: sBackslashQuote, 150 | sourceId: sourceId 151 | }; 152 | } 153 | 154 | function Input(f) { 155 | f._base = f.defvar("base", 10); 156 | 157 | // Input buffer pointer 158 | var toIn = f.defvar(">in", 0); 159 | 160 | // Address offset to indicate input addresses 161 | var INPUT_SOURCE = 1 << 31; 162 | 163 | f.defjs("source", function source() { 164 | var positionLength = f._currentInput.source(); 165 | f.stack.push(INPUT_SOURCE + positionLength[0]); 166 | f.stack.push(positionLength[1]); 167 | }); 168 | 169 | f.defjs("source-id", function sourceId() { 170 | f.stack.push(f._currentInput.sourceId); 171 | }); 172 | 173 | f.defjs("refill", function refill() { 174 | f.stack.push(f._currentInput.refill()); 175 | }); 176 | 177 | f.defjs("key", function key() { 178 | f.stack.push(f._currentInput.readKey().charCodeAt(0)); 179 | }); 180 | 181 | f.defjs("parse", function parse() { 182 | var addressLength = f._currentInput.parse(f.stack.pop(), false); 183 | f.stack.push(INPUT_SOURCE + addressLength[0]); 184 | f.stack.push(addressLength[1]); 185 | }); 186 | 187 | f.defjs("parse-name", function parse() { 188 | var addressLength = f._currentInput.parse(" ".charCodeAt(0), true); 189 | f.stack.push(INPUT_SOURCE + addressLength[0]); 190 | f.stack.push(addressLength[1]); 191 | }); 192 | 193 | function readWord(delimiter) { 194 | return f._currentInput.readWord(delimiter); 195 | } 196 | 197 | var wordBufferStart = f.dataSpace.length; 198 | f.dataSpace.length += 128; 199 | f.defjs("word", function word() { 200 | var delimiter = f.stack.pop(); 201 | var word = readWord(delimiter); 202 | var length = Math.min(word.length, 127); 203 | f.dataSpace[wordBufferStart] = length; 204 | for (var i = 0; i < length; i++) { 205 | f.dataSpace[wordBufferStart + i + 1] = word.charCodeAt(i); 206 | } 207 | 208 | f.stack.push(wordBufferStart); 209 | }); 210 | 211 | f.defjs("s\\\"", function sBackslashQuote() { 212 | var string = f._currentInput.sBackslashQuote(); 213 | var stringAddress = f.dataSpace.length + 1; 214 | f.dataSpace.push(function() { 215 | f.stack.push(stringAddress); 216 | f.stack.push(string.length); 217 | 218 | // Jump over compiled string 219 | f.instructionPointer += string.length; 220 | }); 221 | 222 | for (var i = 0; i < string.length; i++) { 223 | f.dataSpace.push(string[i]); 224 | } 225 | }, true); // Immediate 226 | 227 | f.defjs("char", function char() { 228 | f.stack.push(readWord().charCodeAt(0)); 229 | }); 230 | 231 | f.defjs("accept", function accept() { 232 | 233 | var maxLength = f.stack.pop(); 234 | var address = f.stack.pop(); 235 | 236 | f.currentInstruction = function acceptCallback() { 237 | f._currentInput.refill(); 238 | var received = f._currentInput.inputBuffer().substring(0, maxLength).split("\n")[0]; 239 | 240 | f.stack.push(received.length); 241 | for (var i = 0; i < received.length; i++) { 242 | f._setAddress(address + i, received[i]); 243 | } 244 | 245 | popInput(); 246 | }; 247 | 248 | throw InputExceptions.WaitingOnInput; 249 | }); 250 | 251 | // returns NaN if any characters are invalid in base 252 | function parseIntStrict(num, base) { 253 | var int = 0; 254 | if (num[0] !== "-") { // Positive 255 | for (var i = 0; i < num.length; i++) { 256 | int *= base; 257 | int += parseInt(num[i], base); 258 | } 259 | return int; 260 | } else { 261 | for (var j = 1; j < num.length; j++) { 262 | int *= base; 263 | int -= parseInt(num[j], base); 264 | } 265 | return int; 266 | } 267 | } 268 | 269 | // Parse a float in the current base 270 | function _parseFloatInBase(string) { 271 | var base; 272 | if (string[0] === "'" && string.length === 3 && string[2] == "'") { // 'a' 273 | return string.charCodeAt(1); 274 | } else if (string[0] === "#") { // decimal - #1234567890 275 | string = string.substring(1); 276 | base = 10; 277 | } else if (string[0] === "$") { // hex - $ff00ff 278 | string = string.substring(1); 279 | base = 16; 280 | } else if (string[0] === "%") { // binary - %10110110 281 | string = string.substring(1); 282 | base = 2; 283 | } else { 284 | base = f._base(); 285 | } 286 | 287 | var num = string.split(/\./); 288 | 289 | var integerPart = 0; 290 | if (num[0] !== '') { 291 | integerPart = parseIntStrict(num[0], base); 292 | } 293 | 294 | var fractionalPart = 0; 295 | if (num.length > 1 && num[1] !== '') { 296 | fractionalPart = parseIntStrict(num[1], base) * Math.pow(base, -num[1].length); 297 | } 298 | 299 | if (integerPart >= 0) { 300 | return integerPart + fractionalPart; 301 | } else { 302 | return integerPart - fractionalPart; 303 | } 304 | } 305 | 306 | var inputString = ""; 307 | 308 | function newInput(input, sourceId) { 309 | saveCurrentInput(); 310 | var startPosition = inputString.length; 311 | inputString += input; 312 | f._currentInput = InputWindow(inputString, startPosition, inputString.length, toIn, sourceId); 313 | } 314 | 315 | var inputStack = []; 316 | 317 | function subInput(position, length) { 318 | saveCurrentInput(); 319 | f._currentInput = f._currentInput.subInput(position, length); 320 | } 321 | 322 | function saveCurrentInput() { 323 | if (f._currentInput) { 324 | inputStack.push({ 325 | input: f._currentInput, 326 | toIn: toIn(), 327 | instructionPointer: f.instructionPointer 328 | }); 329 | } 330 | } 331 | 332 | function popInput() { 333 | var savedInput = inputStack.pop(); 334 | if (savedInput) { 335 | f._currentInput = savedInput.input; 336 | toIn(savedInput.toIn); 337 | f.instructionPointer = savedInput.instructionPointer; 338 | f.currentInstruction = f.dataSpace[f.instructionPointer++]; 339 | } else { 340 | f._currentInput = null; 341 | } 342 | } 343 | 344 | f.defjs("save-input", function saveInput() { 345 | saveCurrentInput(); 346 | for (var i = 0; i < inputStack.length; i++) { 347 | f.stack.push(inputStack[i]); 348 | } 349 | f.stack.push(inputStack.length); 350 | inputStack.pop(); 351 | }); 352 | 353 | f.defjs("restore-input", function restoreInput() { 354 | inputStack.length = 0; 355 | 356 | var length = f.stack.pop(); 357 | for (var i = length - 1; i >= 0; i--) { 358 | inputStack[i] = f.stack.pop(); 359 | } 360 | 361 | var savedInput = inputStack.pop(); 362 | f._currentInput = savedInput.input; 363 | toIn(savedInput.toIn); 364 | 365 | f.stack.push(0); 366 | }); 367 | 368 | f._readWord = readWord; 369 | f._newInput = newInput; 370 | f._subInput = subInput; 371 | f._popInput = popInput; 372 | f._parseFloatInBase = _parseFloatInBase; 373 | f._INPUT_SOURCE = INPUT_SOURCE; 374 | return f; 375 | } 376 | 377 | module.exports = Input; -------------------------------------------------------------------------------- /kernel/interpreter.js: -------------------------------------------------------------------------------- 1 | var InputExceptions = require("./input-exceptions.js"); 2 | 3 | function Interpreter(f) { 4 | function run(input, outputCallback, sourceId) { 5 | f.outputCallback = outputCallback; 6 | 7 | f._newInput(input, sourceId || 0); 8 | f._output = ""; 9 | 10 | try { 11 | runInterpreter(); 12 | } catch (err) { 13 | if (err !== InputExceptions.WaitingOnInput) { 14 | console.error("Exception " + err + " at:\n" + printStackTrace()); 15 | console.error(f._currentInput.inputBuffer()); 16 | console.error(f._output); 17 | f.currentInstruction = quit; 18 | f.stack.clear(); 19 | outputCallback(err, f._output); 20 | throw err; 21 | } 22 | } 23 | 24 | outputCallback(null, f._output); 25 | } 26 | 27 | function runInterpreter() { 28 | // Run while there is still input to consume 29 | while (f._currentInput) { 30 | try { 31 | // As js doesn't support tail call optimisation the 32 | // run function uses a trampoline to execute forth code 33 | while (true) { 34 | f.currentInstruction(); 35 | f.currentInstruction = f.dataSpace[f.instructionPointer++]; 36 | } 37 | } catch (err) { 38 | if (err === InputExceptions.EndOfInput) { 39 | f._popInput(); 40 | } else { 41 | throw err; 42 | } 43 | } 44 | } 45 | } 46 | 47 | function printStackTrace() { 48 | var stackTrace = " " + f.currentInstruction.name + " @ " + (f.instructionPointer - 1); 49 | for (var i = f.returnStack.length - 1; i >= 0; i--) { 50 | var instruction = f.returnStack[i]; 51 | stackTrace += "\n " + f.dataSpace[instruction - 1].name + " @ " + (instruction - 1); 52 | } 53 | return stackTrace; 54 | } 55 | 56 | f._evaluate = f.defjs("evaluate", function evaluate() { 57 | var length = f.stack.pop(); 58 | var address = f.stack.pop(); 59 | if (address < 0) { 60 | var position = address - f._INPUT_SOURCE; 61 | f._subInput(position, length); 62 | } else { 63 | var string = ""; 64 | for (var i = 0; i < length; i++) { 65 | string += String.fromCharCode(f._getAddress(address + i)); 66 | } 67 | f._newInput(string, -1); 68 | } 69 | 70 | f.instructionPointer = interpretInstruction; 71 | }); 72 | 73 | function interpretWord() { 74 | var word = f._readWord(); 75 | while (!word) { 76 | if (!f._currentInput.refill()) throw InputExceptions.EndOfInput; 77 | word = f._readWord(); 78 | } 79 | 80 | var definition = f.findDefinition(word); 81 | if (definition) { 82 | if (!f.compiling() || definition.immediate) { 83 | f.dataSpace[definition.executionToken](); 84 | return; 85 | } else { 86 | f.dataSpace.push(f.dataSpace[definition.executionToken]); 87 | } 88 | } else { 89 | var num = f._parseFloatInBase(word); 90 | if (isNaN(num)) throw "Word not defined: " + word; 91 | if (f.compiling()) { 92 | f.dataSpace.push(f._lit); 93 | f.dataSpace.push(num); 94 | } else { 95 | f.stack.push(num); 96 | } 97 | } 98 | } 99 | 100 | var interpretInstruction = f.dataSpace.length + 1; 101 | f.defjs("interpret", function interpret() { 102 | f.instructionPointer = interpretInstruction; // Loop after interpret word is called 103 | interpretWord(); 104 | }); 105 | 106 | var quit = f.defjs("quit", function quit() { 107 | f.compiling(false); // Enter interpretation state 108 | f.returnStack.clear(); // Clear return stack 109 | f.instructionPointer = interpretInstruction; // Run the interpreter 110 | }); 111 | 112 | var abort = f.defjs("abort", function abort(error) { 113 | f.stack.clear(); 114 | throw error || ""; 115 | }); 116 | 117 | f.defjs('abort"', function abortQuote() { 118 | var error = f._currentInput.parse('"'.charCodeAt(0))[2]; 119 | f.dataSpace.push(function abortQuote() { 120 | if (f.stack.pop()) 121 | abort(error); 122 | }); 123 | }, true); // Immediate 124 | 125 | f.defjs("execute", function execute() { 126 | f.dataSpace[f.stack.pop()](); 127 | }); 128 | 129 | // Set initial instruction 130 | f.currentInstruction = quit; 131 | f.run = run; 132 | 133 | return f; 134 | } 135 | 136 | module.exports = Interpreter; -------------------------------------------------------------------------------- /kernel/js-interop.js: -------------------------------------------------------------------------------- 1 | function JsInterop(f) { 2 | // Interop 3 | // - new with params js .new{1} 4 | // - global variable access js /document 5 | // - array access js .0.2 6 | // - property access/setting js .name js .name! 7 | // - function calling js .sin{1} .{2} >> obj = pop, f = obj[name], f.call(obj, pop(), pop()) 8 | // - method calling js /document.getElementById{1} 9 | // 10 | // When compiling it should resolve global names immediately. 11 | function jsNewCall(path) { 12 | var constructor = f.stack.pop(); 13 | var argsCount = parseInt(path.match(/\{(\d*)\}/)[1] || 0); 14 | var args = [null]; // new replaces the first argument with this 15 | for (var j = 0; j < argsCount; j++) { 16 | args.push(f.stack.pop()); 17 | } 18 | // Use new operator with any number of arguments 19 | return new(Function.prototype.bind.apply(constructor, args))(); 20 | } 21 | 22 | function jsFunctionCall(path) { 23 | var argsCount = parseInt(path.match(/\{(\d*)\}/)[1] || 0); 24 | var obj = f.stack.pop(); 25 | path = path.match(/[^\{]*/)[0]; 26 | var func = path ? obj[path] : obj; 27 | var args = []; 28 | for (var j = 0; j < argsCount; j++) { 29 | args.push(f.stack.pop()); 30 | } 31 | return func.apply(obj, args); 32 | } 33 | 34 | var jsAssignmentRegex = /(^[A-Za-z$_][\w$_]*!$)|(^\d+!$)/; // name! 35 | var jsNewCallRegex = /new\{\d*\}$/; // new{2} 36 | var jsFunctionCallRegex = /((^[A-Za-z$_][\w$_]*)|(^\d+))?\{\d*\}$/; // getElementById{1} 37 | 38 | var globl = (typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document) ? window : global; 39 | 40 | function jsInterop(js) { 41 | if (js.startsWith("/")) { // Add global to f.stack 42 | f.stack.push(globl); 43 | } else if (!js.startsWith(".")) { 44 | throw "js interop call must start with '/' or '.'"; 45 | } 46 | 47 | var paths = js.length > 1 ? js.substring(1).split(".") : []; 48 | 49 | for (var i = 0; i < paths.length; i++) { 50 | var path = paths[i]; 51 | 52 | if (path.match(jsAssignmentRegex)) { 53 | f.stack.pop()[path.substring(0, path.length - 1)] = f.stack.pop(); 54 | } else if (path.match(jsNewCallRegex)) { 55 | f.stack.push(jsNewCall(path)); 56 | } else if (path.match(jsFunctionCallRegex)) { 57 | f.stack.push(jsFunctionCall(path)); 58 | } else { // Property access 59 | f.stack.push(f.stack.pop()[path]); 60 | } 61 | } 62 | } 63 | 64 | var JS = f.defjs("js", function js() { 65 | jsInterop(f.stack.pop()); 66 | }); 67 | 68 | f.defjs("js", function js() { 69 | if (f.compiling()) { 70 | f.dataSpace.push(f._lit); 71 | f.dataSpace.push(f._readWord()); 72 | f.dataSpace.push(JS); 73 | } else { 74 | jsInterop(f._readWord()); 75 | } 76 | }, true); 77 | 78 | f.defjs(">js-string", function toJsString() { 79 | var length = f.stack.pop(); 80 | var address = f.stack.pop(); 81 | var string = ""; 82 | for (var i = 0; i < length; i++) { 83 | string += String.fromCharCode(f._getAddress(address + i)); 84 | } 85 | f.stack.push(string); 86 | }) 87 | 88 | return f; 89 | } 90 | 91 | module.exports = JsInterop; 92 | -------------------------------------------------------------------------------- /kernel/memory-operations.js: -------------------------------------------------------------------------------- 1 | function MemoryOperations(f) { 2 | function getAddress(address) { 3 | if (address < 0) { 4 | return f._currentInput.charCodeAt(address - f._INPUT_SOURCE); 5 | } else { 6 | var value = f.dataSpace[address]; 7 | if (typeof value == "string") 8 | return value.charCodeAt(0); 9 | else 10 | return value; 11 | } 12 | } 13 | 14 | function setAddress(address, value) { 15 | if (address < 0) { 16 | throw "Illegal attempt to change input"; 17 | } else { 18 | f.dataSpace[address] = value; 19 | } 20 | } 21 | 22 | f.defjs("!", function store() { 23 | var address = f.stack.pop(); 24 | var data = f.stack.pop(); 25 | setAddress(address, data); 26 | }); 27 | 28 | f.defjs("@", function fetch() { 29 | var address = f.stack.pop(); 30 | f.stack.push(getAddress(address)); 31 | }); 32 | 33 | f.defjs("+!", function addStore() { 34 | var address = f.stack.pop(); 35 | var data = f.stack.pop(); 36 | f.dataSpace[address] = f.dataSpace[address] + data; 37 | }); 38 | 39 | f.defjs("-!", function subtractStore() { 40 | var address = f.stack.pop(); 41 | var data = f.stack.pop(); 42 | f.dataSpace[address] = f.dataSpace[address] - data; 43 | }); 44 | 45 | f.defjs("here", function here() { 46 | f.stack.push(f.dataSpace.length); 47 | }); 48 | 49 | f._getAddress = getAddress; 50 | f._setAddress = setAddress; 51 | return f; 52 | } 53 | 54 | module.exports = MemoryOperations; -------------------------------------------------------------------------------- /kernel/numeric-operations.js: -------------------------------------------------------------------------------- 1 | var Long = require("long"); 2 | 3 | function NumericOperations(f) { 4 | f.defjs("+", function plus() { 5 | var first = f.stack.pop(); 6 | f.stack.push(f.stack.pop() + first | 0); 7 | }); 8 | 9 | f.defjs("-", function minus() { 10 | var first = f.stack.pop(); 11 | f.stack.push(f.stack.pop() - first | 0); 12 | }); 13 | 14 | f.defjs("*", function multiply() { 15 | var first = f.stack.pop(); 16 | f.stack.push(f.stack.pop() * first | 0); 17 | }); 18 | 19 | f.defjs("/", function divide() { 20 | var first = f.stack.pop(); 21 | var second = f.stack.pop(); 22 | f.stack.push(Math.trunc(second / first)); 23 | }); 24 | 25 | f.defjs("2*", function inc() { 26 | f.stack.push(f.stack.pop() << 1); 27 | }); 28 | 29 | f.defjs("2/", function inc() { 30 | f.stack.push(f.stack.pop() >> 1); 31 | }); 32 | 33 | f.defjs("mod", function mod() { 34 | var first = f.stack.pop(); 35 | f.stack.push(f.stack.pop() % first); 36 | }); 37 | 38 | f.defjs("s>d", function singleToDouble() { 39 | var value = Long.fromInt(f.stack.pop()); 40 | f.stack.push(value.low); 41 | f.stack.push(value.high); 42 | }); 43 | 44 | f.defjs("*/", function multiplyDivide() { 45 | var divisor = Long.fromInt(f.stack.pop()); 46 | var first = Long.fromInt(f.stack.pop()); 47 | var second = Long.fromInt(f.stack.pop()); 48 | var quotient = first.mul(second).div(divisor).toInt(); 49 | f.stack.push(quotient); 50 | }); 51 | 52 | f.defjs("m*", function() { 53 | var first = Long.fromInt(f.stack.pop()); 54 | var second = Long.fromInt(f.stack.pop()); 55 | var result = first.mul(second); 56 | f.stack.push(result.low); 57 | f.stack.push(result.high); 58 | }); 59 | 60 | f.defjs("*/mod", function multiplyDivideMod() { 61 | var divisor = Long.fromInt(f.stack.pop()); 62 | var first = Long.fromInt(f.stack.pop()); 63 | var second = Long.fromInt(f.stack.pop()); 64 | var mult = first.mul(second); 65 | var quotient = mult.div(divisor).toInt(); 66 | var mod = mult.mod(divisor).toInt(); 67 | f.stack.push(mod); 68 | f.stack.push(quotient); 69 | }); 70 | 71 | f.defjs("um*", function() { 72 | var first = Long.fromInt(f.stack.pop(), true); 73 | var second = Long.fromInt(f.stack.pop(), true); 74 | var result = first.mul(second); 75 | f.stack.push(result.low); 76 | f.stack.push(result.high); 77 | }); 78 | 79 | f.defjs("um/mod", function unsignedDivideMod() { 80 | var divisor = Long.fromInt(f.stack.pop()); 81 | var bigPart = f.stack.pop(); 82 | var smallPart = f.stack.pop(); 83 | var long = new Long(smallPart, bigPart, true); 84 | var quotient = long.div(divisor).toInt(); 85 | var mod = long.mod(divisor).toInt(); 86 | f.stack.push(mod); 87 | f.stack.push(quotient); 88 | }); 89 | 90 | f.defjs("fm/mod", function flooredDivideMod() { 91 | var divisor = Long.fromInt(f.stack.pop()); 92 | var bigPart = f.stack.pop(); 93 | var smallPart = f.stack.pop(); 94 | var long = new Long(smallPart, bigPart); 95 | var quotient = long.div(divisor).toInt(); 96 | var mod = long.mod(divisor).toInt(); 97 | f.stack.push(mod); 98 | f.stack.push(quotient); 99 | }); 100 | 101 | f.defjs("sm/rem", function symmetricDivideRem() { 102 | var divisor = Long.fromInt(f.stack.pop()); 103 | var bigPart = f.stack.pop(); 104 | var smallPart = f.stack.pop(); 105 | var long = new Long(smallPart, bigPart); 106 | var quotient = long.div(divisor).toInt(); 107 | var mod = long.mod(divisor).toInt(); 108 | f.stack.push(mod); 109 | f.stack.push(quotient); 110 | }); 111 | 112 | f.defjs("abs", function abs() { 113 | f.stack.push(Math.abs(f.stack.pop()) | 0); 114 | }); 115 | 116 | f.defjs("lshift", function lshift() { 117 | var shift = f.stack.pop(); 118 | var num = f.stack.pop(); 119 | f.stack.push(num << shift); 120 | }); 121 | 122 | f.defjs("rshift", function rshift() { 123 | var shift = f.stack.pop(); 124 | var num = f.stack.pop(); 125 | f.stack.push(num >>> shift); 126 | }); 127 | 128 | f.defjs("max", function max() { 129 | f.stack.push(Math.max(f.stack.pop(), f.stack.pop())); 130 | }); 131 | 132 | f.defjs("min", function min() { 133 | f.stack.push(Math.min(f.stack.pop(), f.stack.pop())); 134 | }); 135 | 136 | f.defjs("negate", function negate() { 137 | f.stack.push(-f.stack.pop()); 138 | }); 139 | 140 | return f; 141 | } 142 | 143 | module.exports = NumericOperations; -------------------------------------------------------------------------------- /kernel/output.js: -------------------------------------------------------------------------------- 1 | var Long = require("long"); 2 | 3 | function Output(f) { 4 | 5 | f._output = ""; 6 | 7 | f.defjs("cr", function cr() { 8 | f._output += "\n"; 9 | }); 10 | 11 | f.defjs(".", function dot() { 12 | var value; 13 | var top = f.stack.pop(); 14 | 15 | if (typeof top === "undefined") 16 | value = "undefined"; 17 | else if (top === null) 18 | value = "null"; 19 | else 20 | value = top.toString(f._base()); // Output numbers in current base 21 | 22 | f._output += value + " "; 23 | }); 24 | 25 | f.defjs(".s", function dotS() { 26 | for (var i = 1; i <= f.stack.length(); i++) { 27 | var top = f.stack.peek(i); 28 | var value; 29 | 30 | if (typeof top === "undefined") 31 | value = "undefined"; 32 | else if (top === null) 33 | value = "null"; 34 | else 35 | value = top.toString(f._base()); // Output numbers in current base 36 | 37 | f._output += value + "\n"; 38 | } 39 | }); 40 | 41 | f.defjs(".r", function dotR() { 42 | var value; 43 | var width = f.stack.pop(); 44 | var top = f.stack.pop(); 45 | 46 | if (typeof top === "undefined") 47 | value = "undefined"; 48 | else if (top === null) 49 | value = "null"; 50 | else 51 | value = top.toString(f._base()); // Output numbers in current base 52 | 53 | while (value.length < width) { 54 | value = " " + value; 55 | } 56 | f._output += value + " "; 57 | }); 58 | 59 | f.defjs("emit", function emit() { 60 | var value = f.stack.pop(); 61 | if (typeof value === "number") 62 | f._output += String.fromCharCode(value); 63 | else 64 | f._output += value; 65 | }); 66 | 67 | f.defjs("type", function type() { 68 | var length = f.stack.pop(); 69 | var address = f.stack.pop(); 70 | for (var i = 0; i < length; i++) { 71 | var value = f._getAddress(address + i); 72 | if (typeof value === "number") { 73 | f._output += String.fromCharCode(value); 74 | } else 75 | f._output += value; 76 | } 77 | }); 78 | 79 | // Numeric output 80 | var numericOutputStart = f.dataSpace.length; 81 | var numericOutput = ""; 82 | f.dataSpace.length += 128; 83 | 84 | f.defjs("<#", function initialiseNumericOutput() { 85 | numericOutput = ""; 86 | }); 87 | 88 | f.defjs("hold", function hold() { 89 | var value = f.stack.pop(); 90 | if (typeof value === "number") 91 | value = String.fromCharCode(value); 92 | numericOutput += value; 93 | }); 94 | 95 | f.defjs("#>", function finishNumericOutput() { 96 | f.stack.pop(); 97 | f.stack.pop(); 98 | for (var i = 0; i < numericOutput.length; i++) { 99 | f.dataSpace[numericOutputStart + i] = numericOutput[numericOutput.length - i - 1]; 100 | } 101 | f.stack.push(numericOutputStart); 102 | f.stack.push(numericOutput.length); 103 | }); 104 | 105 | f.defjs("sign", function sign() { 106 | if (f.stack.pop() < 0) 107 | numericOutput += "-"; 108 | }); 109 | 110 | f.defjs("#", function writeNextNumericOutput() { 111 | var bigPart = f.stack.pop(); 112 | var smallPart = f.stack.pop(); 113 | var value = new Long(smallPart, bigPart, true); 114 | var base = Long.fromInt(f._base()); 115 | 116 | numericOutput += value.mod(base).toString(base).toUpperCase(); 117 | value = value.div(base); 118 | 119 | f.stack.push(value.smallPart); 120 | f.stack.push(value.bigPart); 121 | }); 122 | 123 | f.defjs("#S", function writeAllNumericOutput() { 124 | var bigPart = f.stack.pop(); 125 | var smallPart = f.stack.pop(); 126 | var value = new Long(smallPart, bigPart, true); 127 | var base = Long.fromInt(f._base()); 128 | 129 | if (value.compare(Long.ZERO)) { 130 | while (value.compare(Long.ZERO)) { 131 | numericOutput += value.mod(base).toString(base).toUpperCase(); 132 | value = value.div(base); 133 | } 134 | } else { 135 | numericOutput += "0"; 136 | } 137 | 138 | f.stack.push(0); 139 | f.stack.push(0); 140 | }); 141 | 142 | f.defjs(">number", function toNumber() { 143 | var base = Long.fromInt(f._base()); 144 | var length = f.stack.pop(); 145 | var address = f.stack.pop(); 146 | var bigPart = f.stack.pop(); 147 | var smallPart = f.stack.pop(); 148 | var value = new Long(smallPart, bigPart, true); 149 | var unconverted = length; 150 | 151 | for (var i = 0; i < length; i++) { 152 | var next = parseInt(String.fromCharCode(f._getAddress(address)), base); 153 | 154 | if (isNaN(next)) { 155 | break; 156 | } else { 157 | address++; 158 | unconverted--; 159 | value = value.mul(base).add(Long.fromInt(next)); 160 | } 161 | } 162 | 163 | f.stack.push(value.low); 164 | f.stack.push(value.high); 165 | f.stack.push(address); 166 | f.stack.push(unconverted); 167 | }); 168 | 169 | return f; 170 | } 171 | 172 | module.exports = Output; 173 | -------------------------------------------------------------------------------- /kernel/stack-operations.js: -------------------------------------------------------------------------------- 1 | function StackOperations(f) { 2 | f.defjs("drop", function drop() { 3 | f.stack.pop(); 4 | }); 5 | 6 | f.defjs("swap", function swap() { 7 | var first = f.stack.pop(); 8 | var second = f.stack.pop(); 9 | f.stack.push(first); 10 | f.stack.push(second); 11 | }); 12 | 13 | f.defjs("dup", function dup() { 14 | f.stack.push(f.stack.peek()); 15 | }); 16 | 17 | f.defjs("over", function over() { 18 | f.stack.push(f.stack.peek(2)); 19 | }); 20 | 21 | f.defjs("pick", function pick() { 22 | f.stack.push(f.stack.peek(f.stack.pop() + 1)); 23 | }); 24 | 25 | f.defjs("rot", function rot() { 26 | var first = f.stack.pop(); 27 | var second = f.stack.pop(); 28 | var third = f.stack.pop(); 29 | f.stack.push(second); 30 | f.stack.push(first); 31 | f.stack.push(third); 32 | }); 33 | 34 | f.defjs("-rot", function backRot() { 35 | var first = f.stack.pop(); 36 | var second = f.stack.pop(); 37 | var third = f.stack.pop(); 38 | f.stack.push(first); 39 | f.stack.push(third); 40 | f.stack.push(second); 41 | }); 42 | 43 | f.defjs("roll", function roll() { 44 | var num = f.stack.pop(); 45 | f.stack.roll(num); 46 | }); 47 | 48 | f.defjs("2drop", function twoDrop() { 49 | f.stack.pop(); 50 | f.stack.pop(); 51 | }); 52 | 53 | f.defjs("2dup", function twoDup() { 54 | f.stack.push(f.stack.peek(2)); 55 | f.stack.push(f.stack.peek(2)); 56 | }); 57 | 58 | f.defjs("2over", function twoOver() { 59 | f.stack.push(f.stack.peek(4)); 60 | f.stack.push(f.stack.peek(4)); 61 | }); 62 | 63 | f.defjs("2swap", function twoSwap() { 64 | var first = f.stack.pop(); 65 | var second = f.stack.pop(); 66 | var third = f.stack.pop(); 67 | var fourth = f.stack.pop(); 68 | f.stack.push(second); 69 | f.stack.push(first); 70 | f.stack.push(fourth); 71 | f.stack.push(third); 72 | }); 73 | 74 | f.defjs("?dup", function nonZeroDup() { 75 | var first = f.stack.peek(); 76 | if (first !== 0) f.stack.push(first); 77 | }); 78 | 79 | f.defjs("depth", function depth() { 80 | f.stack.push(f.stack.length()); 81 | }); 82 | 83 | // Return f.stack 84 | f.defjs(">r", function toR() { 85 | f.returnStack.push(f.stack.pop()); 86 | }); 87 | 88 | f.defjs("r>", function rFrom() { 89 | f.stack.push(f.returnStack.pop()); 90 | }); 91 | 92 | f.defjs("r@", function rFetch() { 93 | f.stack.push(f.returnStack.peek()); 94 | }); 95 | 96 | f.defjs("2r>", function twoRFrom() { 97 | var top = f.returnStack.pop(); 98 | f.stack.push(f.returnStack.pop()); 99 | f.stack.push(top); 100 | }); 101 | 102 | f.defjs("2>r", function twoToR() { 103 | var top = f.stack.pop(); 104 | f.returnStack.push(f.stack.pop()); 105 | f.returnStack.push(top); 106 | }); 107 | 108 | f.defjs("2r@", function twoRFetch() { 109 | f.stack.push(f.returnStack.peek(2)); 110 | f.stack.push(f.returnStack.peek(1)); 111 | }); 112 | 113 | return f; 114 | } 115 | 116 | module.exports = StackOperations; -------------------------------------------------------------------------------- /kernel/stack.js: -------------------------------------------------------------------------------- 1 | function Stack(name) { 2 | var data = []; 3 | 4 | this.pop = function() { 5 | if (data.length > 0) 6 | return data.pop(); 7 | else { 8 | throw "Stack empty: " + name; 9 | } 10 | }; 11 | 12 | this.push = function(element) { 13 | data.push(element); 14 | }; 15 | 16 | this.peek = function(offset) { 17 | var index = data.length - (offset || 1); 18 | if (0 <= index && index < data.length) 19 | return data[index]; 20 | else 21 | throw "Attempted to peek at invalid stack index " + index + ": " + name; 22 | }; 23 | 24 | this.roll = function(num) { 25 | if (num === 0) return; 26 | 27 | var index = data.length - num - 1; 28 | if (0 <= index && index < data.length) { 29 | var newTop = data.splice(index, 1)[0]; 30 | data.push(newTop); 31 | } else 32 | throw "Attempted to roll more elements than in stack " + num + ": " + name; 33 | }; 34 | 35 | this.length = function() { 36 | return data.length; 37 | }; 38 | 39 | this.clear = function() { 40 | data.length = 0; 41 | }; 42 | 43 | this.toString = function() { 44 | return data.toString(); 45 | }; 46 | } 47 | 48 | module.exports = Stack; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-forth", 3 | "version": "0.2.0", 4 | "author": "brendanator", 5 | "main": "kernel/forth.js", 6 | "homepage": "https://github.com/brendanator/jsForth", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/brendanator/jsForth.git" 10 | }, 11 | "keywords": [ 12 | "forth" 13 | ], 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/brendanator/jsForth/issues" 17 | }, 18 | "scripts": { 19 | "build": "browserify browser/main.js > bundle.js", 20 | "build:watch": "watchify browser/main.js -o 'exorcist bundle.js.map > bundle.js' -dv", 21 | "test": "test/run-ans-forth-tests.js", 22 | "test:watch": "watch 'npm run test' .", 23 | "start": "python -m SimpleHTTPServer" 24 | }, 25 | "dependencies": { 26 | "long": "^3.0.3", 27 | "request": "^2.69.0" 28 | }, 29 | "devDependencies": { 30 | "browserify": "latest", 31 | "watchify": "latest", 32 | "exorcist": "latest", 33 | "watch": "latest" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## jsForth 2 | 3 | An implementation of [Forth](https://en.wikipedia.org/wiki/Forth_(programming_language)) in JavaScript 4 | 5 | Try it out [here](https://brendanator.github.io/jsForth/) 6 | 7 | ### ANS Forth 8 | 9 | jsForth implements the full core ANS standard 10 | 11 | The following word sets are implemented: 12 | 13 | - Core words - fully implemented 14 | - All tests pass except some integer multiplication/division edge cases where the ANS spec differs from the results given by the [long](https://github.com/dcodeIO/long.js) package 15 | - Core plus words - fully implemented and all tests pass 16 | - Core extension words - fully implemented and all tests pass 17 | 18 | The ANS Forth tests can be run using `npm run test` or in the intepreter with the following Forth code 19 | ``` 20 | include test/verbose-tester.fth 21 | include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/core.fr 22 | ``` 23 | The complete test suite is available [here](https://github.com/gerryjackson/forth2012-test-suite/) 24 | 25 | ### JavaScript interoperability 26 | 27 | - global variable access `js /document` 28 | - array access `js .0.2` 29 | - property access `js .name` 30 | - property setting `js .name!` 31 | - function calling `js .sin{1}` 32 | - method calling `js /document.getElementById{1}` 33 | - new with params `js .new{1}` 34 | 35 | ### Threading model - trampolined threading 36 | 37 | All of the standard [threading models](https://en.wikipedia.org/wiki/Threaded_code#Threading_models) require `call` or `jump` to execute the next instruction. In JavaScript these both translate into functions calls in tail position. Without tail call optimisation this will lead to a stack overflow. Therefore jsForth uses a trampoline to execute instructions. 38 | 39 | ECMAScript 6 specifies tail call optimisation, but unfortunately most [browsers don't support it yet](https://kangax.github.io/compat-table/es6/). 40 | -------------------------------------------------------------------------------- /test/run-ans-forth-tests.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | var Forth = require("../kernel/forth.js"); 4 | var fs = require("fs"); 5 | 6 | var forth = new Forth(); 7 | 8 | function outputCallback(error, output) { 9 | console.log(output); 10 | if (error) { 11 | console.error(error); 12 | } 13 | } 14 | 15 | fs.readFile(__dirname + "/../forth/forth.fth", "utf8", function(err, data) { 16 | fs.readFile(__dirname + "/runtests.fth", "utf8", function(err, tests) { 17 | forth.run(data, outputCallback); 18 | forth.run(tests, outputCallback); 19 | }); 20 | }); -------------------------------------------------------------------------------- /test/runtests.fth: -------------------------------------------------------------------------------- 1 | \ ANS Forth tests - run all tests 2 | 3 | \ Adjust the file paths as appropriate to your system 4 | \ Select the appropriate test harness, either the simple tester.fr 5 | \ or the more complex ttester.fs 6 | 7 | CR .( Running ANS Forth and Forth 2012 test programs, version 0.13) CR 8 | 9 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/prelimtest.fth 10 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/tester.fr 11 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/ttester.fs 12 | include test/verbose-tester.fth 13 | 14 | \ Dummy implementation so accept test runs in batch 15 | : accept drop 0 swap ! 0 ; 16 | 17 | include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/core.fr 18 | include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/coreplustest.fth 19 | include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/utilities.fth 20 | include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/errorreport.fth 21 | include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/coreexttest.fth 22 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/blocktest.fth 23 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/doubletest.fth 24 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/exceptiontest.fth 25 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/facilitytest.fth 26 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/filetest.fth 27 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/localstest.fth 28 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/memorytest.fth 29 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/toolstest.fth 30 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/searchordertest.fth 31 | \ include https://raw.githubusercontent.com/gerryjackson/forth2012-test-suite/master/src/stringtest.fth 32 | \ REPORT-ERRORS 33 | 34 | CR .( Forth tests completed ) CR CR 35 | 36 | -------------------------------------------------------------------------------- /test/verbose-tester.fth: -------------------------------------------------------------------------------- 1 | \ From: John Hayes S1I 2 | \ Subject: tester.fr 3 | \ Date: Mon, 27 Nov 95 13:10:09 PST 4 | 5 | \ (C) 1995 JOHNS HOPKINS UNIVERSITY / APPLIED PHYSICS LABORATORY 6 | \ MAY BE DISTRIBUTED FREELY AS LONG AS THIS COPYRIGHT NOTICE REMAINS. 7 | \ VERSION 1.1 8 | HEX 9 | 10 | \ SET THE FOLLOWING FLAG TO TRUE FOR MORE VERBOSE OUTPUT; THIS MAY 11 | \ ALLOW YOU TO TELL WHICH TEST CAUSED YOUR SYSTEM TO HANG. 12 | VARIABLE VERBOSE 13 | FALSE VERBOSE ! 14 | 15 | : EMPTY-STACK \ ( ... -- ) EMPTY STACK: HANDLES UNDERFLOWED STACK TOO. 16 | DEPTH ?DUP IF DUP 0< IF NEGATE 0 DO 0 LOOP ELSE 0 DO DROP LOOP THEN THEN ; 17 | 18 | VARIABLE ACTUAL-DEPTH \ STACK RECORD 19 | CREATE ACTUAL-RESULTS 20 CELLS ALLOT 20 | VARIABLE EXPECTED-DEPTH \ STACK RECORD 21 | CREATE EXPECTED-RESULTS 20 CELLS ALLOT 22 | 23 | : SHOW-EXPECTED-ACTUAL 24 | ." Expected: " EXPECTED-DEPTH @ ?DUP IF 0 DO EXPECTED-RESULTS I CELLS + @ . LOOP ELSE ." " THEN 25 | ." , Actual: " ACTUAL-DEPTH @ ?DUP IF 0 DO ACTUAL-RESULTS I CELLS + @ . LOOP ELSE ." " THEN 26 | ; 27 | 28 | VARIABLE #ERRORS 0 #ERRORS ! 29 | 30 | : ERROR \ ( C-ADDR U -- ) DISPLAY AN ERROR MESSAGE FOLLOWED BY 31 | \ THE LINE THAT HAD THE ERROR. 32 | TYPE \ DISPLAY ERROR 33 | SOURCE TYPE SPACE \ DISPLAY LINE CORRESPONDING TO ERROR 34 | SHOW-EXPECTED-ACTUAL CR 35 | EMPTY-STACK \ THROW AWAY EVERY THING ELSE 36 | #ERRORS @ 1 + #ERRORS ! 37 | ; 38 | 39 | : T{ \ ( -- ) SYNTACTIC SUGAR. 40 | ; 41 | 42 | : -> \ ( ... -- ) RECORD DEPTH AND CONTENT OF STACK. 43 | DEPTH DUP ACTUAL-DEPTH ! \ RECORD DEPTH 44 | ?DUP IF \ IF THERE IS SOMETHING ON STACK 45 | 0 DO ACTUAL-RESULTS I CELLS + ! LOOP \ SAVE THEM 46 | THEN ; 47 | 48 | : }T \ ( ... -- ) COMPARE STACK (EXPECTED) CONTENTS WITH SAVED (ACTUAL) CONTENTS. 49 | 50 | DEPTH DUP EXPECTED-DEPTH ! \ RECORD DEPTH 51 | ?DUP IF \ IF THERE IS SOMETHING ON STACK 52 | 0 DO EXPECTED-RESULTS I CELLS + ! LOOP \ SAVE THEM 53 | THEN 54 | 55 | EXPECTED-DEPTH @ ACTUAL-DEPTH @ = IF \ IF DEPTHS MATCH 56 | EXPECTED-DEPTH @ ?DUP IF \ IF THERE IS SOMETHING ON THE STACK 57 | 0 DO \ FOR EACH STACK ITEM 58 | EXPECTED-RESULTS I CELLS + @ \ COMPARE ACTUAL WITH EXPECTED 59 | ACTUAL-RESULTS I CELLS + @ \ COMPARE ACTUAL WITH EXPECTED 60 | <> IF S" Incorrect result: " ERROR LEAVE THEN 61 | LOOP 62 | THEN 63 | ELSE \ DEPTH MISMATCH 64 | S" Wrong number of results: " ERROR 65 | THEN ; 66 | 67 | : TESTING \ ( -- ) TALKING COMMENT. 68 | SOURCE VERBOSE @ 69 | IF DUP >R TYPE CR R> >IN ! 70 | ELSE >IN ! DROP 71 | THEN ; 72 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | ### Todo 2 | 3 | - ~~Sensible scrolling of output on site~~ 4 | - ~~Inline `next` into `run`~~ 5 | - ~~Pass all non-numeric ans-forth-tests and most numeric ones~~ 6 | - ~~Modularise codebase~~ 7 | - ~~Implement `include` to asynchronously continue execution when the file is received from the server~~ 8 | - ~~Stack comment forth words~~ 9 | - Introduce stack word to allow `: over s( x1 x2 -- x1 x2 x1 ) ;` 10 | - Introduce `js-operator` to allow binary `+ - * / % >> == >= etc` and unary `! ~` operators etc to be used in forth - [all operators](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) 11 | - Optimising compiler - merge all words that don't contain jumps or jump destinations 12 | - Rename dataStack, wordDefinitions, Header, defheader, defjs, defword 13 | - Remove public access to stack, definitions 14 | - Implement backtick for js interop? 15 | - Go through `starting forth` to make sure all the examples work 16 | + forget -------------------------------------------------------------------------------- /universal-stack-operations.md: -------------------------------------------------------------------------------- 1 | ## pop, dup, swap, dip are universal stack operations - sketch of proof 2 | 3 | > For Joy itself the following seems to be an adequate base for shuffling the stack: the three simple operators swap dup and pop, together with the combinator dip. 4 | > [Manfred von Thun](http://www.nsl.com/papers/interview.htm) 5 | 6 | 7 | // Bring element `n-th from top` to the top 8 | bring-up(0) = noop 9 | bring-up(1) = swap 10 | bring-up(2) = [swap] dip swap 11 | bring-up(n) = [bring-to-top(n-1)] dip swap 12 | 13 | // Push top element to `n-th from top` position 14 | push-down(0) = noop 15 | push-down(1) = swap 16 | push-down(2) = swap [swap] dip 17 | push-down(n) = swap [push-down(n-1)] dip 18 | 19 | // Delete `n-th from top` element 20 | delete(n) = bring-up(n) pop 21 | 22 | // Make a copy of `n-th from top` element 23 | copy(n) = bring-up(n) dup push-down(n+1) 24 | 25 | Given any starting stack and any final stack we can use the above operations to go from starting stack to final stack: 26 | 27 | 1. delete all elements not in final result 28 | 2. copy elements that occur multiple times until stack contains all final elements 29 | 3. for each final index from bottom, bring-up top matching element from current index, push-down to final position 30 | --------------------------------------------------------------------------------