Flesh out API for failure

This commit is contained in:
Jeremy Penner 2011-09-15 18:19:20 -04:00
parent b9dd53eea1
commit e80826de9e
2 changed files with 58 additions and 28 deletions

View file

@ -5,6 +5,8 @@ from util import *
from contextlib import contextmanager from contextlib import contextmanager
from vm import * from vm import *
# sobj -- generic hungarian for any object that is usable in scripts
# stmt -- statement; a synt that can be used as a line in a SyntBlock
def RegStmt(clsStmt): def RegStmt(clsStmt):
SyntLine.rgclsStmt.append(clsStmt) SyntLine.rgclsStmt.append(clsStmt)
return clsStmt return clsStmt
@ -166,10 +168,9 @@ class Synt(Typeable):
"Project the current synt into its corresponding pws." "Project the current synt into its corresponding pws."
pass pass
def Compile(self): def Compile(self):
"Return a list of {synt|callable}s to be interpreted." "Return a list of instrs to be interpreted. See vm.py for details on what an instr can be."
if hasattr(self, "Eval"): return [self.Eval]
return [self.Eval]
return []
# desc - list of desces # desc - list of desces
# desce: # desce:
@ -267,7 +268,9 @@ class SyntExpr(Synt):
def Project(self, pcur): def Project(self, pcur):
self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, " ") self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, " ")
def Eval(self, vm):
Fail.Push(vm, "Empty expression", self)
class SyntLine(SyntDesc): class SyntLine(SyntDesc):
rgclsStmt = [] rgclsStmt = []
desc = [[" "]] desc = [[" "]]
@ -276,6 +279,8 @@ class SyntLine(SyntDesc):
for clsStmt in cls.rgclsStmt: for clsStmt in cls.rgclsStmt:
if clsStmt.StForTypein().lower().startswith(st.lower()): if clsStmt.StForTypein().lower().startswith(st.lower()):
yield clsStmt() yield clsStmt()
def Compile(self):
return []
class SyntBlock(Synt): class SyntBlock(Synt):
def Project(self, pcur): def Project(self, pcur):
@ -358,7 +363,7 @@ class SyntVarRef(Synt):
def Project(self, pcur): def Project(self, pcur):
self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, self.StForTypein()) self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, self.StForTypein())
def Eval(self, vm): def Eval(self, vm):
vm.Push(vm.vars.Get(self.syntVar)) vm.Push(vm.vars.Get(self.syntVar, self))
class SyntBinOp(SyntDesc): class SyntBinOp(SyntDesc):
@classmethod @classmethod
@ -370,16 +375,16 @@ class SyntBinOp(SyntDesc):
def Create(cls, op, rtype, dgEval = None, stNameI = None): def Create(cls, op, rtype, dgEval = None, stNameI = None):
def Decorate(dgEval): def Decorate(dgEval):
stName = stNameI or dgEval.__name__ stName = stNameI or dgEval.__name__
def Eval(vm): def Eval(self, vm):
right = vm.Pop() right = vm.Pop()
left = vm.Pop() left = vm.Pop()
try: try:
vm.Push(dgEval(left, right)) vm.Push(dgEval(left, right))
except Exception as e: except Exception as e:
vm.Push(Fail("Failed to " + op + ": " + e)) Fail.Push(vm, "Failed to " + op + ": " + e, self)
def Compile(self): def Compile(self):
return [self.left, self.right, Eval] return [self.left, self.right, self.Eval]
clsNew = type(stName, (cls,), {"Compile": Compile, "op": op}) clsNew = type(stName, (cls,), {"Compile": Compile, "Eval": Eval, "op": op})
SyntExpr.rgclsSyntOp.append(clsNew) SyntExpr.rgclsSyntOp.append(clsNew)
return clsNew return clsNew
@ -428,13 +433,28 @@ class SyntSet(SyntDesc):
def Compile(self): def Compile(self):
return [self.expr, self.Eval] return [self.expr, self.Eval]
def Eval(self, vm): def Eval(self, vm):
vm.vars.Set(self.lvalue.syntVar, vm.Pop()) val = vm.Pop()
# sobj -- generic hungarian for any object that is usable in scripts if not isinstance(val, Fail):
# fail -- a FAILURE OBJECT; if it is used as a sarg when evaluating an scmd, a failure is logged and the scmd returns fail. vm.Log(vm.vars.Set(self.lvalue.syntVar, vm.Pop(), self))
class Fail(TPrs): class Fail(TPrs):
def InitPersistent(self, stFailure): """
self.stFailure == stFailure Failure object. Our scripting language has no exceptions; instead, if a failure object is involved in a calculation,
that calculation fails as well. Inspired by Icon. I would like this model to have the property that the designer can
edit a script while the game is running and see the effect of their changes immediately; this suggests that failures
should be logged and the script should continue to run.
"""
def InitPersistent(self, stFailure, synt):
self.stFailure = stFailure
self.synt = synt
@staticmethod
def Push(cls, vm, stFailure, synt):
fail = Fail(stFailure, synt)
vm.Push(fail)
vm.Log(fail)
@staticmethod
def Log(cls, vm, stFailure, synt):
vm.Log(Fail(stFailure, synt))
# projection cursor # projection cursor
class Pcur(object): class Pcur(object):

34
vm.py
View file

@ -5,45 +5,58 @@ class Vars(TPrs):
def InitPersistent(self): def InitPersistent(self):
self.rgmpsynt_sobj = [{}] # [0] is for globals? self.rgmpsynt_sobj = [{}] # [0] is for globals?
def Get(self, synt): def Get(self, syntVar, synt):
for mpsynt_sobj in reversed(self.rgmpsynt_sobj): for mpsynt_sobj in reversed(self.rgmpsynt_sobj):
try: try:
return mpsynt_sobj[synt] return mpsynt_sobj[syntVar]
except: except:
pass pass
return Fail("Variable " + synt.name.St() + " not defined") return Fail("Variable " + synt.name.St() + " not defined", synt)
def Define(self, synt, sobj): def Define(self, synt, sobj):
self.rgmpsynt_sobj[-1][synt] = sobj self.rgmpsynt_sobj[-1][synt] = sobj
def Set(self, synt, sobj): def Set(self, syntVar, sobj, synt):
if syntVar == None:
return Fail("No variable specified to set", None, synt)
for mpsynt_sobj in reversed(self.rgmpsynt_sobj): for mpsynt_sobj in reversed(self.rgmpsynt_sobj):
if synt in mpsynt_sobj: if syntVar in mpsynt_sobj:
mpsynt_sobj[synt] = sobj mpsynt_sobj[syntVar] = sobj
return return
return Fail("Variable " + synt.name.St() + " not defined") return Fail("Variable " + syntVar.name.St() + " not defined", synt)
def Push(self): def Push(self):
self.rgmpsynt_sobj.append({}) self.rgmpsynt_sobj.append({})
def Pop(self): def Pop(self):
self.rgmpsynt_sobj.pop() self.rgmpsynt_sobj.pop()
# instr: can be a synt or a callable. If instr is a synt, the VM will push the current list onto the rstack with its associated
# ip (instruction pointer), compile the synt, and begin executing it (Vm.CallI). If instr is a callable, the vm will call it; if it
# returns a synt, the synt is compiled and called.
class Vm(TPrs): class Vm(TPrs):
def InitPersistent(self): def InitPersistent(self):
self.vstack = [] self.vstack = []
self.rstack = [] self.rstack = []
self.vars = Vars() self.vars = Vars()
self.rgfail = []
def Push(self, sobj): def Push(self, sobj):
self.vstack.append(sobj) self.vstack.append(sobj)
def Pop(self): def Pop(self):
return self.vstack.pop() return self.vstack.pop()
def Log(self, fail):
if fail != None:
self.rgfail.append(fail)
def CallI(self, synt, r, ip): def CallI(self, synt, r, ip):
rNew = synt.Compile()
if len(rNew) == 0: # no-op optimization
return (r, ip)
if ip < len(r): # tail call optimization if ip < len(r): # tail call optimization
self.rstack.append(r, ip) self.rstack.append(r, ip)
return (synt.Compile(), 0) return (rNew, 0)
def RunSynt(self, synt): def RunSynt(self, synt):
r = synt.Compile() r = synt.Compile()
@ -64,6 +77,3 @@ class Vm(TPrs):
if len(rstack) == 0: if len(rstack) == 0:
break break
(r, ip) = self.rstack.pop() (r, ip) = self.rstack.pop()