From bae69502bdced7776372bc3147d22ae4f5cca1e9 Mon Sep 17 00:00:00 2001 From: cecilkorik Date: Tue, 7 Jun 2011 19:24:31 +0000 Subject: [PATCH] added optimizer and got language/VM mostly working --HG-- branch : mung --- builtins.py | 153 ++++++++++++++++++----------------- builtins_code.py | 10 +++ database.py | 192 ++++++++++++++++++++++++++++++++++++++++++++ database_dsdb.py | 2 +- database_fake.py | 11 +-- language.py | 53 ++++++++++--- listener.py | 75 +++++++++++++++++- optimizer.py | 54 +++++++++++++ parse.py | 18 ++++- server.py | 24 +++++- test.moo | 8 ++ tt.py | 9 +++ virtualmachine.py | 198 ++++++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 708 insertions(+), 99 deletions(-) create mode 100755 builtins_code.py create mode 100755 database.py create mode 100755 optimizer.py create mode 100755 test.moo create mode 100755 tt.py create mode 100755 virtualmachine.py diff --git a/builtins.py b/builtins.py index ed6c750..c5dc5b8 100755 --- a/builtins.py +++ b/builtins.py @@ -1,104 +1,113 @@ +from builtins_code import bi builtin_map = {} -# decorator for builtin function with "n" args -def bi(func, n): - def builtin_varg(vm): - args = vm.pop(n) - rv = func(args) - vm.push(rv) - return builtin_varg + """ # manage properties builtin_map.update({ - 'properties': bi_properties, - 'add_property': bi_add_property, - 'delete_property': bi_delete_property, - 'clear_property': bi_clear_property, - 'set_property_opts': bi_set_property_opts, - 'get_property_opts': bi_get_property_opts, - 'set_property_owner': bi_set_property_owner, - 'get_property_owner': bi_get_property_owner, + 'properties': bi.properties, + 'add_property': bi.add_property, + 'delete_property': bi.delete_property, + 'clear_property': bi.clear_property, + 'set_property_opts': bi.set_property_opts, + 'get_property_opts': bi.get_property_opts, + 'set_property_owner': bi.set_property_owner, + 'get_property_owner': bi.get_property_owner, }) # manage files builtin_map.update({ - 'files': bi_files, - 'add_file': bi_add_file, - 'delete_file': bi_delete_file, - 'set_file_opts': bi_set_file_opts, - 'get_file_opts': bi_get_file_opts, - 'set_file_owner': bi_set_file_owner, - 'get_file_owner': bi_get_file_owner, + 'files': bi.files, + 'add_file': bi.add_file, + 'delete_file': bi.delete_file, + 'set_file_opts': bi.set_file_opts, + 'get_file_opts': bi.get_file_opts, + 'set_file_owner': bi.set_file_owner, + 'get_file_owner': bi.get_file_owner, }) # manage functions builtin_map.update({ - 'functions': bi_functions, - 'add_function': bi_add_function, - 'delete_function': bi_delete_function, - 'set_function_code': bi_set_function_code, - 'get_function_code': bi_get_function_code, - 'set_function_opts': bi_set_function_opts, - 'get_function_opts': bi_get_function_opts, - 'set_function_args': bi_set_function_args, - 'get_function_args': bi_get_function_args, - 'set_function_owner': bi_set_function_owner, - 'get_function_owner': bi_get_function_owner, + 'functions': bi.functions, + 'add_function': bi.add_function, + 'delete_function': bi.delete_function, + 'set_function_code': bi.set_function_code, + 'get_function_code': bi.get_function_code, + 'set_function_opts': bi.set_function_opts, + 'get_function_opts': bi.get_function_opts, + 'set_function_args': bi.set_function_args, + 'get_function_args': bi.get_function_args, + 'set_function_owner': bi.set_function_owner, + 'get_function_owner': bi.get_function_owner, }) # server configuration builtin_map.update({ - 'set_server_var': bi_set_server_var, - 'get_server_var': bi_get_server_var, - 'save_database': bi_save_database, - 'shutdown': bi_shutdown, - 'server_info': bi_server_info, - 'time': bi_time, - 'format_time': bi_format_time, + 'set_server_var': bi.set_server_var, + 'get_server_var': bi.get_server_var, + 'save_database': bi.save_database, + 'shutdown': bi.shutdown, + 'server_info': bi.server_info, + 'time': bi.time, + 'format_time': bi.format_time, }) # manage running tasks builtin_map.update({ - 'set_perms': bi_set_perms, - 'get_perms': bi_get_perms, - 'task_id': bi_task_id, - 'kill_task': bi_kill_task, - 'eval': bi_eval, - 'fork': bi_fork, - 'sleep': bi_sleep, - 'resume': bi_resume, - 'raise': bi_raise, - 'stack': bi_stack, + 'set_perms': bi.set_perms, + 'get_perms': bi.get_perms, + 'task_id': bi.task_id, + 'kill_task': bi.kill_task, + 'eval': bi.eval, + 'fork': bi.fork, + 'sleep': bi.sleep, + 'resume': bi.resume, + 'raise': bi.raise, + 'stack': bi.stack, }) # manage objects builtin_map.update({ - 'create': bi_create, - 'destroy': bi_destroy, - 'set_parent': bi_set_parent, - 'parent': bi_parent, - 'children': bi_children, - 'set_owner': bi_set_owner, - 'owner': bi_owner, - 'max_object': bi_max_object, - 'renumber': bi_renumber, + 'create': bi.create, + 'destroy': bi.destroy, + 'set_parent': bi.set_parent, + 'parent': bi.parent, + 'children': bi.children, + 'set_owner': bi.set_owner, + 'owner': bi.owner, + 'max_object': bi.max_object, + 'renumber': bi.renumber, }) # manage connections and ports builtin_map.update({ - 'connect': bi_connect, - 'disconnect': bi_disconnect, - 'open_connection': bi_open_connection, - 'close_connection': bi_close_connection, - 'incoming_connections': bi_incoming_connections, - 'outgoing_connections': bi_outgoing_connections, - 'get_connection_info': bi_get_connection_info, - 'set_connection_opts': bi_set_connection_opts, - 'send': bi_send, - 'recv': bi_recv, - 'listen': bi_listen, - 'unlisten': bi_unlisten, - 'setuid': bi_setuid, + 'connect': bi.connect, + 'disconnect': bi.disconnect, + 'open_connection': bi.open_connection, + 'close_connection': bi.close_connection, + 'incoming_connections': bi.incoming_connections, + 'outgoing_connections': bi.outgoing_connections, + 'get_connection_info': bi.get_connection_info, + 'set_connection_opts': bi.set_connection_opts, + 'send': bi.send, + 'recv': bi.recv, + 'listen': bi.listen, + 'unlisten': bi.unlisten, + 'setuid': bi.setuid, + }) +""" + +builtin_map.update({ + 'serverlog': bi.serverlog, + }) + +""" +# builtin functions for list types +listbi_map = {} +listbi_map.update({ + 'length': bi.list_len, + 'len': bi.list_len, + 'sort': bi.list_sort, }) """ \ No newline at end of file diff --git a/builtins_code.py b/builtins_code.py new file mode 100755 index 0000000..8c15506 --- /dev/null +++ b/builtins_code.py @@ -0,0 +1,10 @@ + +class builtin_functions(object): + def __init__(self): + pass + + @staticmethod + def serverlog(vm, args): + print "serverlog: %s" % (args,) + +bi = builtin_functions() \ No newline at end of file diff --git a/database.py b/database.py new file mode 100755 index 0000000..3d3922f --- /dev/null +++ b/database.py @@ -0,0 +1,192 @@ +from parse import static_parser + +class ObjRef(object): + def __init__(self, objnum): + self.objnum = objnum + + def __eq__(self, other): + return self.objnum == other.objnum + + +class DBObject(object): + def __init__(self, objref): + self.obj = objref + self.parent = ObjRef(-1) + self.children = [] + self.props = {} + self.funcs = [] + + def match_local_func(self, name, flags=""): + for func in self.funcs: + skip = False + for f in flags: + if not f in o.flags: + skip = True + if not skip and func.match(name): + return func + return None + + + def get_property(self, prop): + if prop in self.props: + rv = self.props[prop] + else: + return None + + def add_func(self, name): + self.funcs.append(DBFunc(self.obj, name)) + + + +class DBFunc(object): + def __init__(self, objref, name): + self.obj = objref + self.name = name + self.names = name.split(' ') + self.flags = "" + self.bytecode = [] + self.code = "" + + def compile(self, code): + try: + bytecode = static_parser.parse(code) + except ParseError: + return [0, "Compile error in line "] + + self.bytecode = bytecode + self.code = code + return [1, "Success"] + + def match(self, name): + for test in self.names: + if test[-1] == '*': + if name[:len(test)-1] == test[:-1]: + "partial match" + return True + elif test == name: + return True + + return False + + def ref(self): + return self.obj + + +class Database(object): + def __init__(self, dbname): + self.dbname = dbname + self.objects = [] + + def load(self): + raise NotImplementedError, "This function should be overridden by a derived class" + def checkpoint(self): + raise NotImplementedError, "This function should be overridden by a derived class" + def save(self): + raise NotImplementedError, "This function should be overridden by a derived class" + + def bootstrap_minimal(self): + so = DBObject(ObjRef(0)) + self.objects = [so] + + so. + + def set_property(self, obj, prop, val): + o = self.get(obj) + o.set_property(prop, val) + + def set_file(self, obj, fn, val): + o = self.get(obj) + o.set_file(fn, val) + + def get_property(self, obj, prop): + o = self.get(obj) + return o.get_property(prop) + + def get_file(self, obj, fn): + o = self.get(obj) + return o.get_file(fn) + + def create(self): + for i in xrange(len(self.objects)): + if self.objects[i] == None: + newref = ObjRef(i) + self.objects[i] = DBObject(newref) + return newref + + newref = ObjRef(len(self.objects)) + self.objects.append(DBObject(newref)) + return newref + + def destroy(self, ref): + if ref.objnum >= len(self.objects) or ref.objnum < 0: + raise ValueError, "Invalid object number" + + obj = self.objects[ref.objnum] + if obj == None: + raise ValueError, "Object already destroyed" + + for child in obj.children: + child.parent = ObjRef(-1) + + self.objects[ref.objnum] = None + self.objects_cleanup() + + def objects_cleanup(self): + i = len(self.objects) + while i > 0: + i -= 1 + if self.objects[i] != None: + i += 1 + break + + if i < len(self.objects): + self.objects[i:] = [] + + def get(self, objref): + return self.objects[objref.objnum] + + def match_command(self, player, cmd, vars): + vars['player'] = player + vars['caller'] = player + + match_order = [player] + for ref in match_order: + match = self.match_func(self, ref, cmd) + if match: + obj, func = match + vars['this'] = ref + vars['funcname'] = func.name + vars['funcobj'] = obj + return [func, vars] + return None + + def match_func(self, obj, funcname, flags=""): + o = self.get(obj) + func = o.match_func(funcname, flags) + if func != None: + return [obj, func] + ancestor = o.parent + while ancestor != ObjRef(-1): + o = self.get(ancestor) + func = o.match_func(funcname, flags) + if func != None: + return [ancestor, func] + ancestor = o.parent + return None + + def match_property(self, obj, propname): + o = self.get(obj) + prop = o.get_property(propname) + if prop != None: + return [obj, prop] + ancestor = o.parent + while ancestor != ObjRef(-1): + o = self.get(ancestor) + prop = o.get_property(propname) + if prop != None: + return [ancestor, prop] + ancestor = o.parent + return None + + + \ No newline at end of file diff --git a/database_dsdb.py b/database_dsdb.py index 80ca453..be39fd3 100644 --- a/database_dsdb.py +++ b/database_dsdb.py @@ -1,6 +1,6 @@ -class DSDB_Database(object): +class DSDB_Database(Database): def __init__(self, dbname): self.dbname = dbname self.fn = "%s.dsdb" % (dbname,) diff --git a/database_fake.py b/database_fake.py index e92cb6f..6cf9a69 100755 --- a/database_fake.py +++ b/database_fake.py @@ -3,14 +3,5 @@ class Fake_Database(object): def __init__(self, dbname): pass - def set_property(self, obj, prop, val): - pass - def set_file(self, obj, fn, val): - pass - def get_property(self, obj, prop): - pass - def get_file(self, obj, fn): - pass - - + \ No newline at end of file diff --git a/language.py b/language.py index 75dadd7..7633b7d 100644 --- a/language.py +++ b/language.py @@ -1,6 +1,7 @@ import sys import time from builtins import builtin_map +from database import ObjRef import bisect import optimizer import traceback @@ -65,6 +66,26 @@ def tokenparser(func): return newfunc +def disallow_keywords(tokens,keywords=None): + if keywords == None: + keywords = disallow_keywords.keywords + else: + keywords = set(keywords) + + for t in tokens: + if isinstance(t, VMIdent): + if t.name in keywords: + raise ParseException, "Restricted keyword: %s" % (t.name,) + elif isinstance(t, unicode): + tstr = t.encode('ascii', 'ignore') + if tstr in keywords: + raise ParseException, "Restricted keyword: %s" % (tstr,) + elif isinstance(t, str): + if t in keywords: + raise ParseException, "Restricted keyword: %s" % (t,) + +disallow_keywords.keywords = set('if,elseif,else,endif,try,except,finally,endtry,while,endwhile,continue,break,for,foreach,endfor'.split(',')) + def codejoin(*args): rv = [] for arg in args: @@ -103,10 +124,6 @@ def uncoerce(value): return value.value -class ObjRef(object): - def __init__(self, objnum): - self.objnum = objnum - class VMBaseObject(object): def __init__(self): self.pos = None @@ -161,7 +178,7 @@ class VMTable(VMType): class VMList(VMType): def __init__(self, value): VMType.__init__(self) - self.value = dict(value) + self.value = list(value) @staticmethod @tokenparser @@ -222,11 +239,12 @@ class VMIdent(VMRef): self.name = name def bytecode(self): - return [StackLiteral(self.name)] + return [StackLiteral(unicode(self.name))] @staticmethod @tokenparser def parse(tokens): + disallow_keywords(tokens) return VMIdent(tokens[0]) def __repr__(self): @@ -239,7 +257,7 @@ class VMVariable(VMRef): self.name = name def ref(self): - return [StackLiteral(self.name)] + return [StackLiteral(unicode(self.name))] def bytecode(self): return codejoin(self.ref(), GetVariable()) @@ -247,6 +265,7 @@ class VMVariable(VMRef): @staticmethod @tokenparser def parse(tokens): + disallow_keywords(tokens) return VMVariable(tokens[0]) def __repr__(self): @@ -388,15 +407,17 @@ class GetVariable(CodeOp): class CallBuiltin(CodeOp): def execute(self, vm): - funcname, = vm.pop(1) - builtin_map[funcname](vm) + funcname, args = vm.pop(2) + funcname = funcname.encode('ascii', 'ignore') + retval = builtin_map[funcname](vm, args) + vm.push(retval) @staticmethod @tokenparser def parse(tokens): - if not tokens[0] in builtin_map: - raise ParseException, 'Attempt to call undefined builtin function: "%s"' % (tokens[0],) - return codejoin(StackLiteral(VMString(tokens[0])), tokens[1], CallBuiltin()) + #if not tokens[0] in builtin_map: + # raise ParseException, 'Attempt to call undefined builtin function: "%s"' % (tokens[0],) + return codejoin(tokens[0], tokens[1], CallBuiltin()) class CallFunction(CodeOp): def execute(self, vm): @@ -555,7 +576,7 @@ class Assignment(CodeOp): class StackLiteral(StackCodeOp): def execute(self, vm): - vm.push(self.value) + vm.push(self.stack_value) @staticmethod @tokenparser @@ -563,6 +584,10 @@ class StackLiteral(StackCodeOp): return StackLiteral(tokens[0]) def __repr__(self): + try: + nv = coerce(self.stack_value) + except TypeError: + return "<** INVALID STACKLITERAL: %r **>" % (self.stack_value,) return "" % (coerce(self.stack_value),) @@ -585,6 +610,8 @@ class StackToList(CodeOp): @staticmethod @tokenparser def parse(tokens): + if not tokens: + return codejoin(StackLiteral(0), StackToList()) rv = codejoin(tokens[0][0], StackLiteral(len(tokens)), StackToList()) return rv diff --git a/listener.py b/listener.py index 1c92ae2..cae1d03 100644 --- a/listener.py +++ b/listener.py @@ -4,6 +4,65 @@ import select import random from ringbuffer import RingBuffer +class Poller(object): + POLLIN = 1 + POLLERR = 2 + POLLRD = 3 # POLLIN | POLLERR + POLLWR = 4 + + def __init__(self): + try: + self.mode = select.poll() + Poller.POLLIN = select + Poller.POLLRD = select.POLLIN|select.POLLPRI|select.POLLHUP|select.POLLERR|select.POLLNVAL + Poller.POLLWR = select.POLLOUT + except: + self.mode = select.select + Poller.POLLIN = 1 + Poller.POLLERR = 2 + Poller.POLLRD = Poller.POLLIN|Poller.POLLERR + Poller.POLLWR = 4 + + self.registered = {} + + def register_read(self, listener, fd): + if not fd in self.registered: + self.registered[fd.fileno()] = [0, listener, fd] + self.registered[fd.fileno()][0] |= Poller.POLLRD + if self.mode != select.select: + self.mode.register(fd.fileno(), Poller.POLLRD) + + def register_write(self, listener, fd): + if not fd in self.registered: + self.registered[fd.fileno()] = [0, listener, fd] + self.registered[fd.fileno()][0] |= Poller.POLLWR + if self.mode != select.select: + self.mode.register(fd.fileno(), Poller.POLLRD|Poller.POLLWR) + + def unregister(self, fd): + if self.mode == select.select and fd in self.registered: + del self.registered[fd.fileno()] + else: + self.mode.unregister(fd.fileno()) + + def get_fd(self, fd, flags): + return (self.registered[fd][1], self.registered[fd][2], flags) + + def poll(self, timeout): + if self.mode == select.select: + rdfd = [] + wrfd = [] + for fd, flags in self.registered.items(): + if flags & Poller.POLLRD: + rdfd.append(fd) + if flags & Poller.POLLWR: + wrfd.append(fd) + r, w, x = select.select(rdfd, wrfd, rdfd, timeout) + return [self.get_fd(fd, Poller.POLLIN) for fd in r] + [self.get_fd(fd, Poller.POLLWR) for fd in w] + [self.get_fd(fd, Poller.POLLERR) for fd in x] + else: + timeout = int(round(timeout * 1000.0, 0)) + return [self.get_fd(fd, flags) for fd, flags in self.mode.poll(timeout)] + class Connection(object): def __init__(self, id, conn, addr): self.id = id @@ -75,6 +134,7 @@ class Listener(object): self.connections = {} self.connection_list = [] self.connection_list_dirty = False + self.remove_from_poller = [] self.MAX_CONN = 3000 @@ -108,6 +168,7 @@ class Listener(object): def delete_connection(self, conn): print "%s disconnected." % (conn.id,) del self.connections[conn.id] + self.remove_from_poller.append(conn.conn) self.connection_list_dirty = True def all_connections(self): @@ -147,10 +208,22 @@ class Listener(object): else: callback(conn, input) + def update_poller(self, poll): + for dconn in self.delete_from_poller: + poll.unregister(dconn) + self.delete_from_poller = [] + for conn in self.all_connections(): + if conn.output_waiting(): + poll.register_write(self, conn.conn) + else: + poll.register_read(self, conn.conn) + + + def flush_output(self): for conn in self.all_connections(): if conn.output_waiting(): - conn.flush_output() + conn.flush_buffer() def shutdown(self): for x in self.connection_list: diff --git a/optimizer.py b/optimizer.py new file mode 100755 index 0000000..5b040c8 --- /dev/null +++ b/optimizer.py @@ -0,0 +1,54 @@ +from pyparsing import ParseResults + +def flatten_orig(l, ltypes=(list, tuple)): + ltype = type(l) + l = list(l) + i = 0 + while i < len(l): + while isinstance(l[i], ltypes): + if not l[i]: + l.pop(i) + i -= 1 + break + else: + l[i:i + 1] = l[i] + i += 1 + return ltype(l) + +def bytecode_flatten(l, ltypes=(list, tuple, ParseResults)): + l = list(l) + i = 0 + while i < len(l): + broken = False + while isinstance(l[i], ltypes): + if not l[i]: + l.pop(i) + broken = True + break + else: + l[i:i + 1] = list(l[i]) + if broken: + continue + assert not isinstance(l[i], ltypes) + assert not isinstance(l[i], (str, unicode, dict)) + bc = l[i].bytecode() + if len(bc) > 1 or bc[0] != l[i]: + l[i:i+1] = bc + continue + i += 1 + return l + +def optimize(data): + """ + yeah, this is some pretty intense optimiziation, I know... + it will hopefully be expanded on later... + """ + + data = bytecode_flatten(data) + + for x in data: + if not hasattr(x, "pos"): + print "Missing position on element %s" % (x,) + + return data + \ No newline at end of file diff --git a/parse.py b/parse.py index d4eead2..0da8d33 100755 --- a/parse.py +++ b/parse.py @@ -61,7 +61,7 @@ class Parser(object): propref = (objrefexpr + point + ident).setParseAction(VMPropRef.parse) | coreref fileref = (objrefexpr + excl + ident).setParseAction(VMFileRef.parse) - argspec = delimitedList(expr) + argspec = Optional(delimitedList(expr)) argspec.setParseAction(StackToList.parse) funccall = objrefexpr + call + identexpr + lpar + argspec + rpar @@ -174,12 +174,25 @@ class Parser(object): self.parser = block #print argspec.parseString("hello(hi.xyz)", parseAll=True) #print block.parseString(u"hi.xyz + #555.test;", parseAll=True) + #print block.parseString("""serverlog();""") def parse(self, data): rv = self.parser.parseString(data, parseAll=True) return optimizer.optimize(rv) + def parse_command(self, line): + ls = line.split(' ') + cmd = ls[0] + argstr = ' '.join(ls[1:]) + vars = { + 'cmdstr': line, + 'cmd': cmd, + 'argstr': argstr, + 'args': [x.strip() for x in ls[1:] if x.strip() != ''] + } + + return [cmd, vars] def test(self): #print self.parse(u"if (1) #740.xyz + -hello.world; endif") @@ -187,6 +200,9 @@ class Parser(object): data = unicode(open("test.moo", "r").read(), 'utf-8') print self.parse(data) + +static_parser = Parser() + if __name__ == "__main__": p = Parser() p.test() diff --git a/server.py b/server.py index 70097d6..72481c8 100644 --- a/server.py +++ b/server.py @@ -1,7 +1,8 @@ import os, sys, time -from listener import Listener +from listener import Listener, Poller from language import VirtualMachine from database_fake import Fake_Database as Database +from parse import static_parser class Server(object): def __init__(self, dbname): @@ -14,16 +15,24 @@ class Server(object): self.server_started = None self.loop_started = None self.listeners = [] + self.poller = Poller() def listen(self, addr, port): l = Listener() l.listen(addr, port) self.listeners.append(l) + + def unlisten(self, addr, port): + l = Listener() + l.listen(addr, port) + self.listeners.append(l) def mainloop(self): self.server_started = time.time() while True: self.loop_started = time.time() + + """ for l in self.listeners: l.handle_incoming_connections() l.scan_for_input(self.read_input) @@ -31,6 +40,9 @@ class Server(object): for l in self.listeners: l.flush_output() self.idlewait() + """ + self.poller.poll(self.get_sleepytime()) + def read_input(self, conn, data): @@ -39,7 +51,17 @@ class Server(object): del ds[-1] for line in ds: print "%s: %s" % (conn.id, line) + cmd, vars = static_parser.parse_command(line) + self.db.match_command(cmd, vars) + + def get_sleepytime(self): + if self.vm.sleepytime == None: + "virtual machine is still busy, no sleeping on the job!" + return 0.0 + + return self.vm.sleepytime + def idlewait(self): if self.vm.sleepytime == None: "virtual machine is still busy anyway, no sleeping on the job!" diff --git a/test.moo b/test.moo new file mode 100755 index 0000000..413f054 --- /dev/null +++ b/test.moo @@ -0,0 +1,8 @@ +serverlog("hello"); +part = 1; +if (part) +vvv = 3; +vxx = "abc"; +else +vvv = #-1; vvx = $nothing; +endif diff --git a/tt.py b/tt.py new file mode 100755 index 0000000..b5c3ece --- /dev/null +++ b/tt.py @@ -0,0 +1,9 @@ +from virtualmachine import VirtualMachine +from parse import Parser + +p = Parser() +vm = VirtualMachine(None) +bytecode = p.parse("""serverlog("hello"); var1 = var2;""") +vm.spawn_cmd_task(bytecode, {}) + +vm.run() \ No newline at end of file diff --git a/virtualmachine.py b/virtualmachine.py new file mode 100755 index 0000000..0d78ad7 --- /dev/null +++ b/virtualmachine.py @@ -0,0 +1,198 @@ +import random, heapq +from parse import Parser +from language import * + + +class VMContext(object): + def __init__(self, vars): + self.variables = vars + +class VMTask(object): + def __init__(self, id, code, vars): + self.task_id = id + self.code_stack = [x for x in reversed(code)] + self.exc_stack = [] + self.stack = [] + self.contexts = [VMContext(vars)] + + def context(self): + return self.contexts[-1] + +class VirtualMachine(object): + def __init__(self, db): + self.db = db + self.active_task_id = None + self.sleepytime = None + self.contexts = [] + self.tasks = {} + self.task = None + self.active_task_heap = [] + self.next_task_heap = [] + self.ticks_used = 0 + self.max_ticks = 300000 + self.max_tasks = 2**16 + + + def generate_task_id(self): + rv = random.randint(1,self.max_tasks) + if len(self.tasks) >= self.max_tasks: + raise RangeError, "Maximum number of tasks exceeded" + while rv in self.tasks: + rv += 1 + if rv > self.max_tasks: + rv = 1 + + return rv + + + def spawn_cmd_task(self, bytecode, vars): + task = VMTask(self.generate_task_id(), bytecode, vars) + self.tasks[task.task_id] = task + heapq.heappush(self.active_task_heap, (time.time(), task.task_id, task)) + + def spawn_forked_task(self, bytecode, vars): + task = VMTask(self.generate_task_id(), bytecode, vars) + self.tasks[task.task_id] = task + heapq.heappush(self.next_task_heap, (time.time(), task.task_id, task)) + + def code_pop(self): + return self.task.code_stack.pop() + + def code_push(self, code): + if isinstance(code, CodeOpSequence): + for op in reverse(code): + self.task.code_stack.append(op) + else: + self.task.code_stack.append(code) + + def exc_pop(self): + return self.task.exc_stack.pop() + + def exc_push(self, exc): + self.task.exc_stack.append(exc) + + def pop(self, count=1): + stack = [uncoerce(self.task.stack.pop()) for x in xrange(count)] + return [x for x in reversed(stack)] + + def pop_raw(self, count=1): + stack = [self.task.stack.pop() for x in xrange(count)] + return [x for x in reversed(stack)] + + def push(self, value): + self.task.stack.append(coerce(value)) + + def setvar(self, varname, val): + self.task.context().variables[varname] = val + + def getvar(self, varname): + return self.task.context().variables[varname] + + def push_context(self): + self.task.contexts.append(VMContext()) + + def pop_context(self): + self.task.contexts.pop() + + if not self.task.contexts: + del self.tasks[self.active_task_id] + self.active_task_id = None + self.task = None + self.finished_start_next() + + def suspend_start_next(self, delay): + now = time.time() + newtask = heapq.heappop(self.active_task_heap) + heapq.heappush(self.next_task_heap, (now+max(0.0,delay), self.active_task_id, self.task)) + return self.activate_task(newtask) + + def finished_start_next(self): + if self.active_task_heap: + now = time.time() + newtask = heapq.heappop(self.active_task_heap) + return self.activate_task(newtask) + + def activate_task(self, task): + now = time.time() + if now < task[0]: + "task isn't ready to execute yet" + self.sleepytime = task[0] - now + heapq.heappush(self.next_task_heap, task) + return False + + self.active_task_id = task[1] + self.task = task[2] + self.ticks_used = 0 + + def uncaught_exception(self): + for exc in self.task.exc_stack: + print "Unhandled exception: %s" % (exc,) + + def run_active_task(self): + task_id = self.active_task_id + while task_id == self.active_task_id and self.task.code_stack: + nextop = self.code_pop() + self.execute(nextop) + + if self.task.exc_stack: + self.uncaught_exception() + self.finished_start_next() + + if task_id == self.active_task_id: + self.finished_start_next() + + + def run(self): + self.sleepytime = None + if self.active_task_id == None: + self.finished_start_next() + if self.active_task_id == None: + "vm is idle" + return + self.run_active_task() + + + def jump(self, target): + while self.code_stack: + nextop = self.code_pop() + if isinstance(nextop, EndBlock): + if nextop.label == target: + self.execute(nextop) + break + + def jump_code(self, target): + while self.code_stack: + nextop = self.code_pop() + if type(nextop) == target: + if isinstance(nextop, target): + self.execute(nextop) + break + if isinstance(nextop, EndBlock): + if nextop.label == target: + break + + + def raise_exc(self, exc=None): + if exc != None: + self.exc_push(exc) + + while self.code_stack: + nextop = self.code_pop() + if isinstance(nextop, ExcHandlerBlock): + break + elif isinstance(nextop, FinallyBlock): + break + + self.execute(nextop) + + def execute(self, op): + self.ticks_used += op.ticks() + if self.ticks_used > self.max_ticks: + "ran out of ticks" + self.exc_push("out of ticks exception") + self.uncaught_exception() + self.finished_start_next() + else: + print "executing %s with stack %s" % (op, self.task.stack) + #op.load_stack(self) + op.execute(self) \ No newline at end of file