diff --git a/script-thoughts.txt b/script-thoughts.txt new file mode 100644 index 0000000..c158780 --- /dev/null +++ b/script-thoughts.txt @@ -0,0 +1,47 @@ +How to refer to things + +==== +MarMOTS: set model? +when sending a message: send to all in set (useful? or explicit foreach better?) +when asking a question: ask a random member? if != 1 I think this is almost never what you really want. +==== +1) construct model: +all values (except for constants) come from some class of object, of which there are always only small number available to you +the instance of the class that you query is chosen by narrowing the set by "picking" in the event conditions + +pros of picking: + = lets you say when(anything) and have it make sense + = if there is only one of something you always get it right +cons: + = not always an easy way to express what you want + +2) game maker: +self, other, pick +-- gml has the set model! +other == other involved in collision + +============= +namespaces: + +---------------------------------------------|-------------- + | * System * + | Thing + | Whatsit + | Enemy + | ... + | variable? + +some core stuff should be global (if, expr, true, false) +I keep forgetting that this kind of interface suggests type inference + +what is a type? it's +- number +- string (ansi) +- boolean (yes/no) +- object<|x,y,z|> + - with mixins x, y, z + object<|x|> is a subtype of object<|x,y|> +- list +- void +- t + diff --git a/scripting.py b/scripting.py index 268dbef..0c2a87a 100644 --- a/scripting.py +++ b/scripting.py @@ -2,6 +2,308 @@ from engine import * from basetoken import * from tpers import * from util import * +from contextlib import contextmanager + +def RegStmt(clsStmt): + SyntLine.rgclsStmt.append(clsStmt) + return clsStmt + +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): + rgclsRtype = [] + @classmethod + def RtypeMax(cls): + return cls() + def RglitFromSt(self, st): + return [] + def RgsyntFromSt(self, syntBlock, st): + for lit in self.RglitFromSt(st): + yield SyntLit(None, lit, self) + + while syntBlock != None: + if isinstance(syntBlock, SyntBlock): + for synt in self.RgsyntFromSyntBlock(syntBlock, st): + yield synt + syntBlock = syntBlock.syntParent + + # helper for RgsyntFromSt + def RgsyntFromSyntBlock(self, syntBlock, st): + # yield all vars in scope + for syntLine in syntBlock.rgsynt: + if SyntVar.FVarOfType(syntLine.rgsynt[0], self): + yield SyntVarRef(None, syntLine.rgsynt[0]) + + def StForSobj(self, sobj): + return str(sobj) + def FMember(self, sobj): + return isinstance(sobj, self.pythontype) + def FOverlap(self, rtype): + return isinstance(rtype, self.__class__) + +@RegRtype +class RtypeBool(Rtype): + pythontype = bool + def RglitFromSt(self, st): + if "true".startswith(st.lower()): + yield True + if "false".startswith(st.lower()): + yield False +@RegRtype +class RtypeNum(Rtype): + pythontype = float + def RglitFromSt(self, st): + try: + yield float(st) + except: + pass +@RegRtype +class RtypeString(Rtype): + pythontype = str + def RglitFromSt(self, st): + if st.startswith('"') or st.startswith("'"): + yield st[1:] + +class RtypeAny(Rtype): + def RgsyntFromSt(self, syntBlock, st): + for clsRtype in self.rgclsRtype: + rtype = clsRtype.RtypeMax() + for synt in rtype.RgsyntFromSt(syntBlock, st): + yield synt + def FMember(self, sobj): + return True + def FOverlap(self, rtype): + return True + +class Typeable(TPrs): + def InitTransient(self): + self.steditTypein = Stedit() + def GetStTypein(self): + st = self.steditTypein.GetValue() + return None if st == "" else st + def SetStTypein(self, stTypein): + self.steditTypein.SetValue(stTypein or "") + 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 + self.rgsynt = [] + self.Populate() + + @classmethod + def SyntDefault(cls, syntParent): + return cls(syntParent) + + def ProjectTypeinForChild(self, pwParent, syntChild, st): + def OnSelect(synt, psel): + self.Replace(syntChild, synt) + syntChild.SetStTypein(None) + if syntChild.GetStTypein() != None: + pwParent = PwDropdown(pwParent) + PwTypein(pwParent, st, syntChild) + if syntChild.GetStTypein() != None: + for synt in self.RgsyntForChild(syntChild, syntChild.GetStTypein()): + PwButton(pwParent, synt.StForTypein(), synt, OnSelect) + + def RgsyntForChild(self, syntChild, st): + return [] + + def Replace(self, syntOld, syntNew): + self.rgsynt[self.Isynt(syntOld)] = syntNew + syntNew.syntParent = self + + def Isynt(self, syntChild): + if isinstance(syntChild, int): + return syntChild + return self.rgsynt.index(syntChild) + + def Rtype(self): + return None + + def RtypeTopForChild(self, syntChild): + return None + + def Eval(self, ectx): + pass + + def Populate(self): + pass + + def Project(self, pcur): + pass + +# desc - list of desces +# desce: +# string: "foo" -- literal uneditable text +# list: ["foo"] -- text to edit the current synt +# tuple: (clsSynt, type) -- child description +# clsSynt: class of child synt +# type: number - same as another child expression with that number +# None - no rtype specified / possible +# tuple (number, rtypeTop) - number == same as another child expression with that number, rtypeTop = required type +class SyntDesc(Synt): + @classmethod + def StForTypein(cls): + for desce in cls.desc: + if isinstance(desce, list) and len(desce) == 1: + return desce[0] + + @staticmethod + def DefProp(isynt): + def get(self): + return self.rgsynt[isynt] + def set(self, syntNew): + self.Replace(isynt, syntNew) + def delete(self): + self.Replace(isynt, None) + return property(get, set, delete) + + def Populate(self): + for desce in self.desc: + if isinstance(desce, tuple): + synt = desce[0].SyntDefault(self) + self.rgsynt.append(synt) +# for synt in self.rgsynt: +# synt.Populate() + + def SyntProperty(self, isynt): + def get(): + return self.rgsynt[isynt] + def set(syntNew): + self.Replace(isynt, syntNew) + def delete(): + self.Replace(isynt, None) + return property(get, set, delete) + + def RtypeTopForChild(self, syntChild): + isyntChild = self.Isynt(syntChild) + isyntDesce = 0 + for desce in self.desc: + if isinstance(desce, tuple): + if isyntDesce == isyntChild: + if isinstance(desce[1], tuple): + return desce[1][0] + return None + isyntDesce += 1 + return None + + def Project(self, pcur): + "adds more horizontal pws to a horizontal pw" + isynt = 0 + for desce in self.desc: + if isinstance(desce, str): + PwStatic(pcur.PwHoriz(self), desce) + elif isinstance(desce, list): + # selection for the synt itself + self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, desce[0]) + elif isinstance(desce, tuple): + # embedded syntax + self.rgsynt[isynt].Project(pcur) + isynt += 1 + +class SyntName(Synt): + def Project(self, pcur): + PwTypein(pcur.PwHoriz(self), self.GetStTypein(), self) + +class SyntExpr(Synt): + def Project(self, pcur): + self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, " ") + +class SyntLine(Synt): + rgclsStmt = [] + def Populate(self): + self.rgsynt.append(SyntBlank(self)) + def SetStmt(self, syntStmt): + self.Replace(self.rgsynt[0], syntStmt) + def Project(self, pcur): + pwKey = PwKeyHandler(pcur.pwVert, self.HandleKey) + with pcur.ProjectHoriz(pwKey, self): + self.rgsynt[0].Project(pcur) + + def RgsyntForChild(self, syntChild, st): + for clsStmt in self.rgclsStmt: + if clsStmt.StForTypein().lower().startswith(st.lower()): + yield clsStmt(None) + + def HandleKey(self, pwKey, pov, psel, key): + if ansi.FEnter(key): + self.syntParent.InsertLineAfter(self, None) + psel.Inc(pwKey.PwParent()) + return True + return False + +class SyntBlock(Synt): + def Project(self, pcur): + with pcur.Indent(2 if pcur.pwHoriz != None else 0): + pcur.EndLine() + if pcur.pwVert == None: + pcur.pwVert = PwBlock(None, self, pcur.dxindent) + def OnInsertNewLine(syntAfter, psel): + self.InsertLineAfter(syntAfter, None) + psel.Inc(pcur.pwVert) + PwButtonHidden(pcur.pwVert, "[insert new line]", None, OnInsertNewLine, pcur.dxindent) + for syntLine in self.rgsynt: + syntLine.Project(pcur) + + def InsertLineAfter(self, syntAfter, syntStmt): + syntLine = SyntLine(self) + if syntStmt != None: + syntLine.SetStmt(syntStmt) + if syntAfter == None: + self.rgsynt.insert(0, syntLine) + else: + self.rgsynt.insert(self.rgsynt.index(syntAfter) + 1, syntLine) + return syntLine + +class SyntLit(Synt): + def InitPersistent(self, syntParent, value, rtype): + Synt.InitPersistent(self, syntParent) + self.value = value + self.rtype = rtype + + def Rtype(self): + return rtype + +@RegStmt +class SyntBlank(SyntDesc): + desc = [[" "]] + +@RegStmt +class SyntVar(SyntDesc): + desc = [["Define"], " ", (SyntName, None), " to be ", (SyntExpr, 1)] + name = SyntDesc.DefProp(0) + expr = SyntDesc.DefProp(1) + @classmethod + def FVarOfType(cls, synt, rtype): + return isinstance(synt, cls) and synt.expr != None and rtype.FOverlap(expr.Rtype()) + +class SyntVarRef(Synt): + def __init__(self, syntParent, syntVar = None): + Synt.__init__(self, syntParent) + self.syntVar = syntVar + def Rtype(self): + return self.syntVar.Rtype() + +@RegStmt +class SyntIf(SyntDesc): + desc = [["If"], " ", (SyntExpr, (1, RtypeBool)), ", then:", (SyntBlock, None), "or else:", (SyntBlock, None)] + expr = SyntDesc.DefProp(0) + blockIfTrue = SyntDesc.DefProp(1) + blockIfFalse = SyntDesc.DefProp(2) # 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 @@ -24,266 +326,46 @@ from util import * # iscril (selection?) # ectxReturn # methods: call(block) - -class Block(TPrs): - def InitPersistent(self, scrilParent): - self.rgscril = [] - self.scrilParent = scrilParent - def InsertScrilAfter(self, scrilAfter, scmd = None): - scrilNew = Scril(self, scmd) - if scrilAfter == None: - self.rgscril.insert(0, scrilNew) - else: - self.rgscril.insert(self.rgscril.index(scrilAfter) + 1, scrilNew) - return scrilNew - def Project(self, pwBlock, dxIndent = 0): - if pwBlock == None: - pwBlock = PwBlock(None, self, 0) - PwButtonHidden(pwBlock, "[insert new line]", None, self.InsertScrilAfter, dxIndent) - for scril in self.rgscril: - scril.Project(pwBlock, dxIndent) - return pwBlock - -class Typeable(TPrs): - def InitTransient(self): - self.steditTypein = Stedit() - def GetStTypein(self): - st = self.steditTypein.GetValue() - return None if st == "" else st - def SetStTypein(self, stTypein): - self.steditTypein.SetValue(stTypein or "") - def HandleTypeinKey(self, key): - return self.steditTypein.HandleKey(key) -# Sarg -- pointer to scripting object / argument to a command -# this object type appears to exist solely so that MiSarg can edit something without digging through a list -class Sarg(Typeable): - def InitPersistent(self, stype, scril): - self.stype = stype - self.sobj = stype.SobjDefault(scril) - - def FExpr(self): - "Returns true if the sobj must be evaluated to get a result." - return self.sobj.__class__ == Scril - - def Eval(self, ectx): - if self.FExpr(): - return self.sobj.Exec(ectx) - else: - return self.sobj - class Fail(TPrs): def InitPersistent(self, stFailure): self.stFailure == stFailure -# stype -- a description of a sobj (scripting object) -# method: FMember(sobj) -- returns true if an object is in the set of things that this stype represents -# method: FOverlaps(stype) -- returns true if there is some overlap between the two stypes -# -- there is no subtype/supertype relation because stypes are used to help the user, -# and I suspect being specific in this manner is too much of a burden to place on them. -# methods: rgsarg(scope) -- returns a list of all legal objects that exist in the current scope -# this includes functions which return things of this stype, and variables -# literals are not included -# project(cls, game, sarg) -- returns an MiSarg which displays and edits (default sproj for a sarg?) -class Stype(object): - """A definition of a scripting object type. All methods are class methods; stypes are never instantiated.""" - def __init__(self): - raise "do not instantiate stypes" - @classmethod - def FMember(cls, sobj): - return isinstance(sobj, cls.pythontype) - @classmethod - def FOverlaps(cls, stype): - return stype == self - @classmethod - def Rgsobj(cls, scope): - return [] - @classmethod - def Rgsobj_st(cls, scope): - return [(sobj, str(sobj)) for sobj in cls.Rgsobj(scope)] - @classmethod - def OnSelect(cls, sarg, sobj): - sarg.sobj = sobj - @classmethod - def Sproj(cls, sarg, pwBlock, pwScril): - ProjectTypein(pwScril, sarg, str(sarg.sobj), cls.Rgsobj_st, cls.OnSelect) - return False - -def ProjectTypein(pwParent, typeable, st, dgRgsobj_st, dgOnSelect): - def OnSelect(sobj): - dgOnSelect(typeable, sobj) - typeable.SetStTypein(None) - if typeable.GetStTypein() != None: - pwParent = PwDropdown(pwParent) - PwTypein(pwParent, st, typeable) - if typeable.GetStTypein() != None: - for sobj, st in dgRgsobj_st(typeable): - PwButton(pwParent, st, sobj, OnSelect) +# projection cursor +class Pcur(object): + @staticmethod + def PwProjected(synt): + pcur = Pcur() + synt.Project(pcur) + return pcur.pwVert -class StypeBool(Stype): - pythontype = bool - @classmethod - def SobjDefault(cls, scril): - return False - @classmethod - def Rgsobj(cls, scope): - return [True, False] + def __init__(self): + self.pwVert = None + self.pwHoriz = None + self.dxindent = 0 -class StypeBlock(Stype): - pythontype = Block - @classmethod - def SobjDefault(cls, scril): - return Block(scril) - @classmethod - def Sproj(cls, sarg, pwBlock, pwScril): - sarg.sobj.Project(pwBlock, pwScril.DxIndent() + 2) - return True + @contextmanager + def Indent(self, dxindent = 2): + self.dxindent += dxindent + yield + self.dxindent -= dxindent + + @contextmanager + def ProjectHoriz(self, pwParent, synt): + self.ProjectHorizI(pwParent, synt) + yield + self.EndLine() + + def PwHoriz(self, synt): + if self.pwHoriz == None: + self.ProjectHorizI(self.pwVert, synt) + return self.pwHoriz -# scmd -- a definition of a scripting command -class Scmd(object): - """A definition of a scripting command. All methods are class methods; scmds are never instantiated.""" - def __init__(self): - raise "do not instantiate scmds" - - # used by RegScmd to build a list of all valid scmds for editing - rgscmd = [] - - @classmethod - def Rgscmd_st(cls, scril): - return [(scmd, scmd.StName()) for scmd in cls.rgscmd] - @classmethod - def Desc(cls, *rgsarg): - """ - Returns a list of desces. A desce can be a constant string, representing nonselectable helper text, - an sproj, a list containing an sproj and a reference to a passed-in sarg, which explicitly binds that - sproj to that sarg, or a list containing a string, which stands for the selectable text which represents - the scril itself. - ie, [["Move"], " ", [SprojMovable, rgsarg[1]], " to ", [SprojPosition, rgsarg[0]]] - If a bare sproj is given, bind it to the first unbound sarg (after processing all explicitly bound sargs). - - This method must accept invalid sargs; it is the sproj's job to relay the invalidness to the user. - """ - return cls.desc - - stName = None - - @classmethod - def StName(cls): - "Returns the name that the user types in to select this scmd." - if cls.stName != None: - return cls.stName - for desce in cls.Desc(Scril(None, cls).rgsarg): - if type(desce) == list and type(desce[0]) == str: - return desce[0] - assert False, "no selectable self in desc" - - @classmethod - def Csarg(cls): - "Returns the number of arguments that this command takes." - return len(cls.rgstypeArg) - @classmethod - def StypeResult(cls): - return cls.stypeResult - @classmethod - def RgstypeArg(cls): - return cls.rgstypeArg - -def RegScmd(stypeResult, *rgstypeArg): - def RegI(cls): - cls.stypeResult = stypeResult - cls.rgstypeArg = rgstypeArg - Scmd.rgscmd.append(cls) - return cls - return RegI - -@RegScmd(None) -class ScmdBlank(Scmd): - stName = "" - desc = [[" "]] - @classmethod - def Exec(cls, ectx): - pass - -@RegScmd(None) -class ScmdHelloWorld(Scmd): - "Hello, world!" - desc = [["Hello World"], "!"] - @classmethod - def Exec(cls, ectx): - print "Hello, world!" - -@RegScmd(None, StypeBool, StypeBlock, StypeBlock) -class ScmdIf(Scmd): - desc = [["If"], " ", StypeBool.Sproj, ", then:", StypeBlock.Sproj, "or else:", StypeBlock.Sproj] - @classmethod - def Exec(cls, ectx, sargExpr, sargIfTrue, sargIfFalse): - if sargExpr.Eval(ectx): - print "true!" - -# needs work - #=========================================================================== - # @RegScmd(None, StypeSt, StypeObj) - # class ScmdVar(Scmd): - # desc = ["Define a new ", ["variable"], " called \"", StypeSt, "\", set to ", StypeObj, "."] - # @classmethod - # def Exec(cls, ectx, sargStName, sargInit): - # ectx.mpvar_sobj[ectx.ScrilCurr()] = sargInit.Eval() - #=========================================================================== - -class Scril(Typeable): - "A line in a script." - def InitPersistent(self, blockParent, scmd = None): - self.scmd = scmd or ScmdBlank - self.rgsarg = [Sarg(stypeArg, self) for stypeArg in self.scmd.RgstypeArg()] - self.blockParent = blockParent - - def SetScmd(self, scmd): - if (scmd.Csarg() > self.scmd.Csarg()): - self.rgsarg.extend([Sarg(stypeArg, self) for stypeArg in scmd.RgstypeArg()[self.scmd.Csarg():]]) - elif (scmd.Csarg() < self.scmd.Csarg()): - self.rgsarg = self.rgsarg[:scmd.Csarg()] - self.scmd = scmd - - def Exec(self, ectx): - return self.scmd.Exec(ectx, *self.rgsarg) - - def HandleKey(self, pwKey, pov, psel, key): - if ansi.FEnter(key): - self.blockParent.InsertScrilAfter(self, ScmdBlank) - return True - return False - def Project(self, pwBlock, dxindent): - "adds horizontal pws to a vertical pw" - pwScril = None + def EndLine(self): + self.pwHoriz = None - desc = self.scmd.Desc(*self.rgsarg) - rgsargUnbound = list(self.rgsarg) - #filter bound sargs - for desce in desc: - if isinstance(desce, list) and len(desce) > 1: - rgsargUnbound.remove(desce[1]) - - fNewScril = True - for desce in desc: - if fNewScril: - pwKey = PwKeyHandler(pwBlock, self.HandleKey) - pwScril = PwList(pwKey, self, dxindent) - fNewScril = False - if isinstance(desce, str): - PwStatic(pwScril, desce) - elif isinstance(desce, list): - if isinstance(desce[0], str): - # selection for the scmd / scril - ProjectTypein(pwScril, self, desce[0], Scmd.Rgscmd_st, Scril.SetScmd) - else: - # mapping to sarg - fNewScril = self.ProjectSarg(pwBlock, pwScril, desce[0], desce[1]) - else: - # sproj - fNewScril = self.ProjectSarg(pwBlock, pwScril, desce, rgsargUnbound.pop(0)) - - def ProjectSarg(self, pwBlock, pwScril, sproj, sarg): - return sproj(sarg, pwBlock, pwScril) + def ProjectHorizI(self, pwParent, synt): + self.pwHoriz = PwList(pwParent, synt, self.dxindent) # pw - projected widget # Nothing ever depends on the object identity or permanence of a pw! @@ -295,6 +377,12 @@ class Pw(object): if pwParent != None: pwParent.AddChild(self) self.pwParent = pwParent + def PrintTree(self, dxindent = 0): + print (" " * dxindent) + self.StDebug() + for pwChild in self.RgpwChild(): + pwChild.PrintTree(dxindent + 2) + def StDebug(self): + return self.__class__.__name__ def PwParent(self): return self.pwParent def Value(self): @@ -326,11 +414,32 @@ class PwContainer(Pw): return self.rgpwContents def FContainer(self): return True + +class PwContainOne(Pw): + def AddChild(self, pw): + self.pwChild = pw + def RgpwChild(self): + return [self.pwChild] + def FContainer(self): + return True +class PwExpand(PwContainOne): + def DxDyNew(self, w, dxStart, mpksel): + dxdy = self.pwChild.DxDyNew(w, dxStart, mpksel) + if dxdy[0] == 0 and dxdy[1] > 0: + return dxdy + return (0, dxdy[0] + 1) + def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay): + if not fOverlay: + ascr.PutSt(" " * w, x, y, self.pwChild.ColFg(), self.pwChild.ColBg(mpksel.Get(self.pwChild))) + self.pwChild.Draw(ascr, x, y, w, dxStart, mpksel, fOverlay) + class PwStatic(Pw): def __init__(self, pwParent, stText): Pw.__init__(self, pwParent) self.stText = stText + def StDebug(self): + return Pw.StDebug(self) + " '" + self.StTextDisplay(Ksel.NAV) + "'" def DxDyNew(self, w, dxStart, mpksel): rgsnippet = self.Rgsnippet(w, dxStart, mpksel) if len(rgsnippet) == 1: @@ -414,7 +523,7 @@ class PwButton(PwStatic): return ansi.BLUE | ansi.FBRIGHT def HandleKey(self, pov, psel, key): if key == ansi.K_RETURN: - self.dgEnter(self.Value()) + self.dgEnter(self.Value(), psel) return True return False @@ -632,7 +741,7 @@ class Pov(TokenClient): self.pselstate = self.game.rgtoken("pselstate")[0] def PwProjected(self): - return self.block.Project(None) + return Pcur.PwProjected(self.block) def run(self): while True: @@ -662,7 +771,7 @@ class PselState(Token): del self.mpclient_psel[client] def OnJoin(self, client): print "client joined,", client - self.mpclient_psel[client] = Psel(self.block.Project(None).PwFirstSelectable(), Ksel.NAV) + self.mpclient_psel[client] = Psel(Pcur.PwProjected(self.block).PwFirstSelectable(), Ksel.NAV) class Mpksel(object): def __init__(self, pwRoot, client, mpclient_psel): self.mppw_ksel = {} @@ -689,12 +798,7 @@ class PselState(Token): class GameScriptTest(Game): def InitPersistent(self): Game.InitPersistent(self) - self.block = Block(None) - self.block.InsertScrilAfter(None, ScmdHelloWorld) - self.block.InsertScrilAfter(None, ScmdBlank) - self.block.InsertScrilAfter(None, ScmdHelloWorld) - self.block.InsertScrilAfter(None, ScmdIf) - self.block.InsertScrilAfter(None, ScmdHelloWorld) + self.block = SyntBlock(None) def GetRgclsTokTrans(self): return [[PselState, self.block], [AutoJoiner, [Pov, self.block]]]