├── 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 |
--------------------------------------------------------------------------------