diff --git a/scripting.py b/scripting.py index 3dd98f3..5c2c200 100644 --- a/scripting.py +++ b/scripting.py @@ -3,6 +3,7 @@ from basetoken import * from tpers import * from util import * from contextlib import contextmanager +from vm import * def RegStmt(clsStmt): SyntLine.rgclsStmt.append(clsStmt) @@ -12,27 +13,26 @@ def RegRtype(clsRtype): Rtype.rgclsRtype.append(clsRtype) return clsRtype -# rtype -- runtime type -# method: RglitFromSt(st) -- parse the string and return a list of potentially matching literals -# method: RgsyntFromSt(scope, st) -- parse the string and return a list of synts whose rtypes match this -# method: StForSobj(sobj) -- a user-visible description of the sobj -# method: FMember(sobj) -- returns true if sobj is a member of this type -# method: FOverlap(rtype) -- returns true if there is some overlap between this type and rtype class Rtype(TPrs): + "Runtime type" rgclsRtype = [] @classmethod def RtypeMax(cls): + "Return an instance of this rtype which will match anything that any instance of this rtype matches." return cls() def RglitFromSt(self, st): + "Return literal objects that match a (partially entered) string." return [] def RgsyntFromSt(self, syntBlock, st): + "Return syntax objects whose rtypes match this that match a (partially entered) string." for lit in self.RglitFromSt(st): - yield SyntLit(None, lit, self) + yield SyntLit(lit, self) for synt in self.RgsyntVarRefFromSt(syntBlock, st): yield synt def RgsyntVarRefFromSt(self, syntBlock, st): + "Return variable references that match a (partially entered) string." while syntBlock != None: if isinstance(syntBlock, SyntBlock): for synt in self.RgsyntFromSyntBlock(syntBlock, st): @@ -44,13 +44,16 @@ class Rtype(TPrs): # yield all vars in scope for syntVar in syntBlock.rgsynt: if SyntVar.FVarOfType(syntVar, self) and syntVar.name.St().lower().startswith(st.lower()): - yield SyntVarRef(None, syntVar) + yield SyntVarRef(syntVar) def StForSobj(self, sobj): + "Return a user-visible description of a sobj." return str(sobj) def FMember(self, sobj): + "Returns true if sobj is a member of this type." return isinstance(sobj, self.pythontype) def FOverlap(self, rtype): + "Returns true if there is some overlap between this type and rtype." return isinstance(rtype, self.__class__) @RegRtype @@ -90,6 +93,13 @@ class RtypeAny(Rtype): class Typeable(TPrs): def InitTransient(self): self.steditTypein = Stedit() + def PersistStedit(self): + assert self.FPersist() + try: + del self.steditTypein + except: + pass + self.steditTypein = Stedit() def GetStTypein(self): st = self.steditTypein.GetValue() return None if st == "" else st @@ -98,19 +108,16 @@ class Typeable(TPrs): def HandleTypeinKey(self, key): return self.steditTypein.HandleKey(key) -# synt -- a piece of syntax -# method: Project() -- projects it -# class Synt(Typeable): - desc = None - def InitPersistent(self, syntParent): - self.syntParent = syntParent + "A piece of syntax" + def InitPersistent(self): + self.syntParent = None self.rgsynt = [] self.Populate() @classmethod - def SyntDefault(cls, syntParent): - return cls(syntParent) + def SyntDefault(cls): + return cls() @classmethod def RgsyntReplace(cls, syntChild, st, rtype): @@ -131,8 +138,14 @@ class Synt(Typeable): return [] def Replace(self, syntOld, syntNew): - self.rgsynt[self.Isynt(syntOld)] = syntNew + assert syntNew.syntParent == None syntNew.syntParent = self + self.rgsynt[self.Isynt(syntOld)] = syntNew + + def Append(self, syntNew): + assert syntNew.syntParent == None + syntNew.syntParent = self + self.rgsynt.append(syntNew) def Isynt(self, syntChild): if isinstance(syntChild, int): @@ -145,15 +158,19 @@ class Synt(Typeable): def RtypeTopForChild(self, syntChild): return None - def Eval(self, ectx): - pass - def Populate(self): + "Populate rgsynt with useful default values." pass def Project(self, pcur): + "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 [] + # desc - list of desces # desce: # string: "foo" -- literal uneditable text @@ -172,6 +189,7 @@ class SyntDesc(Synt): @staticmethod def DefProp(isynt): + "Create an alias for rgsynt[isynt]." def getI(self): return self.rgsynt[isynt] def setI(self, syntNew): @@ -184,8 +202,8 @@ class SyntDesc(Synt): def Populate(self): for desce in self.Desc(): if isinstance(desce, tuple): - synt = desce[0].SyntDefault(self) - self.rgsynt.append(synt) + synt = desce[0].SyntDefault() + self.Append(synt) # for synt in self.rgsynt: # synt.Populate() @@ -229,6 +247,9 @@ class SyntDesc(Synt): isynt += 1 class SyntName(Synt): + def InitPersistent(self): + Synt.InitPersistent(self) + self.PersistStedit() def Project(self, pcur): PwTypein(pcur.PwHoriz(self), self.St(), self) def St(self): @@ -242,7 +263,7 @@ class SyntExpr(Synt): yield synt for clsSyntOp in cls.rgclsSyntOp: if clsSyntOp.StForTypein().lower().startswith(st.lower()): - yield clsSyntOp.SyntDefault(None) + yield clsSyntOp.SyntDefault() def Project(self, pcur): self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, " ") @@ -254,7 +275,7 @@ class SyntLine(SyntDesc): def RgsyntReplace(cls, syntChild, st, rtype): for clsStmt in cls.rgclsStmt: if clsStmt.StForTypein().lower().startswith(st.lower()): - yield clsStmt(None) + yield clsStmt() class SyntBlock(Synt): def Project(self, pcur): @@ -276,7 +297,7 @@ class SyntBlock(Synt): def InsertLineAfter(self, syntAfter, syntStmt): if syntStmt == None: - syntStmt = SyntLine(self) + syntStmt = SyntLine() if syntAfter == None: self.rgsynt.insert(0, syntStmt) else: @@ -290,9 +311,12 @@ class SyntBlock(Synt): psel.Inc(pwKey.PwParent()) return True return False + def Compile(self): + return self.rgsynt + class SyntLit(Synt): - def InitPersistent(self, syntParent, value, rtype): - Synt.InitPersistent(self, syntParent) + def InitPersistent(self, value, rtype): + Synt.InitPersistent(self) self.value = value self.rtype = rtype def StForTypein(self): @@ -301,9 +325,9 @@ class SyntLit(Synt): return self.rtype def Project(self, pcur): self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, self.StForTypein()) - def Exec(self, ectx): - return self.value - + def Eval(self, vm): + vm.Push(self.value) + @RegStmt class SyntVar(SyntDesc): desc = [["Define"], " ", (SyntName, None), " to be ", (SyntExpr, 1)] @@ -312,22 +336,30 @@ class SyntVar(SyntDesc): @classmethod def FVarOfType(cls, synt, rtype): return isinstance(synt, cls) and synt.expr != None and rtype.FOverlap(synt.expr.Rtype()) - + def Compile(self): + return [self.expr, self.Eval] + def Eval(self, vm): + vm.vars.Define(self, vm.Pop()) + class SyntVarRef(Synt): - def __init__(self, syntParent, syntVar = None): - Synt.__init__(self, syntParent) + def InitPersistent(self, syntVar = None): + Synt.InitPersistent(self) self.syntVar = syntVar @classmethod def RgsyntReplace(cls, syntChild, st, rtype): - rtype = rtype and RtypeAny() - return rtype.RgsyntVarRefForChild(syntChild, st) + rtype = rtype or RtypeAny() + return rtype.RgsyntVarRefFromSt(syntChild, st) def Rtype(self): return self.syntVar.Rtype() def StForTypein(self): - return self.syntVar.name.St() + if self.syntVar: + return self.syntVar.name.St() + return "" def Project(self, pcur): self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, self.StForTypein()) - + def Eval(self, vm): + vm.Push(vm.vars.Get(self.syntVar)) + class SyntBinOp(SyntDesc): @classmethod def Desc(cls): @@ -338,9 +370,16 @@ class SyntBinOp(SyntDesc): def Create(cls, op, rtype, dgEval = None, stNameI = None): def Decorate(dgEval): stName = stNameI or dgEval.__name__ - def Eval(self, ectx): - return dgEval(self.left.Eval(ectx), self.right.Eval(ectx)) - clsNew = type(stName, (cls,), {"Eval": Eval, "op": op}) + def Eval(vm): + right = vm.Pop() + left = vm.Pop() + try: + vm.Push(dgEval(left, right)) + except Exception as e: + vm.Push(Fail("Failed to " + op + ": " + e)) + def Compile(self): + return [self.left, self.right, Eval] + clsNew = type(stName, (cls,), {"Compile": Compile, "op": op}) SyntExpr.rgclsSyntOp.append(clsNew) return clsNew @@ -354,9 +393,14 @@ class SyntBinOp(SyntDesc): SyntBinOp.Create("+", RtypeNum(), lambda l,r: l + r, "SyntPlus") SyntBinOp.Create("-", RtypeNum(), lambda l,r: l - r, "SyntMinus") SyntBinOp.Create("*", RtypeNum(), lambda l,r: l * r, "SyntMult") -SyntBinOp.Create("/", RtypeNum(), lambda l,r: l / r, "SyntDiv") +SyntBinOp.Create("/", RtypeNum(), lambda l,r: l / r, "SyntDiv") # use ansi? SyntBinOp.Create("=", RtypeBool(), lambda l,r: l == r, "SyntEq") SyntBinOp.Create(">", RtypeBool(), lambda l,r: l > r, "SyntGt") +SyntBinOp.Create(">=", RtypeBool(), lambda l,r: l >= r, "SyntGte") # use ansi? +SyntBinOp.Create("<", RtypeBool(), lambda l,r: l < r, "SyntLt") +SyntBinOp.Create("<=", RtypeBool(), lambda l,r: l <= r, "SyntLte") # use ansi? +SyntBinOp.Create("and", RtypeBool(), lambda l,r: l and r, "SyntAnd") +SyntBinOp.Create("or", RtypeBool(), lambda l,r: l or r, "SyntOr") @RegStmt class SyntIf(SyntDesc): @@ -364,28 +408,29 @@ class SyntIf(SyntDesc): expr = SyntDesc.DefProp(0) blockIfTrue = SyntDesc.DefProp(1) blockIfFalse = SyntDesc.DefProp(2) + def Compile(self): + return [self.expr, self.Compare, self.PopCtx] + def Compare(self, vm): + val = vm.Pop() + vm.vars.Push() # lexical scoping, bitches + if val: + return self.blockIfTrue + else: + return self.blockIfFalse + def PopCtx(self, vm): + vm.vars.Pop() -# scope -- a block? a scril? (scril lets you specifically avoid vars that are defined after a certain point) + game? -# method: rgvar(stype) -- returns a list of variables in scope that match the current stype -# var -- a variable -# contains: stype, sargInitializer -# a var IS A SCRIL -# the scmd creates a new vari in the current ectx, and adds the scril to a map of things that refer to it -# sproj -- an object that can project a sarg into an pw -# stypes should be sprojectors and express a default projection as a class method (why?) -# MiSarg -- an editor for sargs -# MiSargNum, MiSargDrop -# MiScriptLine -- projects a scriptline +@RegStmt +class SyntSet(SyntDesc): + desc = [["Set"], " ", (SyntVarRef, 1), " to ", (SyntExpr, 1)] + lvalue = SyntDesc.DefProp(0) + expr = SyntDesc.DefProp(1) + 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. - -# execution: -# ectx -- execution context -# contains: mpvar_sobj -# block -# iscril (selection?) -# ectxReturn -# methods: call(block) class Fail(TPrs): def InitPersistent(self, stFailure): @@ -858,7 +903,7 @@ class PselState(Token): class GameScriptTest(Game): def InitPersistent(self): Game.InitPersistent(self) - self.block = SyntBlock(None) + self.block = SyntBlock() def GetRgclsTokTrans(self): return [[PselState, self.block], [AutoJoiner, [Pov, self.block]]] diff --git a/vm.py b/vm.py new file mode 100644 index 0000000..5deb9ab --- /dev/null +++ b/vm.py @@ -0,0 +1,69 @@ +from tpers import * +from scripting import Fail, Synt + +class Vars(TPrs): + def InitPersistent(self): + self.rgmpsynt_sobj = [{}] # [0] is for globals? + + def Get(self, synt): + for mpsynt_sobj in reversed(self.rgmpsynt_sobj): + try: + return mpsynt_sobj[synt] + except: + pass + return Fail("Variable " + synt.name.St() + " not defined") + + def Define(self, synt, sobj): + self.rgmpsynt_sobj[-1][synt] = sobj + + def Set(self, synt, sobj): + for mpsynt_sobj in reversed(self.rgmpsynt_sobj): + if synt in mpsynt_sobj: + mpsynt_sobj[synt] = sobj + return + return Fail("Variable " + synt.name.St() + " not defined") + + def Push(self): + self.rgmpsynt_sobj.append({}) + + def Pop(self): + self.rgmpsynt_sobj.pop() + +class Vm(TPrs): + def InitPersistent(self): + self.vstack = [] + self.rstack = [] + self.vars = Vars() + + def Push(self, sobj): + self.vstack.append(sobj) + def Pop(self): + return self.vstack.pop() + + def CallI(self, synt, r, ip): + if ip < len(r): # tail call optimization + self.rstack.append(r, ip) + return (synt.Compile(), 0) + + def RunSynt(self, synt): + r = synt.Compile() + ip = 0 + while True: + if ip < len(r): + instr = r[ip] + ip += 1 + if isinstance(instr, Synt): + (r, ip) = self.CallI(instr, r, ip) + elif callable(instr): + synt = instr(self) + if synt != None: + (r, ip) = self.CallI(instr, r, ip) + else: + assert False, "invalid opcode" + else: + if len(rstack) == 0: + break + (r, ip) = self.rstack.pop() + + + \ No newline at end of file