-
-
Save dunmatt/6664472 to your computer and use it in GitHub Desktop.
This is a compiler and interpreter for the register machine outlined in the Phil 191 readings.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import sys | |
from pyquery import PyQuery as pq | |
class Node: | |
def __init__(self, operation, register, initial): | |
self.operation = operation | |
self.register = register | |
self.lineNumb = 0 | |
self.outgoing = 0 | |
self.branchTo = 0 | |
self.initial = initial | |
def __repr__(self): | |
if self.operation == "-": | |
return "{0}\t{1}\t{2}\t{3}\t{4}".format(self.lineNumb, "Deb", self.register, self.outgoing, self.branchTo) | |
if self.operation == "+": | |
return "{0}\t{1}\t{2}\t{3}".format(self.lineNumb, "Inc", self.register, self.outgoing) | |
if self.operation == "0": | |
return "{0}\t{1}".format(self.lineNumb, "End") | |
if self.operation == "d": | |
return "{0}\t{1}\t{2}\t{3}".format(self.lineNumb, "Debug", self.register, self.outgoing) | |
return "" | |
def setOutgoing(self, out): | |
if self.outgoing == 0: | |
self.outgoing = out | |
else: | |
print "COMPILING ERROR: {0} has too many outgoing edges! It cannot point to both {1} and {2}, did you forget to label one 0?".format(self.operation + str(self.register), self.outgoing, out) | |
def setBranchTo(self, out): | |
if self.branchTo == 0: | |
self.branchTo = out | |
else: | |
print "COMPILING ERROR: {0} has too many branch destinations! It cannot point to both {1} and {2}, did you get a bit over-zealous with those zeros?".format(self.operation + str(self.register), self.branchTo, out) | |
def parse(contents): | |
"""Don't worry about really understanding this function, to do so would require understanding the JFLAP file format.""" | |
nodes = {} | |
def parseState(i, e): | |
e = pq(e) # wrap the xml elemment in a pyquery object for easy querying | |
if e.children("final"): | |
nodes[e.attr.id] = Node("0", 0, e.children("initial")) | |
else: | |
nodes[e.attr.id] = Node(e.attr.name[0], int(e.attr.name[1:]), e.children("initial")) | |
def parseTransition(i, e): | |
e = pq(e) # wrap the xml elemment in a pyquery object for easy querying | |
if e.find("read").text() == "0": | |
nodes[e.find("from").text()].setBranchTo(e.find("to").text()) | |
else: | |
nodes[e.find("from").text()].setOutgoing(e.find("to").text()) | |
doc = pq(contents) | |
doc.find("state").each(parseState) | |
doc.find("transition").each(parseTransition) | |
script = [] | |
def assignLineNumbers(node, initial): | |
if node and node.lineNumb == 0: | |
node.lineNumb = initial | |
script.append(node) | |
initial += 1 | |
if node.outgoing: | |
initial = assignLineNumbers(nodes[node.outgoing], initial) | |
if node.branchTo: | |
initial = assignLineNumbers(nodes[node.branchTo], initial) | |
return initial | |
startNode = False | |
for node in nodes.values(): | |
if node.initial: | |
startNode = node | |
assignLineNumbers(node, 1) # assign will recurse to assign to everything, unless there are floating states | |
if not startNode: | |
print "COMPILING ERROR: no start node found... you do want this program to start, don't you?" | |
registersLength = 0 | |
for node in nodes.values(): | |
if node.operation == "-" and (node.outgoing == 0 or node.branchTo == 0): | |
print "COMPILING ERROR: All Deb nodes must have two outgoing arrows. {0} does not".format(node.operation + str(node.register)) | |
if node.operation == "+" and (node.outgoing == 0 or node.branchTo != 0): | |
print "COMPILING ERROR: All Inc nodes must have one outgoing arrow. {0} does not".format(node.operation + str(node.register)) | |
if node.operation == "0" and (node.outgoing != 0 or node.branchTo != 0): | |
print "COMPILING ERROR: End nodes must not have outgoing arrows. {0} does not".format(node.operation + str(node.register)) | |
if node.operation == "d" and (node.outgoing == 0 or node.branchTo != 0): | |
print "COMPILING ERROR: All debug nodes must have one outgoing arrow. {0} does not".format(node.operation + str(node.register)) | |
if node.lineNumb == 0: | |
print "COMPILING ERROR: Unreachable node detected! {0} has no incomming edges.".format(node.operation + str(node.register)) | |
if node.operation not in ["-", "+", "d", "0"]: | |
print "COMPILING ERROR: Unrecognized node type. Methinks you forgot to rename node {0}".format(node.operation + str(node.register)) | |
registersLength = max(registersLength, node.register+1) | |
if node.outgoing: | |
node.outgoing = int(nodes[node.outgoing].lineNumb) | |
if node.branchTo: | |
node.branchTo = int(nodes[node.branchTo].lineNumb) | |
return (script, [0 for i in range(registersLength)]) | |
def run(script, registers): | |
programCounter = 1 | |
while True: | |
line = script[programCounter] | |
if line.operation == "0": | |
break | |
elif line.operation == "-": | |
if registers[line.register]: | |
registers[line.register] -= 1 | |
programCounter = line.outgoing | |
else: | |
programCounter = line.branchTo | |
elif line.operation == "+": | |
registers[line.register] += 1 | |
programCounter = line.outgoing | |
elif line.operation == "d": | |
if line.register == 0: | |
print registers[1:] | |
else: | |
print "Register {0}: {1}".format(line.register, registers[line.register]) | |
programCounter = line.outgoing | |
print registers[1:] | |
if len(sys.argv) == 2: | |
with open(sys.argv[1]) as xml: | |
(script, registers) = parse(xml.read()) | |
for line in script: | |
print line | |
elif len(sys.argv) > 2: | |
with open(sys.argv[1]) as xml: | |
(script, registers) = parse(xml.read()) | |
run([0] + script, [0] + [int(i) for i in sys.argv[2:]] + registers) # prepending zero here because these machines are 1 indexed...... I know, right? | |
else: | |
print "You must specify the name of the jff file to compile! Eg: 'jfc example.jff' or 'python jjc example.jff'" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment