#! /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 <Noctis.Skytower@gmail.com>'
__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: Compiled 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()
Diff to Previous Revision
--- revision 2 2010-05-24 12:01:13
+++ revision 3 2011-07-17 19:48:14
@@ -142,7 +142,6 @@
program.append(ins)
else:
syntax_error(line)
-
def parse_label(line, text):
if not text or text[0] != ' ':