mirror of
https://github.com/russolsen/sallyforth
synced 2024-12-25 21:58:18 +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