From 8b70e97601b2bdb23bce6c5fef580586d881ec2f Mon Sep 17 00:00:00 2001 From: Christoph Date: Fri, 13 Aug 2021 12:00:24 +0200 Subject: [PATCH] implement boolean --- builtins.simple | 37 +++++++++++++ de.churl.simple/bytecodeinterpreter.py | 3 + de.churl.simple/compile.py | 55 +++++++++--------- de.churl.simple/objmodel.py | 17 ++++-- de.churl.simple/objspace.py | 6 +- de.churl.simple/primitives.py | 77 ++++++++++++++++++++++---- de.churl.simple/simpleast.py | 13 ++++- de.churl.simple/simplelexer.py | 12 +++- de.churl.simple/simpleparser.py | 12 +++- mytests/test_boolean.py | 20 ++++++- 10 files changed, 200 insertions(+), 52 deletions(-) diff --git a/builtins.simple b/builtins.simple index b257fe6..0d2b41e 100644 --- a/builtins.simple +++ b/builtins.simple @@ -7,5 +7,42 @@ def pass: object inttrait: def add(other): self $int_add(other) + + # Project: Sugar + def sub(other): + self $int_sub(other) + def mul(other): + self $int_mul(other) + def div(other): + self $int_div(other) + def mod(other): + self $int_mod(other) + def inc: + self $int_inc + + # Project: Boolean def eq(other): self $int_eq(other) + def leq(other): + self $int_leq(other) + def geq(other): + self $int_geq(other) + def greater(other): + self $int_greater(other) + def less(other): + self $int_less(other) + + def tobool: + self $int_tobool + +# Project: Boolean +object booltrait: + def and(other): + self $bool_and(other) + def or(other): + self $bool_or(other) + def not: + self $bool_not + + def toint: + self $bool_toint diff --git a/de.churl.simple/bytecodeinterpreter.py b/de.churl.simple/bytecodeinterpreter.py index 80d7358..5f7d6fb 100644 --- a/de.churl.simple/bytecodeinterpreter.py +++ b/de.churl.simple/bytecodeinterpreter.py @@ -64,6 +64,9 @@ class Interpreter(object): elif opcode == compile.INT_LITERAL: w_value = self.space.newint(oparg) stack.append(w_value) + elif opcode == compile.BOOL_LITERAL: # Project: Boolean + w_value = self.space.newbool(oparg) # oparg is 1 or 0 + 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) diff --git a/de.churl.simple/compile.py b/de.churl.simple/compile.py index 704dfec..025d40b 100644 --- a/de.churl.simple/compile.py +++ b/de.churl.simple/compile.py @@ -109,23 +109,24 @@ import simpleast # ---------- bytecodes ---------- -INT_LITERAL = 2 # integer value -ASSIGNMENT = 4 # index of attrname -METHOD_LOOKUP = 5 # index of method name -METHOD_CALL = 6 # number of arguments -PRIMITIVE_METHOD_CALL = 7 # number of the primitive -MAKE_FUNCTION = 8 # bytecode literal index -MAKE_OBJECT = 9 # index of object name -ASSIGNMENT_APPEND_PARENT = 10 # index of parentname -MAKE_OBJECT_CALL = 11 # bytecode literal index -JUMP_IF_FALSE = 12 # offset -JUMP = 13 # offset -GET_LOCAL = 15 # index of attrname (optimization) -SET_LOCAL = 16 # index of attrname (optimization) +BOOL_LITERAL = 1 # 1 or 0 # Project: Boolean +INT_LITERAL = 2 # integer value +ASSIGNMENT = 4 # index of attrname +METHOD_LOOKUP = 5 # index of method name +METHOD_CALL = 6 # number of arguments +PRIMITIVE_METHOD_CALL = 7 # number of the primitive +MAKE_FUNCTION = 8 # bytecode literal index +MAKE_OBJECT = 9 # index of object name +ASSIGNMENT_APPEND_PARENT = 10 # index of parentname +MAKE_OBJECT_CALL = 11 # bytecode literal index +JUMP_IF_FALSE = 12 # offset +JUMP = 13 # offset +GET_LOCAL = 15 # index of attrname (optimization) +SET_LOCAL = 16 # index of attrname (optimization) -IMPLICIT_SELF = 32 # (no argument) -POP = 33 # (no argument) -DUP = 34 # (no argument) +IMPLICIT_SELF = 32 # (no argument) +POP = 33 # (no argument) +DUP = 34 # (no argument) opcode_names = [None] * 256 for key, value in list(globals().items()): @@ -133,11 +134,11 @@ for key, value in list(globals().items()): opcode_names[value] = key - def hasarg(opcode): """ Helper function to determine whether an opcode has an argument.""" return opcode < 32 + def isjump(opcode): """ Helper function to determine whether an opcode is a jump.""" return opcode == JUMP_IF_FALSE or opcode == JUMP @@ -188,6 +189,7 @@ def compile(ast, argumentnames=[], name=None): stack_effects = { + BOOL_LITERAL: 1, # Project: Boolean INT_LITERAL: 1, ASSIGNMENT: -1, METHOD_LOOKUP: 1, @@ -219,10 +221,10 @@ class Compiler(object): for name, index in list(self.symbols.items()): symbols[index] = name result = Bytecode(''.join(self.code), - funcname, - symbols, - self.subbytecodes, - numargs, self.max_stackdepth) + funcname, + symbols, + self.subbytecodes, + numargs, self.max_stackdepth) assert self.stackdepth == 1 return result @@ -257,10 +259,10 @@ class Compiler(object): return len(self.code) def set_target_position(self, oldposition, newtarget): - offset = newtarget - (oldposition+5) + offset = newtarget - (oldposition + 5) i = 0 for c in self.encode4(offset): - self.code[oldposition+1+i] = c + self.code[oldposition + 1 + i] = c i += 1 def encode4(self, value): @@ -274,13 +276,16 @@ class Compiler(object): self.symbols[symbol] = len(self.symbols) return self.symbols[symbol] - def compile(self, ast, needsresult=True): return getattr(self, "compile_" + ast.__class__.__name__)(ast, needsresult) def compile_IntLiteral(self, astnode, needsresult): self.emit(INT_LITERAL, astnode.value) + # Project: Boolean + def compile_BooleanLiteral(self, astnode, needsresult): + self.emit(BOOL_LITERAL, astnode.value) + def compile_ImplicitSelf(self, astnode, needsresult): self.emit(IMPLICIT_SELF) @@ -318,7 +323,7 @@ class Compiler(object): expected_args = primitives.get_number_of_arguments_of_primitive(index) if not (len(astnode.arguments) == expected_args): raise TypeError( - "Expected {ex} arguments, received {re}.".format(ex=expected_args, re=len(astnode.arguments))) + "Expected {ex} arguments, received {re}.".format(ex=expected_args, re=len(astnode.arguments))) self.compile(astnode.receiver) for arg in astnode.arguments: self.compile(arg) diff --git a/de.churl.simple/objmodel.py b/de.churl.simple/objmodel.py index 172b548..a3e54f1 100644 --- a/de.churl.simple/objmodel.py +++ b/de.churl.simple/objmodel.py @@ -74,16 +74,17 @@ class W_NormalObject(AbstractObject): class W_Integer(AbstractObject): - def __init__(self, value, space=None): - self.value = value + def __init__(self, value, space=None, trait="inttrait"): + self.value = int(value) self.space = space + self.__trait = trait # used this to extend from W_Integer def getparents(self): if self.space is None: return [] # for tests - inttrait = self.space.getbuiltin('inttrait') - assert inttrait is not None, 'O_o bogus state' - return [inttrait] + trait = self.space.getbuiltin(self.__trait) + assert trait is not None, 'O_o bogus state' + return [trait] def hasslot(self, name): return False @@ -97,6 +98,12 @@ class W_Integer(AbstractObject): return self.value != 0 +# Project: Boolean +class W_Boolean(W_Integer): # don't know if extending is good idea + def __init__(self, value, space=None): + super().__init__(int(value), space=space, trait="booltrait") + + class W_Method(W_NormalObject): def __init__(self, code, *args, **kwargs): diff --git a/de.churl.simple/objspace.py b/de.churl.simple/objspace.py index 6ec005f..e1a9758 100644 --- a/de.churl.simple/objspace.py +++ b/de.churl.simple/objspace.py @@ -1,4 +1,4 @@ -from objmodel import W_Integer +from objmodel import W_Integer, W_Boolean from objmodel import W_Method from objmodel import W_NormalObject @@ -49,6 +49,10 @@ class ObjectSpace(object): def newint(self, value): return W_Integer(value, space=self) + # Project: Boolean + def newbool(self, value): + return W_Boolean(value, space=self) + def definemethod(self, name, code, w_target): w_meth = W_Method(code, name=name, slots={'__parent__': w_target}, diff --git a/de.churl.simple/primitives.py b/de.churl.simple/primitives.py index c862419..7db12cd 100644 --- a/de.churl.simple/primitives.py +++ b/de.churl.simple/primitives.py @@ -3,25 +3,32 @@ all_primitives = [] primitive_number_of_arguments = [] -def primitive(name, unwrap_spec, wrap_spec): +def primitive(name, unwrap_spec, wrap_spec): # decorator arguments assert '$' + name not in registry, '${name} already defined'.format(name=name) primitive_number_of_arguments.append(len(unwrap_spec) - 1) # first argument is the receiver - def expose(func): + def expose(func): # decorator def unwrapper(w_receiver, args_w, space): args = [w_receiver] + args_w - if len(args) != len(unwrap_spec): + if len(args) != len(unwrap_spec): # check that call args match primitive args raise TypeError( "Expected {ex} arguments, received {re}.".format(ex=len(unwrap_spec), re=len(args))) + unwrapped_args = () - for t, arg in zip(unwrap_spec, args): + for t, arg in zip(unwrap_spec, args): # unpack values from simple-objects if t is int: unwrapped_args += (arg.value,) + elif t is bool: # Project: Boolean + unwrapped_args += (bool(arg.value),) # isn't necessary because "1 or 0" is valid else: unwrapped_args += (arg,) - result = func(*unwrapped_args) - if wrap_spec is int: + + result = func(*unwrapped_args) # actual call + + if wrap_spec is int: # wrap the result return space.newint(result) + elif wrap_spec is bool: # Project: Boolean + return space.newbool(result) return result unwrapper.__qualname__ = name @@ -45,12 +52,7 @@ def simple_int_add(a, b): return a + b -@primitive('int_eq', [int, int], int) -def simple_int_eq(a, b): - return a == b - - -# Syntactic Sugar Primitives +# Project: Sugar @primitive("int_sub", [int, int], int) def simple_int_subtract(a, b): return a - b @@ -74,3 +76,54 @@ def simple_int_modulo(a, b): @primitive("int_inc", [int], int) def simple_int_increment(a): return a + 1 + + +# Project: Boolean +@primitive("bool_and", [bool, bool], bool) +def simple_bool_and(a, b): + return a and b + + +@primitive("bool_or", [bool, bool], bool) +def simple_bool_or(a, b): + return a or b + + +@primitive("bool_not", [bool], bool) +def simple_bool_not(a): + return not a + + +@primitive("int_eq", [int, int], bool) +def simple_int_eq(a, b): + return a == b + + +@primitive("int_leq", [int, int], bool) +def simple_int_leq(a, b): + return a <= b + + +@primitive("int_geq", [int, int], bool) +def simple_int_geq(a, b): + return a >= b + + +@primitive("int_greater", [int, int], bool) +def simple_int_greater(a, b): + return a > b + + +@primitive("int_less", [int, int], bool) +def simple_int_less(a, b): + return a < b + + +@primitive("int_tobool", [int], bool) +def simple_int_tobool(a): + return a + + +@primitive("bool_toint", [bool], int) +def simple_bool_toint(a): + return a diff --git a/de.churl.simple/simpleast.py b/de.churl.simple/simpleast.py index 8a6448b..d361fab 100644 --- a/de.churl.simple/simpleast.py +++ b/de.churl.simple/simpleast.py @@ -93,8 +93,9 @@ class IntLiteral(Expression): self.value = int(value) +# Project: String class StringLiteral(Expression): - """ An string literal (like "hello world") """ + """ A string literal (like "hello world") """ attrs = ["value"] @@ -102,6 +103,16 @@ class StringLiteral(Expression): self.value = str(value) +# Project: Boolean +class BooleanLiteral(Expression): + """ A boolean literal (like "false") """ + + attrs = ["value"] + + def __init__(self, value): + self.value = value == "true" + + class MethodCall(Expression): """ A call to a method with name 'methodname' on 'receiver' with 'arguments' (which is a list of expression ASTs). diff --git a/de.churl.simple/simplelexer.py b/de.churl.simple/simplelexer.py index 9305bdc..3177219 100644 --- a/de.churl.simple/simplelexer.py +++ b/de.churl.simple/simplelexer.py @@ -130,7 +130,6 @@ def make_single_string(delim): # Literals Number = r'(([+-])?[1-9][0-9]*)|0' -String = group(make_single_string(r"\'"), make_single_string(r'\"')) # ____________________________________________________________ # Ignored @@ -159,7 +158,13 @@ OpenBracket = r'[\[\(\{]' CloseBracket = r'[\]\)\}]' # ____________________________________________________________ -# Syntactic Sugar Operators +# Project: Boolean, String + +Boolean = group(r'true', r'false') +String = group(make_single_string(r"\'"), make_single_string(r'\"')) + +# ____________________________________________________________ +# Project: Sugar Plus = r'\+' Minus = r'-' @@ -178,8 +183,9 @@ Def = r'def' Object = r'object' tokens = ["If", "Else", "While", "Def", "Object", "Number", "String", "Ignore", + "Boolean", # Project: Boolean "NewlineAndWhitespace", "OpenBracket", "CloseBracket", "Comma", "Assign", "Colon", - "Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", # Sugar + "Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", # Project: Sugar "Name", "PrimitiveName"] diff --git a/de.churl.simple/simpleparser.py b/de.churl.simple/simpleparser.py index dc77364..b1998f0 100644 --- a/de.churl.simple/simpleparser.py +++ b/de.churl.simple/simpleparser.py @@ -6,6 +6,7 @@ from simplelexer import lex import simpleast pg = ParserGenerator(["If", "Else", "While", "Def", "Object", "Number", + "Boolean", # New Types "String", "Name", "Indent", "Dedent", "Newline", "OpenBracket", "CloseBracket", "Comma", "Assign", "Colon", "Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", # Sugar @@ -154,7 +155,7 @@ def expression(expr): return expr[0] -# Syntactic Sugar: Plus, Minus, Multiply, Divide, Modulo, Increment +# Project: Sugar @pg.production("expression : expression Plus expression") @pg.production("expression : expression Minus expression") @pg.production("expression : expression Multiply expression") @@ -197,9 +198,16 @@ def number_expression(stmt): return simpleast.IntLiteral(stmt[0].value) +# Project: String @pg.production("basic_expression : String") def string_expression(stmt): - return simpleast.StringLiteral(stmt[0].value) + return simpleast.StringLiteral(stmt[0].value[1:-1]) # cut off delimiters + + +# Project: Boolean +@pg.production("basic_expression : Boolean") +def boolean_expression(stmt): + return simpleast.BooleanLiteral(stmt[0].value) @pg.production("basic_expression : implicitselfmethodcall") diff --git a/mytests/test_boolean.py b/mytests/test_boolean.py index 9f98c3b..a267008 100644 --- a/mytests/test_boolean.py +++ b/mytests/test_boolean.py @@ -1,10 +1,24 @@ import py +from rply import Token +from simpleast import Program, ExprStatement, BooleanLiteral, Assignment, ImplicitSelf +from simplelexer import lex from simpleparser import parse -from objmodel import W_NormalObject, W_Integer +from objmodel import W_NormalObject, W_Integer, W_Boolean from interpreter import Interpreter +def test_basic_boolean_lexing(): + assert lex("true")[0] == Token("Boolean", "true") + assert lex("false")[0] == Token("Boolean", "false") + assert lex("x = true")[:3] == [Token("Name", "x"), Token("Assign", "="), Token("Boolean", "true")] + + +def test_basic_boolean_parsing(): + assert parse("false") == Program([ExprStatement(BooleanLiteral("false"))]) + assert parse("x = false") == Program([Assignment(ImplicitSelf(), "x", BooleanLiteral("false"))]) + + def test_boolean_assignment(): ast = parse(""" x = true @@ -24,7 +38,7 @@ def test_boolean_operations(): ast = parse(""" x = true and(false) y = true or(false) -z = not(true) +z = true not """) interpreter = Interpreter() w_model = interpreter.make_module() @@ -55,7 +69,7 @@ y = 1 leq(2) def test_boolean_conversion(): ast = parse(""" -x = 1 toboolean +x = 1 tobool y = true toint z = false toint """)