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 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):
SyntLine.rgclsStmt.append(clsStmt)
return clsStmt
@ -166,10 +168,9 @@ class Synt(Typeable):
"Project the current synt into its corresponding pws."
pass
def Compile(self):
"Return a list of {synt|callable}s to be interpreted."
if hasattr(self, "Eval"):
return [self.Eval]
return []
"Return a list of instrs to be interpreted. See vm.py for details on what an instr can be."
return [self.Eval]
# desc - list of desces
# desce:
@ -267,6 +268,8 @@ class SyntExpr(Synt):
def Project(self, pcur):
self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, " ")
def Eval(self, vm):
Fail.Push(vm, "Empty expression", self)
class SyntLine(SyntDesc):
rgclsStmt = []
@ -276,6 +279,8 @@ class SyntLine(SyntDesc):
for clsStmt in cls.rgclsStmt:
if clsStmt.StForTypein().lower().startswith(st.lower()):
yield clsStmt()
def Compile(self):
return []
class SyntBlock(Synt):
def Project(self, pcur):
@ -358,7 +363,7 @@ class SyntVarRef(Synt):
def Project(self, pcur):
self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, self.StForTypein())
def Eval(self, vm):
vm.Push(vm.vars.Get(self.syntVar))
vm.Push(vm.vars.Get(self.syntVar, self))
class SyntBinOp(SyntDesc):
@classmethod
@ -370,16 +375,16 @@ class SyntBinOp(SyntDesc):
def Create(cls, op, rtype, dgEval = None, stNameI = None):
def Decorate(dgEval):
stName = stNameI or dgEval.__name__
def Eval(vm):
def Eval(self, vm):
right = vm.Pop()
left = vm.Pop()
try:
vm.Push(dgEval(left, right))
except Exception as e:
vm.Push(Fail("Failed to " + op + ": " + e))
Fail.Push(vm, "Failed to " + op + ": " + e, self)
def Compile(self):
return [self.left, self.right, Eval]
clsNew = type(stName, (cls,), {"Compile": Compile, "op": op})
return [self.left, self.right, self.Eval]
clsNew = type(stName, (cls,), {"Compile": Compile, "Eval": Eval, "op": op})
SyntExpr.rgclsSyntOp.append(clsNew)
return clsNew
@ -428,13 +433,28 @@ class SyntSet(SyntDesc):
def Compile(self):
return [self.expr, self.Eval]
def Eval(self, vm):
vm.vars.Set(self.lvalue.syntVar, vm.Pop())
# sobj -- generic hungarian for any object that is usable in scripts
# fail -- a FAILURE OBJECT; if it is used as a sarg when evaluating an scmd, a failure is logged and the scmd returns fail.
val = vm.Pop()
if not isinstance(val, Fail):
vm.Log(vm.vars.Set(self.lvalue.syntVar, vm.Pop(), self))
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
class Pcur(object):

32
vm.py
View file

@ -5,23 +5,25 @@ class Vars(TPrs):
def InitPersistent(self):
self.rgmpsynt_sobj = [{}] # [0] is for globals?
def Get(self, synt):
def Get(self, syntVar, synt):
for mpsynt_sobj in reversed(self.rgmpsynt_sobj):
try:
return mpsynt_sobj[synt]
return mpsynt_sobj[syntVar]
except:
pass
return Fail("Variable " + synt.name.St() + " not defined")
return Fail("Variable " + synt.name.St() + " not defined", synt)
def Define(self, 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):
if synt in mpsynt_sobj:
mpsynt_sobj[synt] = sobj
if syntVar in mpsynt_sobj:
mpsynt_sobj[syntVar] = sobj
return
return Fail("Variable " + synt.name.St() + " not defined")
return Fail("Variable " + syntVar.name.St() + " not defined", synt)
def Push(self):
self.rgmpsynt_sobj.append({})
@ -29,21 +31,32 @@ class Vars(TPrs):
def Pop(self):
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):
def InitPersistent(self):
self.vstack = []
self.rstack = []
self.vars = Vars()
self.rgfail = []
def Push(self, sobj):
self.vstack.append(sobj)
def Pop(self):
return self.vstack.pop()
def Log(self, fail):
if fail != None:
self.rgfail.append(fail)
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
self.rstack.append(r, ip)
return (synt.Compile(), 0)
return (rNew, 0)
def RunSynt(self, synt):
r = synt.Compile()
@ -64,6 +77,3 @@ class Vm(TPrs):
if len(rstack) == 0:
break
(r, ip) = self.rstack.pop()