├── examples ├── addition.bytes ├── variable.bytes ├── addition.fs ├── variable.fs ├── power.bytes ├── code.bytes ├── euler001.bytes ├── power.fs ├── code.fs └── euler001.fs ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── erlang_compiler.md ├── README.md ├── opcodes.md ├── speed.md ├── compiler.erl └── src └── vm.rs /examples/addition.bytes: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /examples/variable.bytes: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.beam 3 | #* 4 | .#* -------------------------------------------------------------------------------- /examples/addition.fs: -------------------------------------------------------------------------------- 1 | pushn 5 pushn 5 pushn 1 + * print -------------------------------------------------------------------------------- /examples/variable.fs: -------------------------------------------------------------------------------- 1 | pushn 4 pushn 5 ! pushn 5 @ print -------------------------------------------------------------------------------- /examples/power.bytes: -------------------------------------------------------------------------------- 1 |  2 |  3 |  -------------------------------------------------------------------------------- /examples/code.bytes: -------------------------------------------------------------------------------- 1 |  2 |  3 |  -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "forth-rs" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /examples/euler001.bytes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zack-bitcoin/forth-in-rust/HEAD/examples/euler001.bytes -------------------------------------------------------------------------------- /examples/power.fs: -------------------------------------------------------------------------------- 1 | : power dup pushn 0 == if drop else >r pushn 2 * r> pushn 1 - pushn recurse call then ; 2 | pushn 2 pushn 10 pushn power call print -------------------------------------------------------------------------------- /examples/code.fs: -------------------------------------------------------------------------------- 1 | : fib dup pushn 0 == if drop else pushn 1 - >r dup rot + r> pushn recurse call then ; 2 | 3 | pushn 1 pushn 1 pushn 20 pushn fib call print -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "forth-rs" 3 | version = "0.1.0" 4 | authors = ["zack "] 5 | 6 | [dependencies] 7 | 8 | [[bin]] 9 | name = "vm" 10 | -------------------------------------------------------------------------------- /examples/euler001.fs: -------------------------------------------------------------------------------- 1 | ( If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. 2 | Find the sum of all the multiples of 3 or 5 below 1000. ) 3 | 4 | : g if r@ + else then ; 5 | 6 | : f r@ pushn 0 == if else 7 | r@ pushn 3 % pushn 0 == 8 | r@ pushn 5 % pushn 0 == 9 | or pushn g call 10 | r> pushn 1 - >r pushn recurse call then ; 11 | push1 231 3 0 0 >r pushn 0 pushn f call print 12 | ( pushn 9 >r pushn 0 pushn f call print ) 13 | -------------------------------------------------------------------------------- /erlang_compiler.md: -------------------------------------------------------------------------------- 1 | To compile the code, open erlang interpreter with `erl` 2 | Now you can compile the compiler. It was written in erlang. 3 | 4 | ``` 5 | 1> c(compiler). 6 | {ok,compiler) 7 | ``` 8 | 9 | Now when the compiler is compiled, you can use it to compile forth code, like the "power.fs" file. 10 | After it is compiled, erlang uses "vm" to run the code, and displays the answer. 11 | 12 | ``` 13 | 2> compiler:doit("examples/power.fs", "examples/power.bytes"). 14 | 2048 15 | ok 16 | 3> 17 | ``` 18 | 19 | There are a [couple examples](examples) of forth code that compiles correctly -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Forth In Rust 2 | 3 | An implementation of the forth programming language written in rust. 4 | 5 | [The opcodes are documented](opcodes.md) 6 | 7 | The [forth compiler I wrote in erlang from a previous project](erlang_compiler.md). All the compiled code is provided, you don't need erlang to test the vm. 8 | 9 | You can compile the vm with cargo like this: 10 | 11 | ```sh 12 | $ cargo build --release 13 | ``` 14 | 15 | There are a [couple examples](examples) of compiled forth code with suffix ".bytes" 16 | 17 | Try running one like this: 18 | 19 | ``` 20 | ./target/release/vm examples/euler001.bytes 21 | ``` 22 | 23 | 24 | -------------------------------------------------------------------------------- /opcodes.md: -------------------------------------------------------------------------------- 1 | Byte means 32 bits. 4 nibbles per byte. 2 | 3 | Opcode number, symbol, what it does: 4 | 5 | - 0 `print`: prints the first few things on the stack. 6 | - 1 `+` `( A B -- C )` 7 | - 2 `*` `( A B -- C )` 8 | - 3 `-` `( A B -- C )` 9 | - 4 `/` `( A B -- C )` 10 | - 5 `%` `( A B -- C )` 11 | - 6 `>r` `( A -- )`: moves the top of the stack to the alt stack 12 | - 7 `r>` `( -- A )`: moves the top of the alt stack to the stack 13 | - 8 `!` `( A B -- )`: stores the value A under name B. 14 | - 9 `@` `( B -- )`: recalls a value from memory 15 | - 10 `dup` `( A -- A A )` 16 | - 11 `swap` `( A B -- B A )` 17 | - 12 `rot` `( A B C -- B C A )` 18 | - 13 `tuck` `( A B C -- C B A )` 19 | - 14 `2dup` `( A B -- A B A B )` 20 | - 15 `2swap` `( A B C D -- C D A B)` 21 | - 16 `:`: starts defining a new word. 22 | - 17 `;`: finishes a word definition. 23 | - 18 `recurse`: puts the address of the current function to the top of the stack. 24 | - 19 `call`: calls the function on the top of the stack. 25 | - 20 `push` `( N -- )`: pushes N bytes to stack 26 | - 21 `pushn`: 1 nibble to stack, store it as a byte 27 | - 22 push 1 byte to stack 28 | - 23 push 2 bytes to stack 29 | - 24 push 3 bytes to stack 30 | - 25 `if` 31 | - 26 `else` 32 | - 27 `then` 33 | - 28 `==` `( X Y -- T )` 34 | - 29 `>` `( X Y -- T )` 35 | - 30 `<` `( X Y -- T )` 36 | - 31 `drop` `( A -- )` 37 | - 32 `finish`: stop the program and exit. 38 | - 33 `r@` `( -- A )`: copy the top of the alt stack onto the stack 39 | - 34 `or` `( A B -- C )`: logical or 40 | - 35 `and` `( A B -- C )`: logical and 41 | -------------------------------------------------------------------------------- /speed.md: -------------------------------------------------------------------------------- 1 | The rust one was built for speed. The erlang one was built to be the virtual machine for a cryptocurrency. I expect it to be at least 2x slower because of the cryptocurrency features it supports. 2 | 3 | measuring speed for rust version 4 | 5 | zack@iloSona:~/Hacking/rust/forth$ time ./target/release/vm 6 | 233168 7 | 8 | real 0m0.007s 9 | user 0m0.004s 10 | sys 0m0.000s 11 | 12 | 13 | measureing speed for erlang version 14 | 15 | 4> {ok, A} = file:read_file("src/vm/euler_problems/001.fs"). 16 | {ok,<<"( If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of"...>>} 17 | 5> B = compiler:compiler(A). 18 | ** exception error: undefined function compiler:compiler/1 19 | 6> B = compiler:compile(A). 20 | [36,40,11,39, 21 | {integer,0}, 22 | 35,17,18,40,11,39, 23 | {integer,3}, 24 | 43, 25 | {integer,0}, 26 | 35,40,11,39, 27 | {integer,5}, 28 | 43, 29 | {integer,0}, 30 | 35,21,17,40,11,39,2,18|...] 31 | 7> timer:tc(language, run [B, 1000000000]). 32 | * 1: syntax error before: '[' 33 | 7> timer:tc(language, run, [B, 1000000000]). 34 | {24900,[233168]} 35 | 36 | 37 | erlang is measuring microseconds, so that is the same as 38 | 0.0249 seconds. 39 | 40 | The rust version was 41 | 0.004 42 | 43 | So the rust version is 6.25 times faster at solving this problem. 44 | 45 | The problem being solved was the first project euler problem which is: 46 | If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. 47 | -> Find the sum of all the multiples of 3 or 5 below 1000. 48 | 49 | The code for the forth vm written in erlang looked like this: 50 | 51 | ``` 52 | : f r@ integer 0 == if else 53 | r@ integer 3 rem integer 0 == 54 | r@ integer 5 rem integer 0 == 55 | or if r@ + else then 56 | r> integer 1 - >r recurse call 57 | then ; 58 | integer 999 >r integer 0 f call 59 | ``` 60 | 61 | The code for the forth vm written in rust looked like this: 62 | 63 | ``` 64 | : g if r@ + else then ; 65 | 66 | : f r@ pushn 0 == if else 67 | r@ pushn 3 % pushn 0 == 68 | r@ pushn 5 % pushn 0 == 69 | or pushn g call 70 | r> pushn 1 - >r pushn recurse call then ; 71 | push1 231 3 0 0 >r pushn 0 pushn f call print 72 | ( pushn 9 >r pushn 0 pushn f call print ) 73 | ``` -------------------------------------------------------------------------------- /compiler.erl: -------------------------------------------------------------------------------- 1 | -module(compiler). 2 | -export([doit/1, test/0]). 3 | 4 | -define(out, "compiled.bytes"). 5 | 6 | doit(X) -> doit(X, ?out). 7 | doit(X, Y) -> 8 | {ok, A} = file:read_file(X), 9 | B = compile(A), 10 | file:write_file(Y, B). 11 | %io:fwrite(os:cmd("./target/release/vm")). 12 | test() -> doit("examples/power.fs"). 13 | compile(A) -> 14 | B = << <<" ">>/binary, A/binary, <<" \n">>/binary>>, 15 | C = remove_comments(B), 16 | D = add_spaces(C), 17 | Words = to_words(D, <<>>, []), 18 | Words2 = numberify(Words), 19 | Macros = get_macros(Words2), 20 | YWords = remove_macros(Words2), 21 | ZWords = apply_macros(Macros, YWords), 22 | Functions = get_functions(ZWords), 23 | AWords = remove_functions(ZWords), 24 | %BWords = apply_functions(AWords, Functions), 25 | reuse_name_check(Macros, Functions), 26 | X = to_opcodes(AWords, Functions, []), 27 | to_bytes(X). 28 | to_bytes(X) -> to_bytes(flip(X), <<>>). 29 | to_bytes([], X) -> X; 30 | to_bytes([H|T], X) -> 31 | to_bytes(T, <>). 32 | numberify(X) -> numberify(X, []). 33 | numberify([], X) -> flip(X); 34 | numberify([H|T], O) -> 35 | case re:run(H, "^[0-9]*$") of 36 | nomatch -> numberify(T, [H|O]); 37 | {match, _} -> numberify(T, [binary_to_integer(H)|O]) 38 | end. 39 | reuse_name_check(Macros, Functions) -> 40 | MacroKeys = dict:fetch_keys(Macros), 41 | FunctionKeys = dict:fetch_keys(Functions), 42 | L = repeats(MacroKeys ++ FunctionKeys), 43 | Bool = 0 == length(L), 44 | if 45 | Bool -> ok; 46 | true -> io:fwrite("error. you reused a name more than once."), 47 | io:fwrite(packer:pack(L)), 48 | Bool == true 49 | end. 50 | repeats([]) -> []; 51 | repeats([H|T]) -> 52 | B = is_in(H, T), 53 | if 54 | B -> [H|repeats(T)]; 55 | true -> repeats(T) 56 | end. 57 | is_in(_, []) -> false; 58 | is_in(A, [A|_]) -> true; 59 | is_in(A, [_|T]) -> is_in(A, T). 60 | add_spaces(B) -> add_spaces(B, <<"">>). 61 | add_spaces(<<"">>, B) -> B; 62 | add_spaces(<<58:8, B/binary >>, Out) -> % ":" 63 | add_spaces(B, <>); 64 | add_spaces(<<59:8, B/binary >>, Out) -> % ";" 65 | add_spaces(B, <>); 66 | add_spaces(<<44:8, B/binary >>, Out) -> % "," 67 | add_spaces(B, <>); 68 | add_spaces(<>, Out) -> 69 | add_spaces(B, <>). 70 | remove_comments(B) -> remove_comments(B, <<"">>). 71 | remove_comments(<<"">>, Out) -> Out; 72 | remove_comments(<<40:8, B/binary >>, Out) -> % [40] == "(". 73 | C = remove_till(41, B), % [41] == ")". 74 | remove_comments(C, Out); 75 | remove_comments(<<59:8, B/binary >>, Out) -> % [59] == ";". 76 | C = remove_till(10, B), 77 | remove_comments(C, <>); 78 | remove_comments(<>, Out) -> 79 | remove_comments(B, <>). 80 | remove_till(N, <>) -> B; 81 | remove_till(N, <<_:8, B/binary>>) -> 82 | remove_till(N, B). 83 | remove_macros(Words) -> remove_macros(Words, []). 84 | remove_macros([], Out) -> Out; 85 | remove_macros([<<"macro">>|Words], Out) -> 86 | {_, B} = split(<<";">>, Words), 87 | remove_macros(B, Out); 88 | remove_macros([W|Words], Out) -> 89 | remove_macros(Words, Out ++ [W]). 90 | apply_macros(Macros, Words) -> apply_macros(Macros, Words, []). 91 | apply_macros(_, [], Out) -> Out; 92 | apply_macros(Macros, [W|Words], Out) -> 93 | NOut = case dict:find(W, Macros) of 94 | error -> Out ++ [W]; 95 | {ok, Val} -> Out ++ Val 96 | end, 97 | apply_macros(Macros, Words, NOut). 98 | get_macros(Words) -> 99 | get_macros(Words, dict:new()). 100 | get_macros([<<"macro">>|[Name|R]], Functions) -> 101 | case dict:find(Name, Functions) of 102 | error -> 103 | {Code, T} = split(<<";">>, R), 104 | Code2 = apply_macros(Functions, Code), 105 | NewFunctions = dict:store(Name, Code2, Functions), 106 | get_macros(T, NewFunctions); 107 | {X, _} -> 108 | io:fwrite("can't name 2 macros the same. reused name: "), 109 | io:fwrite(Name), 110 | io:fwrite("\n"), 111 | X = okay 112 | end; 113 | get_macros([], Functions) -> Functions; 114 | get_macros([_|T], Functions) -> get_macros(T, Functions). 115 | get_functions3(R, Name, Functions, ID) -> 116 | case dict:find(Name, Functions) of 117 | error -> 118 | NewFunctions = dict:store(Name, ID, Functions), 119 | get_functions(R, NewFunctions, ID + 1); 120 | {X, _} -> 121 | io:fwrite("can't name 2 functions the same. reused name: "), 122 | io:fwrite(Name), 123 | io:fwrite("\n"), 124 | X = okay 125 | end. 126 | get_functions(Words) -> get_functions(Words, dict:new(), 0). 127 | get_functions([<<":">>|[Name|R]], Functions, ID) -> 128 | %Make sure Name isn't on the restricted list. 129 | {_Code, T} = split(<<";">>, R), 130 | get_functions3(T, Name, Functions, ID); 131 | get_functions([], Functions, _) -> Functions; 132 | get_functions([_|T], Functions, ID) -> get_functions(T, Functions, ID). 133 | split(C, B) -> split(C, B, []). 134 | split(C, [C|B], Out) -> {flip(Out), B}; 135 | split(C, [D|B], Out) -> 136 | split(C, B, [D|Out]). 137 | remove_functions(Words) -> rad(Words, []). 138 | rad([], Out) -> flip(Out); 139 | rad([<<":">>|T], Out) -> rad(T, [<<":">>|Out]); 140 | rad([<<"macroSign">>|[_|[<<"binary">>|[_|[<<"binary">>|[_|T]]]]]], Out) -> rad(T, Out); 141 | rad([<<"macroSign">>|[_|[<<"binary">>|[_|[_|T]]]]], Out) -> rad(T, Out); 142 | rad([X|T], Out) -> rad(T, [X|Out]). 143 | to_opcodes([<<"print">>|R], F, Out) -> 144 | to_opcodes(R, F, [0|Out]); 145 | to_opcodes([<<"+">>|R], F, Out) -> 146 | to_opcodes(R, F, [1|Out]); 147 | to_opcodes([<<"*">>|R], F, Out) -> 148 | to_opcodes(R, F, [2|Out]); 149 | to_opcodes([<<"-">>|R], F, Out) -> 150 | to_opcodes(R, F, [3|Out]); 151 | to_opcodes([<<"/">>|R], F, Out) -> 152 | to_opcodes(R, F, [4|Out]); 153 | to_opcodes([<<"%">>|R], F, Out) -> 154 | to_opcodes(R, F, [5|Out]); 155 | to_opcodes([<<">r">>|R], F, Out) -> 156 | to_opcodes(R, F, [6|Out]); 157 | to_opcodes([<<"r>">>|R], F, Out) -> 158 | to_opcodes(R, F, [7|Out]); 159 | to_opcodes([<<"!">>|R], F, Out) -> 160 | to_opcodes(R, F, [8|Out]); 161 | to_opcodes([<<"@">>|R], F, Out) -> 162 | to_opcodes(R, F, [9|Out]); 163 | to_opcodes([<<"dup">>|R], F, Out) -> 164 | to_opcodes(R, F, [10|Out]); 165 | to_opcodes([<<"swap">>|R], F, Out) -> 166 | to_opcodes(R, F, [11|Out]); 167 | to_opcodes([<<"rot">>|R], F, Out) -> 168 | to_opcodes(R, F, [12|Out]); 169 | to_opcodes([<<"tuck">>|R], F, Out) -> 170 | to_opcodes(R, F, [13|Out]); 171 | to_opcodes([<<"2dup">>|R], F, Out) -> 172 | to_opcodes(R, F, [14|Out]); 173 | to_opcodes([<<"2swap">>|R], F, Out) -> 174 | to_opcodes(R, F, [15|Out]); 175 | to_opcodes([<<":">>|R], F, Out) -> 176 | to_opcodes(R, F, [16|Out]); 177 | to_opcodes([<<";">>|R], F, Out) -> 178 | to_opcodes(R, F, [17|Out]); 179 | to_opcodes([<<"recurse">>|R], F, Out) -> 180 | to_opcodes(R, F, [18|Out]); 181 | to_opcodes([<<"call">>|R], F, Out) -> 182 | to_opcodes(R, F, [19|Out]); 183 | to_opcodes([<<"push">>|R], F, Out) -> 184 | to_opcodes(R, F, [20|Out]); 185 | to_opcodes([<<"pushn">>|R], F, Out) -> 186 | to_opcodes(R, F, [21|Out]); 187 | to_opcodes([<<"push1">>|R], F, Out) -> 188 | to_opcodes(R, F, [22|Out]); 189 | to_opcodes([<<"push2">>|R], F, Out) -> 190 | to_opcodes(R, F, [23|Out]); 191 | to_opcodes([<<"push3">>|R], F, Out) -> 192 | to_opcodes(R, F, [24|Out]); 193 | to_opcodes([<<"if">>|R], F, Out) -> 194 | to_opcodes(R, F, [25|Out]); 195 | to_opcodes([<<"else">>|R], F, Out) -> 196 | to_opcodes(R, F, [26|Out]); 197 | to_opcodes([<<"then">>|R], F, Out) -> 198 | to_opcodes(R, F, [27|Out]); 199 | to_opcodes([<<"==">>|R], F, Out) -> 200 | to_opcodes(R, F, [28|Out]); 201 | to_opcodes([<<">">>|R], F, Out) -> 202 | to_opcodes(R, F, [29|Out]); 203 | to_opcodes([<<"<">>|R], F, Out) -> 204 | to_opcodes(R, F, [30|Out]); 205 | to_opcodes([<<"drop">>|R], F, Out) -> 206 | to_opcodes(R, F, [31|Out]); 207 | to_opcodes([<<"stop">>|R], F, Out) -> 208 | to_opcodes(R, F, [32|Out]); 209 | to_opcodes([<<"r@">>|R], F, Out) -> 210 | to_opcodes(R, F, [33|Out]); 211 | to_opcodes([<<"or">>|R], F, Out) -> 212 | to_opcodes(R, F, [34|Out]); 213 | to_opcodes([<<"and">>|R], F, Out) -> 214 | to_opcodes(R, F, [35|Out]); 215 | to_opcodes([<<"xor">>|R], F, Out) -> 216 | to_opcodes(R, F, [36|Out]); 217 | to_opcodes([I|R], F, Out) when (is_integer(I) and (I > -1)) -> 218 | to_opcodes(R, F, [I|Out]); 219 | to_opcodes([], _, Out) -> flip(Out); 220 | to_opcodes([Name|R], Functions, Out) -> 221 | case dict:find(Name, Functions) of 222 | error -> 223 | io:fwrite("undefined word "), 224 | io:fwrite(integer_to_list(Name)),%looking up a hash. 225 | io:fwrite("\n"); 226 | {ok, Val} -> 227 | to_opcodes(R, Functions, [Val|Out]) 228 | end. 229 | 230 | to_words(<<>>, <<>>, Out) -> flip(Out); 231 | to_words(<<>>, N, Out) -> flip([flip_bin(N)|Out]); 232 | to_words(<<"\t", B/binary>>, X, Out) -> 233 | to_words(B, X, Out); 234 | to_words(<<" ", B/binary>>, <<"">>, Out) -> 235 | to_words(B, <<>>, Out); 236 | to_words(<<"\n", B/binary>>, <<"">>, Out) -> 237 | to_words(B, <<>>, Out); 238 | to_words(<<" ", B/binary>>, N, Out) -> 239 | to_words(B, <<>>, [flip_bin(N)|Out]); 240 | to_words(<<"\n", B/binary>>, N, Out) -> 241 | to_words(B, <<>>, [flip_bin(N)|Out]); 242 | to_words(<>, N, Out) -> 243 | to_words(B, <>, Out). 244 | flip_bin(B) -> flip_bin(B, <<>>). 245 | flip_bin(<<>>, Out) -> Out; 246 | flip_bin(<>, Out) -> 247 | flip_bin(B, <>). 248 | flip(X) -> flip(X, []). 249 | flip([], Out) -> Out; 250 | flip([H|T], Out) -> flip(T, [H|Out]). 251 | 252 | 253 | -------------------------------------------------------------------------------- /src/vm.rs: -------------------------------------------------------------------------------- 1 | //use std::io; 2 | use std::collections::BTreeMap; 3 | use std::io::prelude::*; 4 | use std::io; 5 | use std::fs::File; 6 | use std::env; 7 | use std::process::exit; 8 | 9 | macro_rules! die { 10 | ($($tok:tt)+) => {{ 11 | let stderr = io::stderr(); 12 | let mut stderr = stderr.lock(); 13 | let _ = writeln!(stderr, $($tok)+); 14 | exit(1) 15 | }} 16 | } 17 | 18 | fn main() { 19 | let mut args = env::args_os().skip(1); 20 | let path = match args.next() { 21 | Some(p) => p, 22 | None => die!("no filename specified"), 23 | }; 24 | let remaining_args = args.count(); 25 | if remaining_args > 0 { 26 | die!("expected one argument, found {}", remaining_args); 27 | } 28 | let mut file = File::open(&path).unwrap_or_else(|err| die!("{}", err)); 29 | let mut buffer = Vec::with_capacity(file.metadata().map(|m|m.len()).unwrap_or(0) as usize); 30 | file.read_to_end(&mut buffer).unwrap_or_else(|err| die!("{}", err)); 31 | forth(buffer); 32 | } 33 | 34 | fn forth(mut code: Vec) -> Vec { 35 | // In-place reversal 36 | code.reverse(); 37 | 38 | // Pre-allocate some space. Keeps short programs with small stacks from 39 | // spending time up front repeatedly re-allocating the stacks. 40 | let mut stack: Vec = Vec::with_capacity(32); 41 | let mut alt_stack: Vec = Vec::with_capacity(32); 42 | 43 | // Could also use a HashMap but BTreeMaps tend to be faster smaller tables. 44 | // If we had more guarantees about where variables could be written, 45 | // variable lookup could be significantly faster. 46 | let mut variables: BTreeMap = BTreeMap::new(); 47 | 48 | // To avoid allocating every time we define a function, store them all in 49 | // the same Vec, terminate them with a 17 byte (can't appear in function 50 | // definitions), and put a pointer to them in function_table. 51 | 52 | // We could put this on the stack but it's a bit large and a single large 53 | // allocation isn't too expensive. 54 | let mut function_table: Vec = vec![0; 256]; 55 | let mut function_code: Vec = Vec::new(); 56 | 57 | // NOTE: this could be significantly faster if we loosened the stack 58 | // abstraction a bit but forth really is all about stacks. 59 | 60 | while let Some(op) = code.pop() { 61 | match op { 62 | //print 63 | 0u8 => { 64 | // Take the top three items from the stack. 65 | for &b in stack.iter().rev().take(3) { 66 | println!("{}", b); 67 | } 68 | } 69 | // + 70 | 1u8 => { 71 | let b = stack.pop().unwrap(); 72 | let d = stack.pop().unwrap(); 73 | stack.push(b+d); 74 | } 75 | // * 76 | 2u8 => { 77 | let b = stack.pop().unwrap(); 78 | let d = stack.pop().unwrap(); 79 | stack.push(b*d); 80 | }, 81 | // - 82 | 3u8 => { 83 | let b = stack.pop().unwrap(); 84 | let d = stack.pop().unwrap(); 85 | stack.push(d-b); 86 | } 87 | 4u8 => { 88 | let b = stack.pop().unwrap(); 89 | let d = stack.pop().unwrap(); 90 | stack.push(b/d); 91 | } 92 | // % 93 | 5u8 => { 94 | let b = stack.pop().unwrap(); 95 | let d = stack.pop().unwrap(); 96 | stack.push( d % b ); 97 | } 98 | // >r 99 | 6u8 => { 100 | let b = stack.pop().unwrap(); 101 | alt_stack.push(b); 102 | } 103 | // r> 104 | 7u8 => { 105 | let b = alt_stack.pop().unwrap(); 106 | stack.push(b); 107 | } 108 | // ! (store value in variable) 109 | 8u8 => { 110 | let name = stack.pop().unwrap(); 111 | let value = stack.pop().unwrap(); 112 | variables.insert(name, value); 113 | } 114 | // @ (get) 115 | 9u8 => { 116 | let name = stack.pop().unwrap(); 117 | let value = *variables.get(&name).unwrap_or(&0); 118 | stack.push(value); 119 | } 120 | //dup 121 | 10u8 => { 122 | let ab = stack.pop().unwrap(); 123 | stack.push(ab); 124 | stack.push(ab); 125 | } 126 | // swap 127 | 11u8 => { 128 | let y = stack.pop().unwrap(); 129 | let u = stack.pop().unwrap(); 130 | stack.push(y); 131 | stack.push(u); 132 | } 133 | //rot 134 | 12u8 => { 135 | let x = stack.pop().unwrap(); 136 | let y = stack.pop().unwrap(); 137 | let z = stack.pop().unwrap(); 138 | stack.push(y); 139 | stack.push(z); 140 | stack.push(x); 141 | }, 142 | // tuck 143 | 13u8 => { 144 | let x = stack.pop().unwrap(); 145 | let y = stack.pop().unwrap(); 146 | let z = stack.pop().unwrap(); 147 | stack.push(z); 148 | stack.push(x); 149 | stack.push(y); 150 | }, 151 | // 2dup 152 | 14u8 => { 153 | let x = stack.pop().unwrap(); 154 | let y = stack.pop().unwrap(); 155 | stack.push(y); 156 | stack.push(x); 157 | stack.push(y); 158 | stack.push(x); 159 | } 160 | // 2swap 161 | 15u8 => { 162 | let x = stack.pop().unwrap(); 163 | let y = stack.pop().unwrap(); 164 | let x2 = stack.pop().unwrap(); 165 | let y2 = stack.pop().unwrap(); 166 | stack.push(y); 167 | stack.push(x); 168 | stack.push(y2); 169 | stack.push(x2); 170 | } 171 | // : (define function) 172 | 16u8 => { 173 | let name = code.pop().unwrap(); 174 | function_code.push(17u8); 175 | loop { 176 | match code.pop().expect("unterminated function") { 177 | 17u8 => break, 178 | 18u8 => function_code.push(name), 179 | op => function_code.push(op), 180 | } 181 | } 182 | function_table[name as usize] = function_code.len(); 183 | } 184 | 17u8|18u8 => unreachable!(), 185 | // call 186 | 19u8 => { 187 | let name = stack.pop().unwrap(); 188 | let function_start = function_table[name as usize]; 189 | assert!(function_start != 0, "attempted to call undefined function"); 190 | for &byte in function_code[..function_start].iter().rev() { 191 | match byte { 192 | 17u8 => break, 193 | _ => code.push(byte), 194 | } 195 | } 196 | } 197 | // push 198 | 20u8 => { 199 | let b = stack.pop().unwrap(); 200 | for _ in 0..b { 201 | let d = code.pop().unwrap(); 202 | stack.push(d as u32); 203 | } 204 | } 205 | //pushn 206 | 21u8 => { 207 | let y = code.pop().unwrap(); 208 | stack.push(y as u32); 209 | } 210 | //push1..3 211 | 22u8|23u8|24u8 => { 212 | let count = op - 21; 213 | for _ in 0..count { 214 | let z = code.pop().unwrap() as u32; 215 | let b = code.pop().unwrap() as u32; 216 | let e = code.pop().unwrap() as u32; 217 | let d = code.pop().unwrap() as u32; 218 | let y = (d << 24) | (e << 16) | (b << 8) | z; 219 | stack.push(y); 220 | } 221 | } 222 | // if 223 | 25u8 => { 224 | let y = stack.pop().unwrap(); 225 | if y == 0 { 226 | // skip to else. 227 | while code.pop().unwrap() != 26 { } 228 | } 229 | } 230 | // skip over else 231 | 26u8 => while code.pop().unwrap() != 27 { }, 232 | // endif 233 | 27u8 => {}, 234 | // == 235 | 28u8 => { 236 | let y = stack.pop().unwrap(); 237 | let z = stack.pop().unwrap(); 238 | stack.push((z == y) as u32); 239 | } 240 | // > 241 | 29u8 => { 242 | let y = stack.pop().unwrap(); 243 | let z = stack.pop().unwrap(); 244 | stack.push((z > y) as u32); 245 | } 246 | // < 247 | 30u8 => { 248 | let y = stack.pop().unwrap(); 249 | let z = stack.pop().unwrap(); 250 | stack.push((z < y) as u32); 251 | } 252 | // drop 253 | 31u8 => { 254 | stack.pop().unwrap(); 255 | } 256 | // Stop 257 | 32u8 => break, 258 | // r@ 259 | 33u8 => { 260 | let z = alt_stack.pop().unwrap(); 261 | stack.push(z); 262 | alt_stack.push(z); 263 | } 264 | // or 265 | 34u8 => { 266 | let z = stack.pop().unwrap(); 267 | let y = stack.pop().unwrap(); 268 | stack.push((z != 0 || y != 0) as u32) 269 | } 270 | // and 271 | 35u8 => { 272 | let z = stack.pop().unwrap(); 273 | let y = stack.pop().unwrap(); 274 | stack.push((z != 0u32 && y != 0u32) as u32); 275 | } 276 | _ => panic!("unknown op code {}", op), 277 | } 278 | } 279 | stack 280 | } 281 | --------------------------------------------------------------------------------