Initial checkin.

This commit is contained in:
Russ Olsen 2020-04-14 12:44:37 -04:00
parent 3e48c372dc
commit 2a9ea9548f
7 changed files with 442 additions and 0 deletions

9
README.md Normal file
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,6 @@
from kernel import Forth
f = Forth()
while True:
f.process_line()

29
sallyforth/stack.py Normal file
View 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
View 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