diff --git a/scripting.py b/scripting.py index 5c2c200..28ba2f7 100644 --- a/scripting.py +++ b/scripting.py @@ -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,7 +268,9 @@ 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 = [] desc = [[" "]] @@ -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): diff --git a/vm.py b/vm.py index 5deb9ab..cb2faa6 100644 --- a/vm.py +++ b/vm.py @@ -5,45 +5,58 @@ 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({}) 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() - - - \ No newline at end of file