colorful rat Ratfactor.com > Dave's Repos

snobol4th

A Forth written in Snobol4
git clone http://ratfactor.com/repos/snobol4th/snobol4th.git

snobol4th/forth.sno

Download raw file: forth.sno

1 * Snobol4th - a Forth written in Snobol4 2 * Dave Gauer 2024 - ratfactor.com 3 4 * Data Stack 5 6 * Data stack and data head index 7 data = array(10,null) 8 * Note that "data head" always points to the 9 * top value and 0 is blank. 10 dh = 0 11 12 * Compile Mode Flag 13 compile_mode = null 14 compiling_name = '' 15 16 * Input state 17 dict_pos = 0 18 stdin_pos = 0 19 stdin_line = '' 20 readfrom = 'stdin_line' 21 storepos = 'stdin_pos' 22 23 * Infinite loop prevention while experimenting: 24 * max_tokens = 4000 25 * token_count = 0 26 27 28 * Dictionary 29 * ************************************* 30 * dtab entries point to positions in 31 * the dict string (dictionary storage) 32 dict = '' 33 dtab = table() 34 35 * (Test for dictionary entries with 36 * differ(dtab['no-exist']) :f(nodict) 37 * |n|_| - native 38 * |_|i| - immediate 39 * |_|_|55 - 'compiled' word position in dict 40 * |_|_|dup - native function to call 41 dtab['.'] = 'n_' 'dot' 42 dtab['."'] = 'ni' 'dotquote' 43 dtab['.S'] = 'n_' 'dotstack' 44 dtab['"'] = 'n_' 'quote' 45 dtab['SPACE'] = 'n_' 'space' 46 dtab['CR'] = 'n_' 'cr' 47 dtab['DUP'] = 'n_' 'dup' 48 dtab['SWAP'] = 'n_' 'swap' 49 dtab['DROP'] = 'n_' 'drop' 50 dtab['+'] = 'n_' 'plus' 51 dtab['-'] = 'n_' 'minus' 52 dtab['1-'] = 'n_' 'oneminus' 53 dtab[':'] = 'n_' 'colon' 54 dtab[';'] = 'ni' 'semicolon' 55 dtab['EXIT'] = 'n_' 'exit' 56 dtab['HERE'] = 'n_' 'here' 57 dtab['('] = 'ni' 'comment' 58 dtab['?DUP'] = 'n_' 'ifdup' 59 dtab['IF'] = 'n_' 'if' 60 dtab['ELSE'] = 'n_' 'else' 61 dtab['THEN'] = 'n_' 'then' 62 dtab['DO'] = 'n_' 'do' 63 dtab['I'] = 'n_' 'i' 64 dtab['LOOP'] = 'n_' 'loop' 65 dtab['+LOOP'] = 'n_' 'plusloop' 66 dtab['-LOOP'] = 'n_' 'minusloop' 67 68 69 * Output line 70 * ************************************* 71 * Since SNOBOL4 wants to print whole lines, let's 72 * collect printed strings and wait for "CR" 73 stdout = '' 74 75 * Return stack 76 * ************************************* 77 * rs stores return positions in the 78 * dictionary string. 79 * 80 * dict: ddddddddddddddd 81 * ^ ^ 82 * stdin: ss|s|ssssssssss 83 * ^ | | 84 * rs = 0 1 2 (rh=3) 85 * 86 * If rh is 0, read from stdin 87 rs = array(10,null) 88 rh = 0 89 90 91 * DUP duplicates head of stack 92 define('dup()') :(enddup) 93 dup 94 x = pop() 95 push(x) 96 push(x) 97 :(return) 98 enddup 99 100 * SWAP duplicates head of stack 101 define('swap()') :(endswap) 102 swap 103 x = pop() 104 y = pop() 105 push(x) 106 push(y) 107 :(return) 108 endswap 109 110 * DROP drops the head item of stack 111 define('drop()') :(enddrop) 112 drop 113 pop() 114 :(return) 115 enddrop 116 117 * '.' (dot) pops head of stack and prints it 118 define('dot()') :(enddot) 119 dot 120 x = pop() 121 stdout = stdout x ' ' 122 :(return) 123 enddot 124 125 * CR prints whatever we've collected in stdout 126 * as a new line. (Snobol4 has no way to print 127 * without printing a whole line.) 128 define('cr()') :(endcr) 129 cr 130 output = stdout 131 stdout = '' 132 :(return) 133 endcr 134 135 * SPACE prints a space character 136 define('space()') :(endspace) 137 space 138 stdout = stdout ' ' 139 :(return) 140 endspace 141 142 * '+' (plus) pops 2 digits, adds them, pushes result 143 define('plus()') :(endplus) 144 plus 145 x = pop() 146 y = pop() 147 push(x + y) 148 :(return) 149 endplus 150 151 * '-' (minus) pops 2 digits, - them, pushes result 152 define('minus()') :(endminus) 153 minus 154 x = pop() 155 y = pop() 156 push(y - x) 157 :(return) 158 endminus 159 160 * '1-' (oneminus) decrements top of stack by one 161 define('oneminus()') :(endoneminus) 162 oneminus 163 x = pop() 164 push(x - 1) 165 :(return) 166 endoneminus 167 168 * ":" (colon) starts compile mode, gathers name 169 define('colon()') :(endcolon) 170 colon 171 compiling_name = get_token() 172 dtab[compiling_name] = '__' size(dict) 173 compile_mode = 1 174 :(return) 175 endcolon 176 177 * ";" (semicolon) starts compile mode, gathers name 178 define('semicolon()') :(endsemicolon) 179 semicolon 180 dict = dict ' EXIT' 181 compile_mode = null 182 :(return) 183 endsemicolon 184 185 * EXIT starts compile mode, gathers name 186 define('exit()') :(endexit) 187 exit 188 rh = rh - 1 189 lt(rh,1) :s(return) 190 dict_pos = rs[rh] 191 :(return) 192 endexit 193 194 * HERE pushes current input pos 195 define('here()') :(endhere) 196 here 197 push(pos) 198 :(return) 199 endhere 200 201 in_comment = 0 202 * '(' is a comment...it throws away input until ')' 203 commentpat = tab(*stdin_pos) break(')') @stdin_pos 204 define('comment()') :(endcomment) 205 comment 206 stdin_line commentpat :f(cget_more) 207 stdin_pos = stdin_pos + 1 208 :(return) 209 cget_more 210 stdin_pos = 0 211 stdin_line = input :f(theend) s(comment) 212 endcomment 213 214 * '.S' prints the parameter stack 215 define('dotstack()') :(enddotstack) 216 dotstack 217 lt(dh,1) :s(printstackempty) 218 i = 0 219 next_si 220 lt(i,dh) :f(return) 221 i = i + 1 222 stdout = stdout data[i] ' ' 223 :(next_si) 224 printstackempty 225 stdout = stdout '<Stack empty>' 226 :(return) 227 enddotstack 228 229 * '."' declares and prints a string! 230 * quotepat = tab(*$storepos) (break('"') @$storepos) . quotestr 231 quotepat = tab(*$storepos) (break('"') @epos) . quotestr 232 define('dotquote()') :(enddotquote) 233 dotquote 234 * output = $readfrom 235 * output = dupl(' ',epos) '^' 236 $readfrom quotepat :f(endquote_notfound) 237 $storepos = epos + 1 238 * output = '"' quotestr '"' 239 * output = 'storing pos ' storepos '=' $storepos 240 eq(1, compile_mode) :s(dotquote_compile) 241 stdout = stdout quotestr 242 :(return) 243 dotquote_compile 244 dict = dict ' ." ' quotestr '"' 245 :(return) 246 endquote_notfound 247 output = "ERROR: No matching end quote." 248 output = $readfrom 249 output = dupl(' ',$storepos) '^' 250 :(end) 251 enddotquote 252 253 * '?DUP' duplicates the top of stack if it's a 'truthy' value 254 define('ifdup()') :(endifdup) 255 ifdup 256 x = data[dh] 257 eq(x,0) :s(return) 258 dup() 259 :(return) 260 endifdup 261 262 * 'IF' skips instructions until THEN when top of stack is false 263 elsethenpat = tab(*dict_pos) arb ('ELSE' | 'THEN') @dict_pos 264 define('if()') :(endif) 265 if 266 * If return stack empty, we are not in a 267 * dictionary definition. 268 lt(rh,1) :s(no_stdin) 269 * If the top of stack value is true, then 270 * start executing the next instruction. 271 eq(0,pop()) :s(if_false) 272 :(return) 273 if_false 274 * If it's false, scan to an ELSE or THEN. 275 dict elsethenpat 276 dictpos = dictpos + 1 277 :(return) 278 no_stdin output = "ERROR: Cannot execute 'IF' outside of a word definition." :(theend) 279 endif 280 281 * 'ELSE' skips instructions until THEN 282 elsepat = tab(*dict_pos) break('THEN') @dict_pos 283 define('else()') :(endelse) 284 else 285 dict elsethenpat 286 dictpos = dictpos + 1 287 :(return) 288 endelse 289 290 * 'THEN' itself is a non-action in this Forth! 291 define('then()') :(endthen) 292 then 293 :(return) 294 endthen 295 296 * DO LOOP, I, and DO +LOOP 297 * This Forth doesn't do nested loops because 298 * it has just one variable for the "I" index. 299 * If desired, this could be fixed with a 300 * stack (traditionally the return stack). 301 302 * 'DO' 303 define('do()') :(enddo) 304 do 305 * If return stack empty, we are not in a 306 * dictionary definition. 307 lt(rh,1) :s(no_stdin2) 308 loop_startpos = dict_pos 309 loop_ivar = pop() 310 loop_endvar = pop() 311 :(return) 312 no_stdin2 output = "ERROR: Cannot execute 'DO...LOOP' outside of a word definition." :(theend) 313 enddo 314 315 * 'i' 316 define('i()') :(endi) 317 i 318 push(loop_ivar) 319 :(return) 320 endi 321 322 * 'loop' 323 define('loop()') :(endloop) 324 loop 325 loop_ivar = loop_ivar + 1 326 le(loop_endvar, loop_ivar) :s(return) 327 dict_pos = loop_startpos 328 :(return) 329 endloop 330 331 * 'plusloop' 332 define('plusloop()') :(endplusloop) 333 plusloop 334 loop_inc = pop() 335 loop_ivar = loop_ivar + loop_inc 336 le(loop_endvar, loop_ivar) :s(return) 337 dict_pos = loop_startpos 338 :(return) 339 endplusloop 340 341 * 'minusloop' 342 define('minusloop()') :(endminusloop) 343 minusloop 344 loop_dec = pop() 345 loop_ivar = loop_ivar - loop_dec 346 ge(loop_endvar, loop_ivar) :s(return) 347 dict_pos = loop_startpos 348 :(return) 349 endminusloop 350 351 352 * Internal functions 353 * ************************************* 354 355 * pop() pops head of stack 356 define('pop()') :(endpop) 357 pop 358 lt(0,dh) :f(popunderflow) 359 pop = data[dh] 360 dh = dh - 1 361 :(return) 362 popunderflow output = 'Stack underflow.' :(theend) 363 endpop 364 365 * push(6) pushes 6 to head of stack 366 define('push(v)') :(endpush) 367 push 368 * output = "push starting with dh=" dh 369 dh = dh + 1 370 * output = 'pushing data[' dh ']:' v 371 data[dh] = v 372 :(return) 373 endpush 374 375 376 377 * Patterns for getting tokens 378 * Note the *p delays the evaluation of cursor position p until pattern used 379 anyspace = (span(' ') | '') 380 wordpat = tab(*pos) anyspace (break(' ') | rem) . token anyspace @pos 381 * get_token() returns next token from input 382 define('get_token()') :(endget_token) 383 get_token 384 * If return stack not empty, we are 385 * reading from the dictionary. 386 lt(rh,1) :s(read_stdin) 387 readfrom = 'dict' 388 storepos = 'dict_pos' 389 pos = dict_pos 390 :(read2) 391 read_stdin 392 readfrom = 'stdin_line' 393 storepos = 'stdin_pos' 394 pos = stdin_pos 395 lt(stdin_pos, size(stdin_line)) :s(read2) 396 * If the pos is not less than input size, read more 397 * And if reading from input fails, we're done! 398 &trim = 1 399 stdin_line = input :f(theend) 400 stdin_pos = 0 401 pos = 0 402 read2 403 $readfrom wordpat :f(fail) 404 $storepos = pos 405 get_token = token 406 407 * TEMP debug output: 408 * $readfrom tab(pos) rem . restofit 409 * output = "*** Read token '" token "', now pos(" storepos ")=" pos "(" restofit ")" 410 411 :(return) 412 endget_token 413 414 415 * Main - read tokens of input 416 * ************************************* 417 418 next_token 419 * Infinite loop prevention while experimenting: 420 * token_count = token_count + 1 421 * lt(token_count, max_tokens) :f(theend) 422 423 token = get_token() 424 425 * Test if token is in dictionary 426 token_data = dtab[token] 427 differ(token_data) :f(nodict) 428 429 * It is, are we compiling? If not, execute it. 430 differ(compile_mode) :f(exec_word) 431 432 * Yes, we're compiling. Is token an immediate word? 433 token_data pos(1) 'i' :s(exec_word) 434 435 * Compiling and not immediate, 'compile' to word 436 dict = dict ' ' token 437 :(next_token) 438 439 exec_word 440 * Is it a native word or dictionary word? 441 token_data pos(0) 'n' :s(exec_native) 442 443 * Is a dictionary word, execute it... 444 token_data tab(2) rem . dict_pos 445 rs[rh] = pos 446 rh = rh + 1 447 448 pos = dict_pos 449 :(next_token) 450 451 exec_native 452 token_data tab(2) rem . native_fn 453 apply(native_fn, null) :(next_token) 454 455 nodict 456 * Test if token is an integer (or empty) 457 differ(token) :f(next_token) 458 integer(token) :f(bad_input) 459 * It is, are we compiling? 460 differ(compile_mode) :f(push_int) 461 * Compile int into dict word definition 462 dict = dict ' ' token 463 :(next_token) 464 push_int 465 push(token) 466 :(next_token) 467 fail output = "FAIL!" :(theend) 468 bad_input output = "ERROR: '" token "' not in dictionary." :(next_token) 469 470 theend 471 * Print any remaining buffered output 472 differ(stdout) :f(end) 473 output = stdout 474 end