├── LICENSE.md ├── README.md ├── index.html └── jsforth.js /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015 Phil Eaton 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSForth 2 | 3 | This is a micro-Forth implementation in Javascript. It is built around an HTML REPL. 4 | 5 | I wrote this two years ago during a PL course in college. The code is not the greatest; it only took a few hours to throw together. That said, it is pretty neat. 6 | 7 | # Try It Out 8 | 9 | A demonstration REPL is available via CodePen [here](http://codepen.io/eatonphil/full/YPbWVN/). 10 | 11 | Alternatively, it can be run locally by navigating to the provided index.html file. 12 | 13 | # Built-in Commands 14 | 15 | ``` 16 | + - / * ^ < > <= >= = != 17 | ex: a b + // displays: Stack: (a+b) 18 | 19 | . - returns the top element of the Stack 20 | ex: a b . // displays: b; Stack: a 21 | 22 | .s - displays the current Stack and the size 23 | ex: a b .s // displays: a b <2>; Stack: a b 24 | 25 | .c - displays the top of the Stack as a character 26 | ex: 0 97 .c // displays: a ; Stack: 0 97 27 | 28 | drop - pops off the top element without returning it 29 | ex: a b drop // displays: nothing; Stack: a 30 | 31 | pick - puts a copy of the nth element on the top of the Stack 32 | ex: a b c 2 pick // displays: nothing; Stack: a b c a 33 | 34 | rot - rotates the Stack clockwise 35 | ex: a b c rot // displays: nothing; Stack: b c a 36 | 37 | -rot - rotates the Stack counter-clockwise 38 | ex: a b c -rot // displays: nothing; Stack c a b 39 | 40 | swap - swaps the top two elements 41 | ex: a b // displays: nothing; Stack: b a 42 | 43 | over - copies the second-to-last element to the top of the Stack 44 | ex: a b over // displays: nothing; Stack: a b a 45 | 46 | dup - copies the top element 47 | ex: a b dup // displays: nothing; Stack: a b b 48 | 49 | if ... then - executes what follows "if" if it evaluates true, continues on normally after optional "then" 50 | ex: a b > if c then d // displays: nothing; Stack: a b c d //if a > b; Stack: a b d //if a <= b 51 | 52 | do ... [loop] - executes what is between "do" and "loop" or the end of the line 53 | ex: a b c do a + // displays: nothing; Stack: adds a to itself b times starting at c 54 | 55 | invert - negates the top element of the Stack 56 | ex: a invert // displays: nothing; Stack: 0 //a != 0; Stack: 1 //a == 0 57 | 58 | clear - empties the Stack 59 | ex: a b clear // displays: nothing; Stack: 60 | 61 | : - creates a new custom (potentially recursive) definition 62 | ex: a b c : add2 + + ; add2 // displays: nothing; Stack: (a+b+c) 63 | 64 | allocate - reallocates the max recursion for a single line of input 65 | ex: 10 allocate 66 | 67 | cls - clears the screen 68 | 69 | debug - toggles console debug mode 70 | ``` 71 | 72 | # Examples 73 | 74 | ## Basics 75 | 76 | ``` 77 | >>> 3 4 + 78 | 79 | >>> .s 80 | 7 81 | >>> 3 4 - .s 82 | -1 83 | >>> 3 4 < .s 84 | 1 85 | >>> 3 4 > .s 86 | 0 87 | >>> 3 dup .s 88 | 3 3 89 | >>> = .s 90 | 1 91 | >>> drop 92 | 93 | >>> .s 94 | 95 | ``` 96 | 97 | ## Conditions 98 | 99 | ``` 100 | >>> 3 4 < if 11 then 12 101 | 102 | >>> .s 103 | 11 12 104 | >>> 3 4 > if 12 then 14 .s 105 | 14 106 | ``` 107 | 108 | ## Functions 109 | 110 | ``` 111 | >>> : plus + ; 112 | 113 | >>> 2 3 plus 114 | 5 115 | ``` 116 | 117 | ## Loops 118 | 119 | ### Power Function 120 | 121 | ``` 122 | >>> : pow over swap 1 do over * loop swap drop ; 123 | 124 | >>> 2 3 pow .s 125 | 8 126 | ``` 127 | 128 | ## Recursion 129 | 130 | ### Fibonacci 131 | 132 | ``` 133 | >>> : fib dup 1 > if 1 - dup fib swap 1 - fib + then ; 134 | 135 | >>> 6 fib .s 136 | 8 137 | ``` 138 | 139 | ### Factorial 140 | 141 | ``` 142 | >>> : fac dup 1 > if dup 1 - fac * then dup 0 = if drop 1 then dup 1 = if drop 1 then ; 143 | 144 | >>> 3 fac 145 | 6 146 | ``` 147 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | JSForth 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /jsforth.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2013-2015 Phil Eaton 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | if (!String.prototype.trim) { 19 | String.prototype.trim = function() { 20 | return this.replace(/^\s+|\s+$/g,''); 21 | }; 22 | } 23 | 24 | /* 25 | * CONSTANTS 26 | */ 27 | var FORTH_TEXT = "JSForth Interpreter v0.1 Copyright (C) 2013-2015 Phil Eaton \ 28 | \nType \"help\" to see some documentation."; 29 | 30 | var FORTH_PROMPT = "\n>>> "; 31 | var FORTH_EOF = "bye"; 32 | var FORTH_DEFAULT_ALLOCATION = 1000; 33 | var FORTH_ALLOCATION = FORTH_DEFAULT_ALLOCATION; 34 | var FORTH_FALSE = 0; 35 | var FORTH_TRUE = !FORTH_FALSE; 36 | var FORTH_DEBUG = false; 37 | var FORTH_HELP = "\For more documentation on the FORTH language, visit http://www.complang.tuwien.ac.at/forth/gforth/Docs-html/ \ 38 | \nFor a concise tutorial/introduction to FORTH, visit http://www.ece.cmu.edu/~koopman/forth/hopl.html \ 39 | \nwww.forth.com is also a great resource. \ 40 | \nPlease feel free to submit any bugs/comments/suggestions to meeatonphilcom \ 41 | \n\nSupported Commands: \ 42 | \n+ - / * ^ < > <= >= = != \ 43 | \n ex: a b + // displays: Stack: (a+b) \ 44 | \n. - returns the top element of the Stack \ 45 | \n ex: a b . // displays: b; Stack: a \ 46 | \n.s - displays the current Stack and the size \ 47 | \n ex: a b .s // displays: a b <2>; Stack: a b \ 48 | \n.c - displays the top of the Stack as a character \ 49 | \n ex: 0 97 .c // displays: a ; Stack: 0 97 \ 50 | \ndrop - pops off the top element without returning it \ 51 | \n ex: a b drop // displays: nothing; Stack: a \ 52 | \npick - puts a copy of the nth element on the top of the Stack \ 53 | \n ex: a b c 2 pick // displays: nothing; Stack: a b c a \ 54 | \nrot - rotates the Stack clockwise \ 55 | \n ex: a b c rot // displays: nothing; Stack: b c a \ 56 | \n-rot - rotates the Stack counter-clockwise \ 57 | \n ex: a b c -rot // displays: nothing; Stack: c a b \ 58 | \nswap - swaps the top two elements \ 59 | \n ex: a b // displays: nothing; Stack: b a \ 60 | \nover - copies the second-to-last element to the top of the Stack \ 61 | \n ex: a b over // displays: nothing; Stack: a b a \ 62 | \ndup - copies the top element \ 63 | \n ex: a b dup // displays: nothing; Stack: a b b \ 64 | \nif ... then - executes what follows \"if\" if it evaluates true, continues on normally after optional \"then\" \ 65 | \n ex: a b > if c then d // displays: nothing; Stack: a b c d //if a > b; Stack: a b d //if a <= b \ 66 | \ndo ... [loop] - executes what is between \"do\" and \"loop\" or the end of the line \ 67 | \n ex: a b c do a + // displays: nothing; Stack: adds a to itself b times starting at c\ 68 | \ninvert - negates the top element of the Stack \ 69 | \n ex: a invert // displays: nothing; Stack: 0 //a != 0; Stack: 1 //a == 0 \ 70 | \nclear - empties the Stack \ 71 | \n ex: a b clear // displays: nothing; Stack: \ 72 | \n: - creates a new custom (potentially recursive) definition \ 73 | \n ex: a b c : add2 + + ; add2 // displays: nothing; Stack: (a+b+c) \ 74 | \nallocate - reallocates the max recursion for a single line of input \ 75 | \n ex: 10 allocate \ 76 | \ncls - clears the screen \ 77 | \ndebug - toggles console debug mode"; 78 | 79 | // Ignore potential Stack underflow errors if an operator is within a definition. 80 | var IN_DEFINITION = false; 81 | 82 | /* 83 | * ERRORS 84 | */ 85 | 86 | var FORTH_OK = ""; 87 | var FORTH_ERROR = ""; 88 | 89 | // CODES 90 | var CMD_NOT_FOUND = -1; 91 | var STACK_UNDERFLOW = -2; 92 | var PICK_OUT_OF_BOUNDS = -3; 93 | var STACK_OVERFLOW = -4; 94 | var BAD_DEF_NAME = -5; 95 | var IF_EXPECTED_THEN = -6; 96 | 97 | // MESSAGES 98 | var FORTH_ERROR_GENERIC = "Forth Error."; 99 | var FORTH_ERROR_MESSAGE = ""; 100 | 101 | var main; 102 | var terminal; 103 | var user_def = {}; 104 | 105 | function valid_def_name(name) 106 | { 107 | var chr = name.charAt(0); 108 | if (chr >= 'a' && chr <= 'z') 109 | return true; 110 | return false; 111 | } 112 | 113 | function interpret(input) { 114 | terminal = window.terminal; 115 | RECUR_COUNT++; 116 | 117 | if (RECUR_COUNT == FORTH_ALLOCATION) 118 | { 119 | FORTH_ERROR = STACK_OVERFLOW; 120 | FORTH_ERROR_MESSAGE = "Stack Overflow. If this is generated incorrectly, the Stack can be reallocated. Default max recursion for a line of input is "+FORTH_DEFAULT_ALLOCATION+"."; 121 | } 122 | else 123 | { 124 | if (FORTH_DEBUG) 125 | { 126 | console.log("current_line: "+input); 127 | } 128 | tokens = input.split(" "); 129 | for (var i = 0; i < tokens.length; i++) { 130 | token = tokens[i]; 131 | if (FORTH_DEBUG) 132 | console.log("current_token: "+token); 133 | if (!isNaN(parseFloat(token)) && isFinite(token)) { 134 | main.push(token); 135 | } else { 136 | token = token.toLowerCase(); 137 | if (token == "cls") { 138 | terminal.value = ""; 139 | return; 140 | } else if (token == "help") { 141 | terminal.value = FORTH_HELP; 142 | return; 143 | } else if (token == "debug") { 144 | FORTH_DEBUG = (FORTH_DEBUG?false:true); 145 | return "console debugging enabled: "+FORTH_DEBUG; 146 | } else if (token == "allocate") { 147 | FORTH_ALLOCATION = Number(main.pop()); 148 | return "Stack max reallocated: "+FORTH_ALLOCATION; 149 | } else if (token == ".s") { 150 | printBuffer.push(main.join(" ")); 151 | continue; 152 | } else if (token == ".c") { 153 | printBuffer.push(String.fromCharCode(main[main.length-1])); 154 | continue; 155 | } 156 | 157 | if (token == "." || token == "if" || token == "invert" || token == "drop" || token == "dup")// if token represents a binary operator 158 | { 159 | if (main.length < 1 || IN_DEFINITION == true) { 160 | FORTH_ERROR = STACK_UNDERFLOW; 161 | FORTH_ERROR_MESSAGE = "Too few arguments: \""+token+"\"."; 162 | } else if (!IN_DEFINITION) { 163 | if (token == ".") { 164 | return main.pop(); 165 | } else if (token == "if") { 166 | var top = (Number(main.pop())==FORTH_FALSE); 167 | var then = tokens.indexOf("then"); 168 | if (then !== -1) { 169 | if (top) { 170 | tokens = tokens.slice(then+1); 171 | if (tokens.join(" ") == "") 172 | return; 173 | } 174 | else { 175 | tokens = tokens.slice(tokens.indexOf("if")+1); 176 | then = tokens.indexOf("then"); 177 | tokens.splice(then, 1); 178 | } 179 | console.log(tokens); 180 | return interpret(tokens.join(" ")); 181 | } else { 182 | FORTH_ERROR = IF_EXPECTED_THEN; 183 | FORTH_ERROR_MESSAGE = "Expected \"then\" in input line."; 184 | return; 185 | } 186 | } else if (token == "invert") 187 | { 188 | top = main.pop(); 189 | if (top == FORTH_TRUE) 190 | top = FORTH_FALSE; 191 | else 192 | top = 1; 193 | main.push(top); 194 | } else if (token == "drop") 195 | { 196 | main.pop(); 197 | } else if (token == "dup") { 198 | first = main.pop(); 199 | main.push(first); 200 | main.push(first); 201 | } 202 | } 203 | } else if (token == "+" || token == "-" || token == "*" || token == "^" || token == "/" || token == "swap" || token == "over" || token == "pick" || token == "=" || token == "!=" || token == ">=" || token == "<=" || token == ">" || token == "<" || token == "do" || token == "rot" || token == "-rot") { 204 | if (main.length < 2) { 205 | FORTH_ERROR = STACK_UNDERFLOW; 206 | FORTH_ERROR_MESSAGE = "Too few arguments: \""+token+"\"."; 207 | } else if (!IN_DEFINITION) { 208 | if (token == "+") { 209 | first = Number(main.pop()); 210 | second = Number(main.pop()); 211 | main.push(second + first); 212 | } else if (token == "-") { 213 | first = Number(main.pop()); 214 | second = Number(main.pop()); 215 | main.push(second - first); 216 | } else if (token == "*") { 217 | first = Number(main.pop()); 218 | second = Number(main.pop()); 219 | main.push(second * first); 220 | } else if (token == "/") { 221 | first = Number(main.pop()); 222 | second = Number(main.pop()); 223 | main.push(second / first); 224 | } else if (token == "^") { 225 | first = Number(main.pop()); 226 | second = Number(main.pop()); 227 | main.push(pow(second, first)); 228 | } else if (token == "swap") { 229 | first = main.pop(); 230 | second = main.pop(); 231 | main.push(first); 232 | main.push(second); 233 | } else if (token == "over") { 234 | first = main.pop(); 235 | second = main.pop(); 236 | main.push(second); 237 | main.push(first); 238 | main.push(second); 239 | } else if (token == "pick") { 240 | n = Number(main.pop()); 241 | if (n < main.length && n >= 1) { 242 | var popped = Array(); 243 | for (var j = 0; j < n; j++) { 244 | popped.push(main.pop()); 245 | } 246 | var picked = Number(main.pop()); 247 | main.push(picked); 248 | for (var j = 0; j < n; j++) { 249 | main.push(popped.pop()); 250 | } 251 | main.push(picked); 252 | } else { 253 | FORTH_ERROR = PICK_OUT_OF_BOUNDS; 254 | FORTH_ERROR_MESSAGE = "Pick out of bounds."; 255 | } 256 | } 257 | else if (token == "<") 258 | { 259 | second = Number(main.pop()); 260 | first = Number(main.pop()); 261 | main.push((first") 264 | { 265 | second = Number(main.pop()); 266 | first = Number(main.pop()); 267 | console.log(first, second, first > second, Number(FORTH_TRUE), "f"); 268 | main.push((first>second)?Number(FORTH_TRUE):FORTH_FALSE); 269 | } 270 | else if (token == ">=") 271 | { 272 | second = Number(main.pop()); 273 | first = Number(main.pop()); 274 | main.push((first>=second)?Number(FORTH_TRUE):FORTH_FALSE); 275 | } 276 | else if (token == "<=") 277 | { 278 | second = Number(main.pop()); 279 | first = Number(main.pop()); 280 | main.push((first<=second)?Number(FORTH_TRUE):FORTH_FALSE); 281 | } 282 | else if (token == "=") 283 | { 284 | second = Number(main.pop()); 285 | first = Number(main.pop()); 286 | main.push((first==second)?Number(FORTH_TRUE):FORTH_FALSE); 287 | } else if (token == "!=") 288 | { 289 | second = Number(main.pop()); 290 | first = Number(main.pop()); 291 | main.push((first!=second)?Number(FORTH_TRUE):FORTH_FALSE); 292 | } else if (token == "do") 293 | { 294 | var rest = Array(); 295 | var func_def = Array(); 296 | var index = main.pop(); 297 | var iterations = main.pop(); 298 | IN_DEFINITION = true; 299 | for (i++; i modified"; 343 | return " created"; 344 | } 345 | else { 346 | FORTH_ERROR = BAD_DEF_NAME; 347 | FORT_ERROR_MESSAGE = "Definition must begin with a letter."; 348 | } 349 | i++; 350 | } 351 | else if ((token in window.user_def) && !IN_DEFINITION) // !IN_DEFINITION allows recursion 352 | { 353 | var def = window.user_def[token]; 354 | var rest = Array(); 355 | for (i++;i < tokens.length;i++) // gather up remaining tokens 356 | { 357 | rest.push(tokens[i]); 358 | } 359 | if (FORTH_DEBUG) 360 | { 361 | console.log("recursive_def: "+window.user_def[token]); 362 | console.log(main.join(" ")); 363 | } 364 | interpret(def); 365 | if (rest.length) 366 | interpret(rest.join(" "));// interpret any remaining tokens 367 | } else if (token == "clear") 368 | { 369 | main = []; 370 | } 371 | else { 372 | FORTH_ERROR = CMD_NOT_FOUND; 373 | if (token == "") 374 | token = "null"; 375 | FORTH_ERROR_MESSAGE = " not found"; 376 | } 377 | } 378 | } 379 | } 380 | } 381 | } 382 | 383 | function displayPrompt(result) { 384 | terminal = window.terminal; 385 | if (!result) 386 | result = ""; 387 | terminal.value += result + FORTH_PROMPT; 388 | terminal.focus(); 389 | } 390 | 391 | function setKeyPressAction(terminal) { 392 | function get_line() { 393 | var lines = terminal.value.split("\n"); 394 | var line = lines[lines.length - 1]; 395 | return line; 396 | } 397 | 398 | 399 | terminal.onkeydown = function(e) { 400 | if (e.keyCode == 13) { 401 | var input = terminal.value.split("\n"); 402 | var last_line = input[input.length - 1].slice(FORTH_PROMPT.length-1); 403 | RECUR_COUNT = 0; 404 | var result = interpret(last_line); 405 | if (printBuffer.length) printBuffer.push(" "); 406 | if (FORTH_ERROR == "") { 407 | if (result) 408 | result += " "; 409 | else 410 | result = ""; 411 | if (terminal.value !== "") 412 | displayPrompt("\n " + printBuffer.join("") + result + FORTH_OK); 413 | else // clear screen 414 | terminal.value = ">>> "; 415 | } else { 416 | displayPrompt("\n"); 417 | FORTH_ERROR = ""; 418 | FORTH_ERROR_MESSAGE = ""; 419 | } 420 | window.setTimeout(function() { 421 | val = terminal.value.split(""); 422 | terminal.value = val.splice(0, val.length - 1).join(""); 423 | }, 1); 424 | } else if (e.keyCode == 8 || e.keyCode == 46) { 425 | if (get_line().length < FORTH_PROMPT.length) { 426 | terminal.value += " "; 427 | } 428 | } 429 | printBuffer = []; 430 | }; 431 | } 432 | 433 | function init_interpreter() { 434 | terminal = window.terminal; 435 | /* 436 | * Set interpreter style settings. 437 | */ 438 | terminal.setAttribute("style", "width:100%;height:100%;position:absolute;left:0;right:0;top:0;bottom:0;background-color:black;color:red;font-size:20px;font-family:\"Courier New\""); 439 | terminal.setAttribute("resize", "none"); 440 | terminal.setAttribute("spellcheck", "false"); 441 | terminal.focus(); 442 | terminal.value = FORTH_TEXT; 443 | 444 | displayPrompt(); 445 | 446 | setKeyPressAction(terminal); 447 | } 448 | 449 | function init_env() { 450 | window.terminal = document.getElementById("interpreter"); 451 | window.main = []; 452 | window.printBuffer = []; 453 | } 454 | 455 | window.onload = function() { 456 | init_env(); 457 | init_interpreter(); 458 | }; 459 | --------------------------------------------------------------------------------