Whitespace Assembler
#! /usr/bin/env python
"""Assembler.py
Compiles a program from "Assembly" folder into "Program" folder.
Can be executed directly by double-click or on the command line.
Give name of *.WSA file without extension (example: stack_calc)."""
################################################################################
__author__ = 'Stephen "Zero" Chappell <
[email protected]>'
__date__ = '14 March 2010'
__version__ = '$Revision: 3 $'
################################################################################
import string
from Interpreter import INS, MNEMONIC
################################################################################
def parse(code):
program = []
process_virtual(program, code)
process_control(program)
return tuple(program)
def process_virtual(program, code):
for line, text in enumerate(code.split('\n')):
if not text or text[0] == '#':
continue
if text.startswith('part '):
parse_part(program, line, text[5:])
elif text.startswith(' '):
parse_code(program, line, text[5:])
else:
syntax_error(line)
def syntax_error(line):
raise SyntaxError('Line ' + str(line + 1))
################################################################################
def process_control(program):
parts = get_parts(program)
names = dict(pair for pair in zip(parts, generate_index()))
correct_control(program, names)
def get_parts(program):
parts = []
for ins in program:
if isinstance(ins, tuple):
ins, arg = ins
if ins == INS.PART:
if arg in parts:
raise NameError('Part definition was found twice: ' + arg)
parts.append(arg)
return parts
def generate_index():
index = 1
while True:
yield index
index *= -1
if index > 0:
index += 1
def correct_control(program, names):
for index, ins in enumerate(program):
if isinstance(ins, tuple):
ins, arg = ins
if ins in HAS_LABEL:
if arg not in names:
raise NameError('Part definition was never found: ' + arg)
program[index] = (ins, names[arg])
################################################################################
def parse_part(program, line, text):
if not valid_label(text):
syntax_error(line)
program.append((INS.PART, text))
def valid_label(text):
if not between_quotes(text):
return
False
label = text[1:-1]
if not valid_name(label):
return
False
return True
def between_quotes(text):
if len(text) < 3:
return
False
if text.count('"') != 2:
return
False
if text[0] != '"' or text[-1] != '"':
return
False
return True
def valid_name(label):
valid_characters = string.ascii_letters + string.digits + '_'
valid_set = frozenset(valid_characters)
label_set = frozenset(label)
if len(label_set - valid_set) != 0:
return
False
return True
################################################################################
from Interpreter import HAS_LABEL, Program
NO_ARGS = Program.NO_ARGS
HAS_ARG = Program.HAS_ARG
TWO_WAY = tuple(set(NO_ARGS) & set(HAS_ARG))
################################################################################
def parse_code(program, line, text):
for ins, word in enumerate(MNEMONIC):
if text.startswith(word):
check_code(program, line, text[len(word):], ins)
break
else:
syntax_error(line)
def check_code(program, line, text, ins):
if ins in TWO_WAY:
if text:
number = parse_number(line, text)
program.append((ins, number))
else:
program.append(ins)
elif ins in HAS_LABEL:
text = parse_label(line, text)
program.append((ins, text))
elif ins in HAS_ARG:
number = parse_number(line, text)
program.append((ins, number))
elif ins in NO_ARGS:
if text:
syntax_error(line)
program.append(ins)
else:
syntax_error(line)
def parse_label(line, text):
if not text or text[0] != ' ':
syntax_error(line)
text = text[1:]
if not valid_label(text):
syntax_error(line)
return text
################################################################################
def parse_number(line, text):
if not valid_number(text):
syntax_error(line)
return int(text)
def valid_number(text):
if len(text) < 2:
return
False
if text[0] != ' ':
return
False
text = text[1:]
if '+' in text and '-' in text:
return
False
if '+' in text:
if text.count('+') != 1:
return
False
if text[0] != '+':
return
False
text = text[1:]
if not text:
return
False
if '-' in text:
if text.count('-') != 1:
return
False
if text[0] != '-':
return
False
text = text[1:]
if not text:
return
False
valid_set = frozenset(string.digits)
value_set = frozenset(text)
if len(value_set - valid_set) != 0:
return
False
return True
################################################################################
################################################################################
from Interpreter import partition_number
VMC_2_TRI = {
(INS.PUSH, True): (0, 0),
(INS.COPY, False): (0, 2, 0),
(INS.COPY, True): (0, 1, 0),
(INS.SWAP, False): (0, 2, 1),
(INS.AWAY, False): (0, 2, 2),
(INS.AWAY, True): (0, 1, 2),
(INS.ADD, False): (1, 0, 0, 0),
(INS.SUB, False): (1, 0, 0, 1),
(INS.MUL, False): (1, 0, 0, 2),
(INS.DIV, False): (1, 0, 1, 0),
(INS.MOD, False): (1, 0, 1, 1),
(INS.SET, False): (1, 1, 0),
(INS.GET, False): (1, 1, 1),
(INS.PART, True): (2, 0, 0),
(INS.CALL, True): (2, 0, 1),
(INS.GOTO, True): (2, 0, 2),
(INS.ZERO, True): (2, 1, 0),
(INS.LESS, True): (2, 1, 1),
(INS.BACK, False): (2, 1, 2),
(INS.EXIT, False): (2, 2, 2),
(INS.OCHR, False): (1, 2, 0, 0),
(INS.OINT, False): (1, 2, 0, 1),
(INS.ICHR, False): (1, 2, 1, 0),
(INS.IINT, False): (1, 2, 1, 1)
}
################################################################################
def to_trinary(program):
trinary_code = []
for ins in program:
if isinstance(ins, tuple):
ins, arg = ins
trinary_code.extend(VMC_2_TRI[(ins, True)])
trinary_code.extend(from_number(arg))
else:
trinary_code.extend(VMC_2_TRI[(ins, False)])
return tuple(trinary_code)
def from_number(arg):
code = [int(arg < 0)]
if arg:
for bit in reversed(list(partition_number(abs(arg), 2))):
code.append(bit)
return code + [2]
return code + [0, 2]
to_ws = lambda trinary: ''.join(' \t\n'[index] for index in trinary)
def compile_wsa(source):
program = parse(source)
trinary = to_trinary(program)
ws_code = to_ws(trinary)
return ws_code
################################################################################
################################################################################
import os
import sys
import time
import traceback
def main():
name, source, command_line, error = get_source()
if not error:
start = time.clock()
try:
ws_code = compile_wsa(source)
except:
print('ERROR: File could not be compiled.\n')
traceback.print_exc()
error = True
else:
path = os.path.join('Programs', name + '.ws')
try:
open(path, 'w').write(ws_code)
except IOError as err:
print(err)
error = True
else:
div, mod = divmod((time.clock() - start) * 1000, 1)
args = int(div), '{:.3}'.format(mod)[1:]
print('DONE: Comipled in {}{} ms'.format(*args))
handle_close(error, command_line)
def get_source():
if len(sys.argv) > 1:
command_line = True
name = sys.argv[1]
else:
command_line =
False
try:
name = input('Source File: ')
except:
return None, None,
False, True
print()
path = os.path.join('Assembly', name + '.wsa')
try:
return name, open(path).read(), command_line,
False
except IOError as err:
print(err)
return None, None, command_line, True
def handle_close(error, command_line):
if error:
usage = 'Usage: {} <assembly>'.format(os.path.basename(sys.argv[0]))
print('\n{}\n{}'.format('-' * len(usage), usage))
if not command_line:
time.sleep(10)
################################################################################
if __name__ == '__main__':
main()
Whitespace Helpers
#! /usr/bin/env python
"""Helpers.py
Includes a function to encode Python strings into my WSA format.
Has a "PRINT_LINE" function that can be copied to a WSA program.
Contains a "PRINT" function and documentation as an explanation."""
################################################################################
__author__ = 'Stephen "Zero" Chappell <
[email protected]>'
__date__ = '14 March 2010'
__version__ = '$Revision: 1 $'
################################################################################
def encode_string(string, addr):
print(' push', addr)
print(' push', len(string))
print(' set')
addr += 1
for offset, character in enumerate(string):
print(' push', addr + offset)
print(' push', ord(character))
print(' set')
################################################################################
# Prints a string with newline.
# push addr
# call "PRINT_LINE"
"""
part "PRINT_LINE"
call "PRINT"
push 10
ochr
back
"""
################################################################################
# def print(array):
# if len(array) <= 0:
# return
# offset = 1
# while len(array) - offset >= 0:
# ptr = array.ptr + offset
# putch(array[ptr])
# offset += 1
"""
part "PRINT"
# Line 1-2
copy
get
less "__PRINT_RET_1"
copy
get
zero "__PRINT_RET_1"
# Line 3
push 1
# Line 4
part "__PRINT_LOOP"
copy
copy 2
get
swap
sub
less "__PRINT_RET_2"
# Line 5
copy 1
copy 1
add
# Line 6
get
ochr
# Line 7
push 1
add
goto "__PRINT_LOOP"
part "__PRINT_RET_2"
away
part "__PRINT_RET_1"
away
back
"""
Whitespace Interpreter
#! /usr/bin/env python
"""Interpreter.py
Runs programs in "Programs" and creates *.WSO files when needed.
Can be executed directly by double-click or on the command line.
If run on command line, add "ASM" flag to dump program assembly."""
################################################################################
__author__ = 'Stephen "Zero" Chappell <
[email protected]>'
__date__ = '14 March 2010'
__version__ = '$Revision: 4 $'
################################################################################
def test_file(path):
disassemble(parse(trinary(load(path))), True)
################################################################################
load = lambda ws: ''.join(c for r in open(ws) for c in r if c in ' \t\n')
trinary = lambda ws: tuple(' \t\n'.index(c) for c in ws)
################################################################################
def enum(names):
names = names.replace(',', ' ').split()
space = dict((reversed(pair) for pair in enumerate(names)), __slots__=())
return type('enum', (object,), space)()
INS = enum('''\
PUSH, COPY, SWAP, AWAY, \
ADD, SUB, MUL, DIV, MOD, \
SET, GET, \
PART, CALL, GOTO, ZERO, LESS, BACK, EXIT, \
OCHR, OINT, ICHR, IINT''')
################################################################################
def parse(code):
ins = iter(code).__next__
program = []
while True:
try:
imp = ins()
except StopIteration:
return tuple(program)
if imp == 0:
# [Space]
parse_stack(ins, program)
elif imp == 1:
# [Tab]
imp = ins()
if imp == 0:
# [Tab][Space]
parse_math(ins, program)
elif imp == 1:
# [Tab][Tab]
parse_heap(ins, program)
else:
# [Tab][Line]
parse_io(ins, program)
else:
# [Line]
parse_flow(ins, program)
def parse_number(ins):
sign = ins()
if sign == 2:
raise StopIteration()
buffer = ''
code = ins()
if code == 2:
raise StopIteration()
while code != 2:
buffer += str(code)
code = ins()
if sign == 1:
return int(buffer, 2) * -1
return int(buffer, 2)
################################################################################
def parse_stack(ins, program):
code = ins()
if code == 0:
# [Space]
number = parse_number(ins)
program.append((INS.PUSH, number))
elif code == 1:
# [Tab]
code = ins()
number = parse_number(ins)
if code == 0:
# [Tab][Space]
program.append((INS.COPY, number))
elif code == 1:
# [Tab][Tab]
raise StopIteration()
else:
# [Tab][Line]
program.append((INS.AWAY, number))
else:
# [Line]
code = ins()
if code == 0:
# [Line][Space]
program.append(INS.COPY)
elif code == 1:
# [Line][Tab]
program.append(INS.SWAP)
else:
# [Line][Line]
program.append(INS.AWAY)
def parse_math(ins, program):
code = ins()
if code == 0:
# [Space]
code = ins()
if code == 0:
# [Space][Space]
program.append(INS.ADD)
elif code == 1:
# [Space][Tab]
program.append(INS.SUB)
else:
# [Space][Line]
program.append(INS.MUL)
elif code == 1:
# [Tab]
code = ins()
if code == 0:
# [Tab][Space]
program.append(INS.DIV)
elif code == 1:
# [Tab][Tab]
program.append(INS.MOD)
else:
# [Tab][Line]
raise StopIteration()
else:
# [Line]
raise StopIteration()
def parse_heap(ins, program):
code = ins()
if code == 0:
# [Space]
program.append(INS.SET)
elif code == 1:
# [Tab]
program.append(INS.GET)
else:
# [Line]
raise StopIteration()
def parse_io(ins, program):
code = ins()
if code == 0:
# [Space]
code = ins()
if code == 0:
# [Space][Space]
program.append(INS.OCHR)
elif code == 1:
# [Space][Tab]
program.append(INS.OINT)
else:
# [Space][Line]
raise StopIteration()
elif code == 1:
# [Tab]
code = ins()
if code == 0:
# [Tab][Space]
program.append(INS.ICHR)
elif code == 1:
# [Tab][Tab]
program.append(INS.IINT)
else:
# [Tab][Line]
raise StopIteration()
else:
# [Line]
raise StopIteration()
def parse_flow(ins, program):
code = ins()
if code == 0:
# [Space]
code = ins()
label = parse_number(ins)
if code == 0:
# [Space][Space]
program.append((INS.PART, label))
elif code == 1:
# [Space][Tab]
program.append((INS.CALL, label))
else:
# [Space][Line]
program.append((INS.GOTO, label))
elif code == 1:
# [Tab]
code = ins()
if code == 0:
# [Tab][Space]
label = parse_number(ins)
program.append((INS.ZERO, label))
elif code == 1:
# [Tab][Tab]
label = parse_number(ins)
program.append((INS.LESS, label))
else:
# [Tab][Line]
program.append(INS.BACK)
else:
# [Line]
code = ins()
if code == 2:
# [Line][Line]
program.append(INS.EXIT)
else:
# [Line][Space] or [Line][Tab]
raise StopIteration()
################################################################################
MNEMONIC = '\
push copy swap away add sub mul div mod set get part \
call goto zero less back exit ochr oint ichr iint'.split()
HAS_ARG = [getattr(INS, name) for name in
'PUSH COPY AWAY PART CALL GOTO ZERO LESS'.split()]
HAS_LABEL = [getattr(INS, name) for name in
'PART CALL GOTO ZERO LESS'.split()]
def disassemble(program, names=False):
if names:
names = create_names(program)
for ins in program:
if isinstance(ins, tuple):
ins, arg = ins
assert ins in HAS_ARG
has_arg = True
else:
assert INS.PUSH <= ins <= INS.IINT
has_arg =
False
if ins == INS.PART:
if names:
print(MNEMONIC[ins], '"' + names[arg] + '"')
else:
print(MNEMONIC[ins], arg)
elif has_arg and ins in HAS_ARG:
if ins in HAS_LABEL and names:
assert arg in names
print(' ' + MNEMONIC[ins], '"' + names[arg] + '"')
else:
print(' ' + MNEMONIC[ins], arg)
else:
print(' ' + MNEMONIC[ins])
################################################################################
def create_names(program):
names = {}
number = 1
for ins in program:
if isinstance(ins, tuple) and ins[0] == INS.PART:
label = ins[1]
assert label not in names
names[label] = number_to_name(number)
number += 1
return names
def number_to_name(number):
name = ''
for offset in reversed(list(partition_number(number, 27))):
if offset:
name += chr(ord('A') + offset - 1)
else:
name += '_'
return name
def partition_number(number, base):
div, mod = divmod(number, base)
yield mod
while div:
div, mod = divmod(div, base)
yield mod
################################################################################
CODE = (' \t\n',
' \n ',
' \t \t\n',
' \n\t',
' \n\n',
' \t\n \t\n',
'\t ',
'\t \t',
'\t \n',
'\t \t ',
'\t \t\t',
'\t\t ',
'\t\t\t',
'\n \t\n',
'\n \t \t\n',
'\n \n \t\n',
'\n\t \t\n',
'\n\t\t \t\n',
'\n\t\n',
'\n\n\n',
'\t\n ',
'\t\n \t',
'\t\n\t ',
'\t\n\t\t')
EXAMPLE = ''.join(CODE)
################################################################################
NOTES = '''\
STACK
=====
push number
copy
copy number
swap
away
away number
MATH
====
add
sub
mul
div
mod
HEAP
====
set
get
FLOW
====
part label
call label
goto label
zero label
less label
back
exit
I/O
===
ochr
oint
ichr
iint'''
################################################################################
################################################################################
class Stack:
def __init__(self):
self.__data = []
# Stack Operators
def push(self, number):
self.__data.append(number)
def copy(self, number=None):
if number is None:
self.__data.append(self.__data[-1])
else:
size = len(self.__data)
index = size - number - 1
assert 0 <= index < size
self.__data.append(self.__data[index])
def swap(self):
self.__data[-2], self.__data[-1] = self.__data[-1], self.__data[-2]
def away(self, number=None):
if number is None:
self.__data.pop()
else:
size = len(self.__data)
index = size - number - 1
assert 0 <= index < size
del self.__data[index:-1]
# Math Operators
def add(self):
suffix = self.__data.pop()
prefix = self.__data.pop()
self.__data.append(prefix + suffix)
def sub(self):
suffix = self.__data.pop()
prefix = self.__data.pop()
self.__data.append(prefix - suffix)
def mul(self):
suffix = self.__data.pop()
prefix = self.__data.pop()
self.__data.append(prefix * suffix)
def div(self):
suffix = self.__data.pop()
prefix = self.__data.pop()
self.__data.append(prefix // suffix)
def mod(self):
suffix = self.__data.pop()
prefix = self.__data.pop()
self.__data.append(prefix % suffix)
# Program Operator
def pop(self):
return self.__data.pop()
################################################################################
class Heap:
def __init__(self):
self.__data = {}
def set_(self, addr, item):
if item:
self.__data[addr] = item
elif addr in self.__data:
del self.__data[addr]
def get_(self, addr):
return self.__data.get(addr, 0)
################################################################################
import os
import zlib
import msvcrt
import pickle
import string
class CleanExit(Exception): pass
NOP = lambda arg: None
DEBUG_WHITESPACE =
False
################################################################################
class Program:
NO_ARGS = INS.COPY, INS.SWAP, INS.AWAY, INS.ADD, \
INS.SUB, INS.MUL, INS.DIV, INS.MOD, \
INS.SET, INS.GET, INS.BACK, INS.EXIT, \
INS.OCHR, INS.OINT, INS.ICHR, INS.IINT
HAS_ARG = INS.PUSH, INS.COPY, INS.AWAY, INS.PART, \
INS.CALL, INS.GOTO, INS.ZERO, INS.LESS
def __init__(self, code):
self.__data = code
self.__validate()
self.__build_jump()
self.__check_jump()
self.__setup_exec()
def __setup_exec(self):
self.__iptr = 0
self.__stck = stack = Stack()
self.__heap = Heap()
self.__cast = []
self.__meth = (stack.push, stack.copy, stack.swap, stack.away,
stack.add, stack.sub, stack.mul, stack.div, stack.mod,
self.__set, self.__get,
NOP, self.__call, self.__goto, self.__zero,
self.__less, self.__back, self.__exit,
self.__ochr, self.__oint, self.__ichr, self.__iint)
def step(self):
ins = self.__data[self.__iptr]
self.__iptr += 1
if isinstance(ins, tuple):
self.__meth[ins[0]](ins[1])
else:
self.__meth[ins]()
def run(self):
while True:
ins = self.__data[self.__iptr]
self.__iptr += 1
if isinstance(ins, tuple):
self.__meth[ins[0]](ins[1])
else:
self.__meth[ins]()
def __oint(self):
for digit in str(self.__stck.pop()):
msvcrt.putwch(digit)
def __ichr(self):
addr = self.__stck.pop()
# Input Routine
while msvcrt.kbhit():
msvcrt.getwch()
while True:
char = msvcrt.getwch()
if char in '\x00\xE0':
msvcrt.getwch()
elif char in string.printable:
char = char.replace('\r', '\n')
msvcrt.putwch(char)
break
item = ord(char)
# Storing Number
self.__heap.set_(addr, item)
def __iint(self):
addr = self.__stck.pop()
# Input Routine
while msvcrt.kbhit():
msvcrt.getwch()
buff = ''
char = msvcrt.getwch()
while char != '\r' or not buff:
if char in '\x00\xE0':
msvcrt.getwch()
elif char in '+-' and not buff:
msvcrt.putwch(char)
buff += char
elif '0' <= char <= '9':
msvcrt.putwch(char)
buff += char
elif char == '\b':
if buff:
buff = buff[:-1]
msvcrt.putwch(char)
msvcrt.putwch(' ')
msvcrt.putwch(char)
char = msvcrt.getwch()
msvcrt.putwch(char)
msvcrt.putwch('\n')
item = int(buff)
# Storing Number
self.__heap.set_(addr, item)
def __goto(self, label):
self.__iptr = self.__jump[label]
def __zero(self, label):
if self.__stck.pop() == 0:
self.__iptr = self.__jump[label]
def __less(self, label):
if self.__stck.pop() < 0:
self.__iptr = self.__jump[label]
def __exit(self):
self.__setup_exec()
raise CleanExit()
def __set(self):
item = self.__stck.pop()
addr = self.__stck.po