marmots/vm.py

95 lines
3.3 KiB
Python

# This file is part of MarMOTS.
#
# MarMOTS is free software: you can redistribute it and/or modify it under the terms of the GNU Affero
# General Public License as published by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# MarMOTS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
# Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License along with MarMOTS. If not,
# see <https://www.gnu.org/licenses/>.
#
# Copyright 2009, 2010, 2011, 2020 Jeremy Penner
from tpers import *
from scripting import Fail
class Vars(TPrs):
def InitPersistent(self):
self.rgmpsynt_sobj = [{}] # [0] is for globals?
def Get(self, syntVar, synt):
for mpsynt_sobj in reversed(self.rgmpsynt_sobj):
try:
return mpsynt_sobj[syntVar]
except:
pass
return Fail("Variable " + synt.name.St() + " not defined", synt)
def Define(self, synt, sobj):
self.rgmpsynt_sobj[-1][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 syntVar in mpsynt_sobj:
mpsynt_sobj[syntVar] = sobj
return
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 (rNew, 0)
def RunSynt(self, synt):
r = synt.Compile()
ip = 0
while True:
if ip < len(r):
instr = r[ip]
ip += 1
if hasattr(instr, "Compile"):
(r, ip) = self.CallI(instr, r, ip)
elif callable(instr):
synt = instr(self)
if synt != None:
(r, ip) = self.CallI(synt, r, ip)
else:
assert False, "invalid instr " + str(instr)
else:
if len(self.rstack) == 0:
break
(r, ip) = self.rstack.pop()