mirror of
https://github.com/russolsen/sallyforth
synced 2024-12-26 21:58:32 +01:00
Initial checkin.
This commit is contained in:
parent
3e48c372dc
commit
2a9ea9548f
7 changed files with 442 additions and 0 deletions
9
README.md
Normal file
9
README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
SallyForth: A simple Forth-like language implemented in Python.
|
||||||
|
##############################################################
|
||||||
|
|
||||||
|
SallyForth is a simple hobby implementation of the FORTH programming
|
||||||
|
language. Possibly the most interesting thing about SallyForth is the
|
||||||
|
name, which
|
||||||
|
[Michael Nygard suggested](https://twitter.com/mtnygard/status/1249781530219642883)
|
||||||
|
as a name for a FORTH implementation
|
||||||
|
at exactly the same time that I happened to be writing this code.
|
28
sallyforth/compiler.py
Normal file
28
sallyforth/compiler.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from lex import forth_prompt
|
||||||
|
from stack import Stack
|
||||||
|
|
||||||
|
class Compiler:
|
||||||
|
def __init__(self, name=None):
|
||||||
|
self.name = name
|
||||||
|
self.instructions = []
|
||||||
|
self.offsets = Stack()
|
||||||
|
|
||||||
|
def add_instruction(self, ins):
|
||||||
|
self.instructions.append(ins)
|
||||||
|
|
||||||
|
def offset(self):
|
||||||
|
return len(self.instructions)
|
||||||
|
|
||||||
|
def push_offset(self):
|
||||||
|
self.offsets.push(self.offset())
|
||||||
|
|
||||||
|
def pop_offset(self):
|
||||||
|
return self.offsets.pop()
|
||||||
|
|
||||||
|
def _str__(self):
|
||||||
|
result = f'Compiler {name} {immediate} '
|
||||||
|
for i in self.instructions:
|
||||||
|
result += str(i)
|
||||||
|
result += ' '
|
||||||
|
return result
|
||||||
|
|
115
sallyforth/kernel.py
Normal file
115
sallyforth/kernel.py
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import sys
|
||||||
|
from os import path
|
||||||
|
from words import *
|
||||||
|
from lex import forth_prompt, read_tokens, is_string
|
||||||
|
from stack import Stack
|
||||||
|
|
||||||
|
def to_number(token):
|
||||||
|
try:
|
||||||
|
return int(token)
|
||||||
|
except ValueError:
|
||||||
|
try:
|
||||||
|
return float(token)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def push_value_f(value):
|
||||||
|
def x(f):
|
||||||
|
f.stack.push(value)
|
||||||
|
return 1
|
||||||
|
return x
|
||||||
|
|
||||||
|
class Forth:
|
||||||
|
def __init__(self, startup=None):
|
||||||
|
self.stack = Stack()
|
||||||
|
self.dictionary = {
|
||||||
|
'true': const_f(True),
|
||||||
|
'false': const_f(False),
|
||||||
|
'nil': const_f(None),
|
||||||
|
'0': const_f(0),
|
||||||
|
'1': const_f(1),
|
||||||
|
'2': const_f(2),
|
||||||
|
';': w_semi,
|
||||||
|
':': w_colon,
|
||||||
|
'+': w_add,
|
||||||
|
'+': w_add,
|
||||||
|
'-': w_sub,
|
||||||
|
'/': w_div,
|
||||||
|
'>': w_gt,
|
||||||
|
'<': w_lt,
|
||||||
|
'<=': w_le,
|
||||||
|
'>=': w_ge,
|
||||||
|
'=': w_eq,
|
||||||
|
'dup': w_dup,
|
||||||
|
'swap': w_swap,
|
||||||
|
'.': w_dot,
|
||||||
|
'nl': w_nl,
|
||||||
|
'dump': w_dump,
|
||||||
|
'idump': w_idump,
|
||||||
|
'stack': w_stack,
|
||||||
|
'begin': w_begin,
|
||||||
|
'until': w_until,
|
||||||
|
'if': w_if,
|
||||||
|
'then': w_then}
|
||||||
|
|
||||||
|
self.compiler = None
|
||||||
|
if startup:
|
||||||
|
execute_startup(startup)
|
||||||
|
|
||||||
|
def compiling(self):
|
||||||
|
return self.compiler
|
||||||
|
|
||||||
|
def process_line(self, readline_f=forth_prompt):
|
||||||
|
tokens = read_tokens(readline_f)
|
||||||
|
for token in tokens:
|
||||||
|
if not self.compiling():
|
||||||
|
self.interpret_token(token)
|
||||||
|
else:
|
||||||
|
self.compile_token(token)
|
||||||
|
|
||||||
|
def compile_token(self, token):
|
||||||
|
if self.compiler.name == None:
|
||||||
|
self.compiler.name = token
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_string(token):
|
||||||
|
self.compiler.add_instruction(push_value_f(token[1::]))
|
||||||
|
return
|
||||||
|
|
||||||
|
if token in self.dictionary:
|
||||||
|
word = self.dictionary[token]
|
||||||
|
if 'immediate' in word.__dict__:
|
||||||
|
#print("before immediate word:", self, self.dictionary)
|
||||||
|
word(self)
|
||||||
|
#print("after immediate word:", self, self.dictionary)
|
||||||
|
else:
|
||||||
|
self.compiler.add_instruction(self.dictionary[token])
|
||||||
|
return
|
||||||
|
|
||||||
|
n = to_number(token)
|
||||||
|
if n == None:
|
||||||
|
self.compiler = None
|
||||||
|
print(token, "? Compile terminated.")
|
||||||
|
else:
|
||||||
|
self.compiler.add_instruction(push_value_f(n))
|
||||||
|
|
||||||
|
def interpret_token(self, token):
|
||||||
|
if is_string(token):
|
||||||
|
self.stack.push(token)
|
||||||
|
return
|
||||||
|
|
||||||
|
if token in self.dictionary:
|
||||||
|
self.dictionary[token](self)
|
||||||
|
return
|
||||||
|
|
||||||
|
n = to_number(token)
|
||||||
|
if n == None:
|
||||||
|
print(token, "?")
|
||||||
|
else:
|
||||||
|
self.stack.push(n)
|
||||||
|
|
||||||
|
def dump(self):
|
||||||
|
print("Forth:", self)
|
||||||
|
print("Stack:", self.stack)
|
||||||
|
print("Dictionary:", self.dictionary)
|
||||||
|
print("Compiler:", self.compiler)
|
55
sallyforth/lex.py
Normal file
55
sallyforth/lex.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import sys
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
def is_string(token):
|
||||||
|
#print("is string:", token, token[0], token[0] == '"')
|
||||||
|
return token[0] == '"'
|
||||||
|
|
||||||
|
def is_space(ch):
|
||||||
|
return ch == " " or ch == "\t" or ch == "\n"
|
||||||
|
|
||||||
|
def tokenize(s):
|
||||||
|
state = 'start'
|
||||||
|
token = ''
|
||||||
|
tokens = []
|
||||||
|
for ch in s:
|
||||||
|
#print(f'Loop state {state} token {token} ch {ch}')
|
||||||
|
if state == 'start' and is_space(ch):
|
||||||
|
continue
|
||||||
|
elif state == 'start' and ch == '"':
|
||||||
|
token = ch
|
||||||
|
state = 'string'
|
||||||
|
elif state == 'start':
|
||||||
|
token = ch
|
||||||
|
state = 'word'
|
||||||
|
elif state == 'string' and ch == '"':
|
||||||
|
tokens.append(token)
|
||||||
|
state = 'start'
|
||||||
|
token = ''
|
||||||
|
elif state == 'word' and is_space(ch):
|
||||||
|
tokens.append(token)
|
||||||
|
state = 'start'
|
||||||
|
token = ''
|
||||||
|
elif state == 'word' or state == 'string':
|
||||||
|
token += ch
|
||||||
|
else:
|
||||||
|
print(f'State: [{state}] token: [{token}] ch: [{ch}]???')
|
||||||
|
state = 'start'
|
||||||
|
if len(token) > 0:
|
||||||
|
tokens.append(token)
|
||||||
|
return tokens
|
||||||
|
|
||||||
|
def read_ch():
|
||||||
|
return sys.stdin.read(1)
|
||||||
|
|
||||||
|
def read_tokens(read_f):
|
||||||
|
line = read_f()
|
||||||
|
return tokenize(line)
|
||||||
|
|
||||||
|
def forth_prompt():
|
||||||
|
return input("SallyForth>> ")
|
||||||
|
|
||||||
|
def file_read_f(f):
|
||||||
|
def read_it():
|
||||||
|
return f.readline()
|
||||||
|
return read_it
|
6
sallyforth/sallyforth.py
Normal file
6
sallyforth/sallyforth.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from kernel import Forth
|
||||||
|
|
||||||
|
f = Forth()
|
||||||
|
while True:
|
||||||
|
f.process_line()
|
||||||
|
|
29
sallyforth/stack.py
Normal file
29
sallyforth/stack.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
class Stack:
|
||||||
|
def __init__(self):
|
||||||
|
self.top = -1
|
||||||
|
self.stack = 100 * [None]
|
||||||
|
|
||||||
|
def push(self, x):
|
||||||
|
# print("stack push", x)
|
||||||
|
self.top += 1
|
||||||
|
self.stack[self.top] = x
|
||||||
|
return x
|
||||||
|
|
||||||
|
def pop(self):
|
||||||
|
result = self.stack[self.top]
|
||||||
|
# print("stack pop", result)
|
||||||
|
self.top -= 1
|
||||||
|
if self.top < -1:
|
||||||
|
print("stack overpop")
|
||||||
|
self.top = -1;
|
||||||
|
return result
|
||||||
|
|
||||||
|
def peek(self):
|
||||||
|
return self.stack[self.top]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
result = ''
|
||||||
|
for i in range(self.top + 1):
|
||||||
|
result += str(self.stack[i])
|
||||||
|
result += ' '
|
||||||
|
return result
|
200
sallyforth/words.py
Normal file
200
sallyforth/words.py
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
from compiler import Compiler
|
||||||
|
|
||||||
|
def execute_f(name, instructions):
|
||||||
|
# print("execute_f:", len(instructions))
|
||||||
|
def inner(forth, debug=False):
|
||||||
|
# print("inner f:", name)
|
||||||
|
# print("inner f:", len(instructions))
|
||||||
|
i = 0
|
||||||
|
while i >= 0:
|
||||||
|
# print(i, "=>", instructions[i])
|
||||||
|
delta = instructions[i](forth)
|
||||||
|
i += delta
|
||||||
|
return 1
|
||||||
|
return inner
|
||||||
|
|
||||||
|
def ifnot_jump_f(n):
|
||||||
|
def ifnot_jump(forth):
|
||||||
|
# print("If not jump:")
|
||||||
|
x = forth.stack.pop()
|
||||||
|
# print("==>value:", x)
|
||||||
|
if not x:
|
||||||
|
# print("==>", x, " is false")
|
||||||
|
# print("==>returning", n)
|
||||||
|
return n
|
||||||
|
# print("==>returning 1")
|
||||||
|
return 1
|
||||||
|
return ifnot_jump
|
||||||
|
|
||||||
|
def const_f(value):
|
||||||
|
def x(f):
|
||||||
|
f.stack.push(value)
|
||||||
|
return 1
|
||||||
|
return x
|
||||||
|
|
||||||
|
def w_gt(f):
|
||||||
|
a = f.stack.pop()
|
||||||
|
b = f.stack.pop()
|
||||||
|
f.stack.push(b > a)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_lt(f):
|
||||||
|
a = f.stack.pop()
|
||||||
|
b = f.stack.pop()
|
||||||
|
f.stack.push(b < a)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_eq(f):
|
||||||
|
a = f.stack.pop()
|
||||||
|
b = f.stack.pop()
|
||||||
|
f.stack.push(a==b)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_le(f):
|
||||||
|
a = f.stack.pop()
|
||||||
|
b = f.stack.pop()
|
||||||
|
f.stack.push(b<=a)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_ge(f):
|
||||||
|
a = f.stack.pop()
|
||||||
|
b = f.stack.pop()
|
||||||
|
f.stack.push(b>=a)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_add(f):
|
||||||
|
a = f.stack.pop()
|
||||||
|
b = f.stack.pop()
|
||||||
|
f.stack.push(b+a)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_mul(f):
|
||||||
|
a = f.stack.pop()
|
||||||
|
b = f.stack.pop()
|
||||||
|
f.stack.push(b*a)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_sub(f):
|
||||||
|
a = f.stack.pop()
|
||||||
|
b = f.stack.pop()
|
||||||
|
f.stack.push(b-a)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_div(f):
|
||||||
|
a = f.stack.pop()
|
||||||
|
b = f.stack.pop()
|
||||||
|
f.stack.push(b/a)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_dot(f):
|
||||||
|
a = f.stack.pop()
|
||||||
|
print(a, end='')
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_dup(f):
|
||||||
|
x = f.stack.peek()
|
||||||
|
f.stack.push(x)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_swap(f):
|
||||||
|
a = f.stack.pop()
|
||||||
|
b = f.stack.pop()
|
||||||
|
f.stack.push(a)
|
||||||
|
f.stack.push(b)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_nl(f):
|
||||||
|
print()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_return(f):
|
||||||
|
return -9999999999
|
||||||
|
|
||||||
|
def w_colon(f):
|
||||||
|
f.compiler = Compiler()
|
||||||
|
|
||||||
|
def w_semi(forth):
|
||||||
|
forth.compiler.add_instruction(w_return)
|
||||||
|
name = forth.compiler.name
|
||||||
|
word_f = execute_f(name, forth.compiler.instructions)
|
||||||
|
forth.dictionary[name] = word_f
|
||||||
|
forth.compiler = None
|
||||||
|
return 1
|
||||||
|
|
||||||
|
w_semi.__dict__['immediate'] = True
|
||||||
|
|
||||||
|
def w_should_not_happen(forth):
|
||||||
|
print("Should not execute this word!")
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
def w_if(forth):
|
||||||
|
print("w_if")
|
||||||
|
compiler = forth.compiler
|
||||||
|
compiler.push_offset()
|
||||||
|
compiler.add_instruction(w_should_not_happen)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
w_if.__dict__['immediate'] = True
|
||||||
|
|
||||||
|
def w_then(forth):
|
||||||
|
compiler = forth.compiler
|
||||||
|
if_offset = compiler.pop_offset()
|
||||||
|
end_offset = compiler.offset()
|
||||||
|
delta = end_offset - if_offset
|
||||||
|
compiler.instructions[if_offset] = ifnot_jump_f(delta)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
w_then.__dict__['immediate'] = True
|
||||||
|
|
||||||
|
def w_do(forth):
|
||||||
|
print("w_do")
|
||||||
|
compiler = forth.compiler
|
||||||
|
compiler.push_offset()
|
||||||
|
compiler.add_instruction(w_should_not_happen)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
w_do.__dict__['immediate'] = True
|
||||||
|
|
||||||
|
def w_while(forth):
|
||||||
|
compiler = forth.compiler
|
||||||
|
do_offset = compiler.pop_offset()
|
||||||
|
while_offset = compiler.offset()
|
||||||
|
delta = do_offset - while_offset
|
||||||
|
compiler.instructions[if_offset] = ifnot_jump_f(delta)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
w_while.__dict__['immediate'] = True
|
||||||
|
|
||||||
|
def w_begin(forth):
|
||||||
|
compiler = forth.compiler
|
||||||
|
compiler.push_offset()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
w_begin.__dict__['immediate'] = True
|
||||||
|
|
||||||
|
def w_until(forth):
|
||||||
|
compiler = forth.compiler
|
||||||
|
begin_offset = compiler.pop_offset()
|
||||||
|
until_offset = compiler.offset()
|
||||||
|
delta = begin_offset - until_offset
|
||||||
|
print("Delta:", delta)
|
||||||
|
compiler.instructions.append(ifnot_jump_f(delta))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
w_until.__dict__['immediate'] = True
|
||||||
|
|
||||||
|
def w_dump(f):
|
||||||
|
f.dump()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def w_idump(f):
|
||||||
|
f.dump()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
w_idump.__dict__['immediate'] = True
|
||||||
|
|
||||||
|
def w_stack(f):
|
||||||
|
print(f.stack)
|
||||||
|
return 1
|
||||||
|
|
Loading…
Reference in a new issue