diff --git a/de.churl.simple/bytecodeinterpreter.py b/de.churl.simple/bytecodeinterpreter.py index d7b55e7..7e13102 100644 --- a/de.churl.simple/bytecodeinterpreter.py +++ b/de.churl.simple/bytecodeinterpreter.py @@ -1,8 +1,8 @@ -from simpleparser import parse -from objspace import ObjectSpace -import compile from disass import disassemble +import compile +from objspace import ObjectSpace + class ByteCodeError(Exception): pass @@ -46,7 +46,6 @@ class Interpreter(object): w_condition = stack.pop() if self.space.isfalse(w_condition): pc += oparg - continue elif compile.hasarg(opcode): oparg = ord(code[pc]) pc += 1 @@ -62,20 +61,24 @@ class Interpreter(object): stack.append(obj) elif opcode == compile.MAKE_OBJECT_CALL: self.run(bytecode.subbytecodes[oparg], stack[-1]) + + # Project ----- elif opcode == compile.INT_LITERAL: w_value = self.space.newint(oparg) stack.append(w_value) - elif opcode == compile.BOOL_LITERAL: # Project: Boolean + elif opcode == compile.BOOL_LITERAL: w_value = self.space.newbool(oparg) # oparg is 1 or 0 stack.append(w_value) - elif opcode == compile.STRING_LITERAL: # Project: String + elif opcode == compile.STRING_LITERAL: value = bytecode.symbols[oparg] w_value = self.space.newstring(value) stack.append(w_value) - elif opcode == compile.DOUBLE_LITERAL: # Project: Double + elif opcode == compile.DOUBLE_LITERAL: value = bytecode.symbols[oparg] w_value = self.space.newdouble(value) stack.append(w_value) + # ------------- + elif opcode == compile.MAKE_FUNCTION: bc = bytecode.subbytecodes[oparg] w_method = self.space.definemethod(name=bc.name, code=bc, w_target=w_context) @@ -85,7 +88,7 @@ class Interpreter(object): w_method = self.space.getvalue(stack[-1], name) stack.append(w_method) elif opcode == compile.METHOD_CALL: - arguments_w = [stack.pop() for n in range(oparg)] + arguments_w = [stack.pop() for _ in range(oparg)] arguments_w.reverse() # w_method = stack.pop() @@ -94,7 +97,7 @@ class Interpreter(object): stack.append(w_result) elif opcode == compile.PRIMITIVE_METHOD_CALL: nargs = self.space.get_number_of_arguments_of_primitive(oparg) - arguments_w = [stack.pop() for n in range(nargs)] + arguments_w = [stack.pop() for _ in range(nargs)] arguments_w.reverse() w_receiver = stack.pop() w_result = self.space.call_primitive(oparg, w_receiver, arguments_w) @@ -126,6 +129,11 @@ class Interpreter(object): stack.append(w_context) elif opcode == compile.DUP: stack.append(stack[-1]) + + # Project + elif opcode == compile.GC: + self.space.gc(w_context) + else: raise ByteCodeError('Invalid bytecode') assert pc == len(code) diff --git a/de.churl.simple/compile.py b/de.churl.simple/compile.py index f6f4407..ba4c743 100644 --- a/de.churl.simple/compile.py +++ b/de.churl.simple/compile.py @@ -109,10 +109,10 @@ import simpleast # ---------- bytecodes ---------- -BOOL_LITERAL = 1 # 1 or 0 # Project: Boolean +BOOL_LITERAL = 1 # 1 or 0 INT_LITERAL = 2 # integer value -STRING_LITERAL = 3 # Project: String -DOUBLE_LITERAL = 17 # Project: Double +STRING_LITERAL = 3 +DOUBLE_LITERAL = 17 ASSIGNMENT = 4 # index of attrname METHOD_LOOKUP = 5 # index of method name METHOD_CALL = 6 # number of arguments @@ -129,6 +129,7 @@ SET_LOCAL = 16 # index of attrname (optimization) IMPLICIT_SELF = 32 # (no argument) POP = 33 # (no argument) DUP = 34 # (no argument) +GC = 35 # (no argument) opcode_names = [None] * 256 for key, value in list(globals().items()): @@ -191,9 +192,9 @@ def compile(ast, argumentnames=[], name=None): stack_effects = { - BOOL_LITERAL: 1, # Project: Boolean - STRING_LITERAL: 1, # Project: String - DOUBLE_LITERAL: 1, # Project: Double + BOOL_LITERAL: 1, + STRING_LITERAL: 1, + DOUBLE_LITERAL: 1, INT_LITERAL: 1, ASSIGNMENT: -1, METHOD_LOOKUP: 1, @@ -208,6 +209,7 @@ stack_effects = { IMPLICIT_SELF: 1, POP: -1, DUP: 1, + GC: 0, } @@ -257,6 +259,7 @@ class Compiler(object): stackeffect = stack_effects[opcode] else: assert stackeffect != sys.maxsize + self.stack_effect(stackeffect) def get_position(self): @@ -289,18 +292,24 @@ class Compiler(object): def compile_IntLiteral(self, astnode, needsresult): self.emit(INT_LITERAL, astnode.value) - # Project: Boolean + # Project ----- def compile_BooleanLiteral(self, astnode, needsresult): self.emit(BOOL_LITERAL, astnode.value) - # Project: String def compile_StringLiteral(self, astnode, needsresult): self.emit(STRING_LITERAL, self.lookup_symbol(astnode.value)) # save string value to symboltable - # Project: Double def compile_DoubleLiteral(self, astnode, needsresult): self.emit(DOUBLE_LITERAL, self.lookup_symbol(astnode.value)) + def compile_GCStatement(self, astnode, needsresult): + self.emit(GC) + + if needsresult: + self.emit(BOOL_LITERAL, True) + + # ------------- + def compile_ImplicitSelf(self, astnode, needsresult): self.emit(IMPLICIT_SELF) @@ -370,7 +379,7 @@ class Compiler(object): for statement in astnode.statements[:-1]: self.compile(statement, needsresult=False) laststatement = astnode.statements[-1] - self.compile(laststatement, needsresult) + self.compile(laststatement, needsresult) # return last result def compile_FunctionDefinition(self, astnode, needsresult): bytecode = compile(astnode.block, astnode.arguments, astnode.name) diff --git a/de.churl.simple/garbagecollection.py b/de.churl.simple/garbagecollection.py new file mode 100644 index 0000000..010bd88 --- /dev/null +++ b/de.churl.simple/garbagecollection.py @@ -0,0 +1,18 @@ +def mark(w_context): + w_context.mark = True + + if not hasattr(w_context, "slots"): # skip primitive objects + return + + for name, obj in w_context.slots.items(): + if name != "__parent__": # only descent + mark(obj) + + +def sweep(objects): + objects[:] = filter(lambda obj: obj.mark, objects) # inplace + + +def clear_marks(objects): + for obj in objects: + obj.mark = False diff --git a/de.churl.simple/objmodel.py b/de.churl.simple/objmodel.py index ceb42fe..c2ed1bd 100644 --- a/de.churl.simple/objmodel.py +++ b/de.churl.simple/objmodel.py @@ -2,6 +2,9 @@ from c3computation import compute_C3_mro as c3 class AbstractObject(object): + def __init__(self): + self.mark = False # gc marker + def call(self, w_receiver, args_w): return self @@ -24,30 +27,9 @@ class AbstractObject(object): return c3(self) -class PrimitiveObject(AbstractObject): - def __init__(self, value, trait, space=None): - self.value = value - self._trait = trait - self.space = space - - def getparents(self): - if self.space is None: - return [] # for tests - trait = self.space.getbuiltin(self._trait) - assert trait is not None, 'O_o bogus state' - return [trait] - - def hasslot(self, name): - return False - - def __str__(self): - return str(self.value) - - __repr__ = __str__ - - class W_NormalObject(AbstractObject): def __init__(self, name=None, slots=None, parents=None, space=None): + super().__init__() self.space = space self.name = name if slots: @@ -93,44 +75,59 @@ class W_NormalObject(AbstractObject): slots=self.slots.copy()) -class W_Integer(PrimitiveObject): - def __init__(self, value, space=None): - super().__init__(int(value), "inttrait", space) +# Project ----- +class PrimitiveObject(AbstractObject): + def __init__(self, value, trait, space=None): + super().__init__() + self.value = value + self._trait = trait + self.space = space - def istrue(self): - return self.value != 0 + def getparents(self): + if self.space is None: + return [] # for tests + trait = self.space.getbuiltin(self._trait) + assert trait is not None, 'O_o bogus state' + return [trait] - -# Project: Boolean -class W_Boolean(PrimitiveObject): # don't know if extending is good idea - def __init__(self, value, space=None): - super().__init__(int(value), "booltrait", space=space) + def hasslot(self, name): + return False def __str__(self): - return str(bool(self.value)) + return str(self.value) __repr__ = __str__ def istrue(self): - return self.value != 0 + return bool(self.value) + + +class W_Integer(PrimitiveObject): + def __init__(self, value, space=None): + super().__init__(int(value), "inttrait", space) + + +class W_Boolean(PrimitiveObject): + def __init__(self, value, space=None): + super().__init__(int(value), "booltrait", space=space) + + def __str__(self): + return str(bool(self.value)) # true instead of 1 + + __repr__ = __str__ -# Project: String class W_String(PrimitiveObject): def __init__(self, value, space=None): super().__init__(str(value), "strtrait", space) - def istrue(self): - return self.value != "" - -# Project: Double class W_Double(PrimitiveObject): def __init__(self, value, space=None): super().__init__(float(value), "doubletrait", space) - def istrue(self): - return self.value != 0. + +# ------------- class W_Method(W_NormalObject): diff --git a/de.churl.simple/objspace.py b/de.churl.simple/objspace.py index cee4110..ccbfd04 100644 --- a/de.churl.simple/objspace.py +++ b/de.churl.simple/objspace.py @@ -1,14 +1,15 @@ +import primitives +from garbagecollection import mark, sweep, clear_marks from objmodel import W_Integer, W_Boolean, W_String, W_Double from objmodel import W_Method from objmodel import W_NormalObject -import primitives - class ObjectSpace(object): def __init__(self, interpreter): self.interpreter = interpreter + self.objects = [] def setup_builtins(self, builtincode=None): if builtincode is None: @@ -20,6 +21,7 @@ class ObjectSpace(object): ast = parse(builtincode) self.interpreter.eval(ast, w_builtins) + self.objects.clear() # remove all builtins, lobby from list def _load_default_builtins(self): import os @@ -40,32 +42,43 @@ class ObjectSpace(object): slots = {} else: slots = {'__parent__': self.getbuiltins()} - return W_NormalObject(name=name, slots=slots) + + return W_NormalObject(name=name, slots=slots) # lobby isn't collected def newobject(self, name, slots, parentnames): - return W_NormalObject(space=self, name=name, - slots=slots, parents=parentnames) + self.objects.append(W_NormalObject(space=self, name=name, + slots=slots, parents=parentnames)) + return self.objects[-1] + # Project ----- def newint(self, value): - return W_Integer(value, space=self) + self.objects.append(W_Integer(value, space=self)) + return self.objects[-1] - # Project: Boolean def newbool(self, value): - return W_Boolean(value, space=self) + self.objects.append(W_Boolean(value, space=self)) + return self.objects[-1] - # Project: String def newstring(self, value): - return W_String(value, space=self) + self.objects.append(W_String(value, space=self)) + return self.objects[-1] - # Project: Double def newdouble(self, value): - return W_Double(value, space=self) + self.objects.append(W_Double(value, space=self)) + return self.objects[-1] + + def gc(self, w_context): + clear_marks(self.objects) + mark(w_context) + sweep(self.objects) + + # ------------- def definemethod(self, name, code, w_target): - w_meth = W_Method(code, name=name, - slots={'__parent__': w_target}, - space=self) - return w_meth + self.objects.append(W_Method(code, name=name, + slots={'__parent__': w_target}, + space=self)) + return self.objects[-1] def execute(self, code, w_context): return self.interpreter.run(code, w_context) diff --git a/de.churl.simple/simpleast.py b/de.churl.simple/simpleast.py index a53f840..f84db06 100644 --- a/de.churl.simple/simpleast.py +++ b/de.churl.simple/simpleast.py @@ -93,7 +93,7 @@ class IntLiteral(Expression): self.value = int(value) -# Project: Boolean +# Project ----- class BooleanLiteral(Expression): """ A boolean literal (like "false") """ @@ -103,7 +103,6 @@ class BooleanLiteral(Expression): self.value = value == "true" -# Project: String class StringLiteral(Expression): """ A string literal (like "hello world") """ @@ -113,7 +112,6 @@ class StringLiteral(Expression): self.value = str(value) -# Project: Double class DoubleLiteral(Expression): """ A double literal (like "1.0", ".0", "1.", "+1.0") """ @@ -123,6 +121,9 @@ class DoubleLiteral(Expression): self.value = float(value) +# ------------- + + class MethodCall(Expression): """ A call to a method with name 'methodname' on 'receiver' with 'arguments' (which is a list of expression ASTs). @@ -172,6 +173,11 @@ class Statement(AstNode): """ Base class of all statement nodes. """ +# Project +class GCStatement(Statement): + """ Triggers Garbage Collection """ + + class Assignment(Statement): """ An assignement: lvalue attrname = expression. diff --git a/de.churl.simple/simplelexer.py b/de.churl.simple/simplelexer.py index 04b9ddf..3bfd504 100644 --- a/de.churl.simple/simplelexer.py +++ b/de.churl.simple/simplelexer.py @@ -158,7 +158,7 @@ OpenBracket = r'[\[\(\{]' CloseBracket = r'[\]\)\}]' # ____________________________________________________________ -# Project: Boolean, String, Double +# Project Boolean = r"true|false" String = group(make_single_string(r"\'"), make_single_string(r'\"')) @@ -166,11 +166,8 @@ String = group(make_single_string(r"\'"), make_single_string(r'\"')) _sign = r"([+-])?" _int = r"(([1-9][0-9]*)|0)" _dec = r"(([0-9]*[1-9])|0)" -Double = group(_sign + group(_int, r"") + r"\." + _dec, - _sign + _int + r"\." + group(_dec, r"")) - -# ____________________________________________________________ -# Project: Sugar +Double = group(_sign + group(_int, r"") + r"\." + _dec, # 0.1 / .1 + _sign + _int + r"\." + group(_dec, r"")) # 1.0 / 1. Plus = r'\+' Minus = r'-' @@ -179,6 +176,8 @@ Divide = r'/' Increment = r'\+\+' Modulo = r'%' +GC = r'gc' + # ____________________________________________________________ # Keywords @@ -189,10 +188,11 @@ Def = r'def' Object = r'object' tokens = ["If", "Else", "While", "Def", "Object", "Ignore", - "String", "Boolean", "Double", # Project: Boolean, String, Double + "String", "Boolean", "Double", "Number", # after Double + "GC", "NewlineAndWhitespace", "OpenBracket", "CloseBracket", "Comma", "Assign", "Colon", - "Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", # Project: Sugar + "Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", "Name", "PrimitiveName"] diff --git a/de.churl.simple/simpleparser.py b/de.churl.simple/simpleparser.py index eb99719..025ee03 100644 --- a/de.churl.simple/simpleparser.py +++ b/de.churl.simple/simpleparser.py @@ -6,10 +6,11 @@ from simplelexer import lex import simpleast pg = ParserGenerator(["If", "Else", "While", "Def", "Object", "Number", - "String", "Boolean", "Double", # Project: Boolean, String, Double + "String", "Boolean", "Double", + "GC", "Name", "Indent", "Dedent", "Newline", "OpenBracket", "CloseBracket", "Comma", "Assign", "Colon", - "Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", # Project: Sugar + "Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", "PrimitiveName", "EOF"], # Operator precedence for ambiguous rules, ascending precedence=[("left", ["Plus", "Minus"]), @@ -63,6 +64,12 @@ def newlines(n): return None +@pg.production("statement : GC newlines") +@pg.production("statement : GC") +def gcstatement(stmt): + return simpleast.GCStatement() + + @pg.production("statement : simplestatement") @pg.production("statement : ifstatement") @pg.production("statement : whilestatement") @@ -198,19 +205,16 @@ def number_expression(stmt): return simpleast.IntLiteral(stmt[0].value) -# Project: Boolean @pg.production("basic_expression : Boolean") def boolean_expression(stmt): return simpleast.BooleanLiteral(stmt[0].value) -# Project: String @pg.production("basic_expression : String") def string_expression(stmt): return simpleast.StringLiteral(stmt[0].value[1:-1]) # cut off delimiters -# Project: Double @pg.production("basic_expression : Double") def double_expression(stmt): return simpleast.DoubleLiteral(stmt[0].value) diff --git a/mytests/test_gc.py b/mytests/test_gc.py index bf97dfb..48fe235 100644 --- a/mytests/test_gc.py +++ b/mytests/test_gc.py @@ -4,33 +4,36 @@ from interpreter import Interpreter def test_reassignment_gc(): ast = parse(""" +true x = 2 y = 3 """) interpreter = Interpreter() w_model = interpreter.make_module() interpreter.eval(ast, w_model) + interpreter.space.gc(w_model) x = w_model.getvalue("x") y = w_model.getvalue("y") - assert x in interpreter.space.realm # Wo alle Objekte leben - assert y in interpreter.space.realm + assert x in interpreter.space.objects + assert y in interpreter.space.objects ast = parse(""" x = y """) interpreter.eval(ast, w_model) - interpreter.space.gc() - assert x not in interpreter.space.realm - assert y in interpreter.space.realm + interpreter.space.gc(w_model) + assert x not in interpreter.space.objects + assert y in interpreter.space.objects ast = parse(""" x = 0 +y = 0 """) interpreter.eval(ast, w_model) - interpreter.space.gc() - assert x not in interpreter.space.realm - assert y not in interpreter.space.realm + interpreter.space.gc(w_model) + assert x not in interpreter.space.objects + assert y not in interpreter.space.objects def test_chain_gc(): @@ -42,40 +45,25 @@ z = y interpreter = Interpreter() w_model = interpreter.make_module() interpreter.eval(ast, w_model) + interpreter.space.gc(w_model) x = w_model.getvalue("x") - assert x in interpreter.space.realm + assert x in interpreter.space.objects ast = parse(""" x = 0 """) interpreter.eval(ast, w_model) - interpreter.space.gc() - assert x in interpreter.space.realm + interpreter.space.gc(w_model) + assert x in interpreter.space.objects ast = parse(""" y = x z = y """) interpreter.eval(ast, w_model) - interpreter.space.gc() - assert x not in interpreter.space.realm - - -def test_while_gc(): - ast = parse(""" -x = 10 -while x: - x = x $int_add(-1) -""") - interpreter = Interpreter() - w_model = interpreter.make_module() - interpreter.eval(ast, w_model) - - count = len(interpreter.space.realm) - interpreter.space.gc() - - assert count - len(interpreter.space.realm) == 10 + interpreter.space.gc(w_model) + assert x not in interpreter.space.objects def test_object_gc(): @@ -88,19 +76,76 @@ object x: interpreter = Interpreter() w_model = interpreter.make_module() interpreter.eval(ast, w_model) + interpreter.space.gc(w_model) a = w_model.getvalue("x").getvalue("a") b = w_model.getvalue("x").getvalue("b") c = w_model.getvalue("x").getvalue("c") - assert a in interpreter.space.realm - assert b in interpreter.space.realm - assert c in interpreter.space.realm + x = w_model.getvalue("x") + assert a in interpreter.space.objects + assert b in interpreter.space.objects + assert c in interpreter.space.objects + assert x in interpreter.space.objects ast = parse(""" x = 0 """) interpreter.eval(ast, w_model) - interpreter.space.gc() - assert a not in interpreter.space.realm - assert b not in interpreter.space.realm - assert c not in interpreter.space.realm + interpreter.space.gc(w_model) + assert a not in interpreter.space.objects + assert b not in interpreter.space.objects + assert c not in interpreter.space.objects + assert x not in interpreter.space.objects + + +def test_method_gc(): + ast = parse(""" +def meth: + 1 +""") + interpreter = Interpreter() + w_model = interpreter.make_module() + interpreter.eval(ast, w_model) + interpreter.space.gc(w_model) + + meth = w_model.getvalue("meth") + assert meth in interpreter.space.objects + + ast = parse(""" +def meth: + 2 +""") + interpreter.eval(ast, w_model) + + assert meth in interpreter.space.objects + + interpreter.space.gc(w_model) + + assert meth not in interpreter.space.objects + + +def test_simple_call_gc(): + ast = parse(""" +x = 1 +gc +""") + interpreter = Interpreter() + w_model = interpreter.make_module() + interpreter.eval(ast, w_model) + + x = w_model.getvalue("x") + assert x in interpreter.space.objects + + ast = parse(""" +x = 2 +""") + interpreter.eval(ast, w_model) + + assert x in interpreter.space.objects + + ast = parse(""" +gc +""") + interpreter.eval(ast, w_model) + + assert x not in interpreter.space.objects