Skip to content

Instantly share code, notes, and snippets.

@dunmatt
Last active December 23, 2015 16:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dunmatt/6664472 to your computer and use it in GitHub Desktop.
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.
#!/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