From ca80f7df2ed23ad1f9f02e929850df7b46cd0739 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sat, 14 Aug 2021 17:54:14 +0200 Subject: [PATCH] implement double --- builtins.simple | 26 +++++++++++ de.churl.simple/bytecodeinterpreter.py | 4 ++ de.churl.simple/compile.py | 6 +++ de.churl.simple/objmodel.py | 29 ++++++++++-- de.churl.simple/objspace.py | 6 ++- de.churl.simple/primitives.py | 62 ++++++++++++++++++++++++-- de.churl.simple/simpleast.py | 10 +++++ de.churl.simple/simplelexer.py | 15 +++++-- de.churl.simple/simpleparser.py | 8 +++- mytests/test_double.py | 58 +++++++++++++++++++++++- 10 files changed, 210 insertions(+), 14 deletions(-) diff --git a/builtins.simple b/builtins.simple index df7c3ae..c313fff 100644 --- a/builtins.simple +++ b/builtins.simple @@ -36,6 +36,8 @@ object inttrait: self $int_tobool def tostr: self $int_tostr + def todouble: + self $int_todouble # Project: Boolean object booltrait: @@ -50,6 +52,8 @@ object booltrait: self $bool_toint def tostr: self $bool_tostr + def todouble: + self $bool_todouble # Project: String object strtrait: @@ -66,3 +70,25 @@ object strtrait: self $str_tobool def toint: self $str_toint + def todouble: + self $str_todouble + +# Project: Double +object doubletrait: + def eq(other): + self $double_eq(other) + def add(other): + self $double_add(other) + def sub(other): + self $double_sub(other) + def mul(other): + self $double_mul(other) + def div(other): + self $double_div(other) + + def toint: + self $double_toint + def tobool: + self $double_tobool + def tostr: + self $double_tostr diff --git a/de.churl.simple/bytecodeinterpreter.py b/de.churl.simple/bytecodeinterpreter.py index 4f9b128..d7b55e7 100644 --- a/de.churl.simple/bytecodeinterpreter.py +++ b/de.churl.simple/bytecodeinterpreter.py @@ -72,6 +72,10 @@ class Interpreter(object): value = bytecode.symbols[oparg] w_value = self.space.newstring(value) stack.append(w_value) + elif opcode == compile.DOUBLE_LITERAL: # Project: Double + 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) diff --git a/de.churl.simple/compile.py b/de.churl.simple/compile.py index 4f03bcd..f6f4407 100644 --- a/de.churl.simple/compile.py +++ b/de.churl.simple/compile.py @@ -112,6 +112,7 @@ import simpleast BOOL_LITERAL = 1 # 1 or 0 # Project: Boolean INT_LITERAL = 2 # integer value STRING_LITERAL = 3 # Project: String +DOUBLE_LITERAL = 17 # Project: Double ASSIGNMENT = 4 # index of attrname METHOD_LOOKUP = 5 # index of method name METHOD_CALL = 6 # number of arguments @@ -192,6 +193,7 @@ def compile(ast, argumentnames=[], name=None): stack_effects = { BOOL_LITERAL: 1, # Project: Boolean STRING_LITERAL: 1, # Project: String + DOUBLE_LITERAL: 1, # Project: Double INT_LITERAL: 1, ASSIGNMENT: -1, METHOD_LOOKUP: 1, @@ -295,6 +297,10 @@ class Compiler(object): 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_ImplicitSelf(self, astnode, needsresult): self.emit(IMPLICIT_SELF) diff --git a/de.churl.simple/objmodel.py b/de.churl.simple/objmodel.py index 2c23865..481be16 100644 --- a/de.churl.simple/objmodel.py +++ b/de.churl.simple/objmodel.py @@ -2,7 +2,6 @@ from c3computation import compute_C3_mro as c3 class AbstractObject(object): - def call(self, w_receiver, args_w): return self @@ -26,7 +25,6 @@ class AbstractObject(object): class W_NormalObject(AbstractObject): - def __init__(self, name=None, slots=None, parents=None, space=None): self.space = space self.name = name @@ -131,12 +129,37 @@ class W_String(AbstractObject): __repr__ = __str__ + def istrue(self): + return self.value != 0. + + +# Project: Double +class W_Double(W_NormalObject): + def __init__(self, value, space=None): + self.value = float(value) + self.space = space + self.__trait = "doubletrait" + + 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__ + def istrue(self): return self.value != "" class W_Method(W_NormalObject): - def __init__(self, code, *args, **kwargs): super(W_Method, self).__init__(*args, **kwargs) self.code = code diff --git a/de.churl.simple/objspace.py b/de.churl.simple/objspace.py index c03a446..cee4110 100644 --- a/de.churl.simple/objspace.py +++ b/de.churl.simple/objspace.py @@ -1,4 +1,4 @@ -from objmodel import W_Integer, W_Boolean, W_String +from objmodel import W_Integer, W_Boolean, W_String, W_Double from objmodel import W_Method from objmodel import W_NormalObject @@ -57,6 +57,10 @@ class ObjectSpace(object): def newstring(self, value): return W_String(value, space=self) + # Project: Double + def newdouble(self, value): + return W_Double(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 fc7c3c0..87cb669 100644 --- a/de.churl.simple/primitives.py +++ b/de.churl.simple/primitives.py @@ -16,12 +16,10 @@ def primitive(name, unwrap_spec, wrap_spec): # decorator arguments unwrapped_args = () for t, arg in zip(unwrap_spec, args): # unpack values from simple-objects - if t is int: + if t in [int, str, float]: # Project: Double, String unwrapped_args += (arg.value,) elif t is bool: # Project: Boolean unwrapped_args += (bool(arg.value),) # isn't really necessary because "1 or 0" is also valid - elif t is str: # Project: String - unwrapped_args += (arg.value,) else: unwrapped_args += (arg,) @@ -33,6 +31,8 @@ def primitive(name, unwrap_spec, wrap_spec): # decorator arguments return space.newbool(result) elif wrap_spec is str: # Project: String return space.newstring(result) + elif wrap_spec is float: # Project: Double + return space.newdouble(result) return result unwrapper.__qualname__ = name @@ -93,6 +93,11 @@ def simple_int_tobstr(a): return a +@primitive("int_todouble", [int], float) +def simple_int_tobdouble(a): + return a + + # Project: Boolean @primitive("bool_and", [bool, bool], bool) def simple_bool_and(a, b): @@ -119,6 +124,11 @@ def simple_bool_tostr(a): return str(a).lower() +@primitive("bool_todouble", [bool], float) +def simple_bool_todouble(a): + return a + + # bool stuff for int @primitive("int_eq", [int, int], bool) def simple_int_eq(a, b): @@ -174,3 +184,49 @@ def simple_str_toint(a): @primitive("str_tobool", [str], bool) def simple_str_tobool(a): return a == "true" + + +@primitive("str_todouble", [str], float) +def simple_str_todouble(a): + return a + + +# Project: Double +@primitive("double_eq", [float, float], bool) +def simple_double_eq(a, b): + return a == b + + +@primitive("double_add", [float, float], float) +def simple_double_add(a, b): + return a + b + + +@primitive("double_sub", [float, float], float) +def simple_double_sub(a, b): + return a - b + + +@primitive("double_mul", [float, float], float) +def simple_double_mul(a, b): + return a * b + + +@primitive("double_div", [float, float], float) +def simple_double_div(a, b): + return a / b + + +@primitive("double_toint", [float], int) +def simple_double_toint(a): + return a + + +@primitive("double_tobool", [float], bool) +def simple_double_tobool(a): + return a + + +@primitive("double_tostr", [float], str) +def simple_double_tostr(a): + return a diff --git a/de.churl.simple/simpleast.py b/de.churl.simple/simpleast.py index a7a79a7..a53f840 100644 --- a/de.churl.simple/simpleast.py +++ b/de.churl.simple/simpleast.py @@ -113,6 +113,16 @@ class StringLiteral(Expression): self.value = str(value) +# Project: Double +class DoubleLiteral(Expression): + """ A double literal (like "1.0", ".0", "1.", "+1.0") """ + + attrs = ["value"] + + def __init__(self, value): + 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). diff --git a/de.churl.simple/simplelexer.py b/de.churl.simple/simplelexer.py index 9718494..04b9ddf 100644 --- a/de.churl.simple/simplelexer.py +++ b/de.churl.simple/simplelexer.py @@ -158,11 +158,17 @@ OpenBracket = r'[\[\(\{]' CloseBracket = r'[\]\)\}]' # ____________________________________________________________ -# Project: Boolean, String +# Project: Boolean, String, Double -Boolean = group(r'true', r'false') +Boolean = r"true|false" 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 @@ -182,8 +188,9 @@ While = r'while' Def = r'def' Object = r'object' -tokens = ["If", "Else", "While", "Def", "Object", "Number", "Ignore", - "String", "Boolean", # Project: Boolean, String +tokens = ["If", "Else", "While", "Def", "Object", "Ignore", + "String", "Boolean", "Double", # Project: Boolean, String, Double + "Number", # after Double "NewlineAndWhitespace", "OpenBracket", "CloseBracket", "Comma", "Assign", "Colon", "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 29fdc84..eb99719 100644 --- a/de.churl.simple/simpleparser.py +++ b/de.churl.simple/simpleparser.py @@ -6,7 +6,7 @@ from simplelexer import lex import simpleast pg = ParserGenerator(["If", "Else", "While", "Def", "Object", "Number", - "String", "Boolean", # Project: Boolean, String + "String", "Boolean", "Double", # Project: Boolean, String, Double "Name", "Indent", "Dedent", "Newline", "OpenBracket", "CloseBracket", "Comma", "Assign", "Colon", "Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", # Project: Sugar @@ -210,6 +210,12 @@ 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) + + @pg.production("basic_expression : implicitselfmethodcall") def implicitselfmethodcall(call): methodcall = call[0] diff --git a/mytests/test_double.py b/mytests/test_double.py index 6ce3f32..2547e93 100644 --- a/mytests/test_double.py +++ b/mytests/test_double.py @@ -1,8 +1,26 @@ +from rply import Token + +from simpleast import Program, ExprStatement, StringLiteral, ImplicitSelf, Assignment, DoubleLiteral +from simplelexer import lex from simpleparser import parse -from objmodel import W_Integer +from objmodel import W_Integer, W_Double from interpreter import Interpreter +def test_basic_double_lexing(): + assert lex("0.1")[0] == Token("Double", "0.1") + assert lex(".1")[0] == Token("Double", ".1") + assert lex("1.")[0] == Token("Double", "1.") + assert lex("-5.5")[0] == Token("Double", "-5.5") + assert lex("-.5")[0] == Token("Double", "-.5") + assert lex("x = 0.0005")[:3] == [Token("Name", "x"), Token("Assign", "="), Token("Double", "0.0005")] + + +def test_basic_double_parsing(): + assert parse("1.0") == Program([ExprStatement(DoubleLiteral("1.0"))]) + assert parse("x = -.1") == Program([Assignment(ImplicitSelf(), "x", DoubleLiteral("-0.1"))]) + + def test_double_assignment(): ast = parse(""" x = 1.5 @@ -15,7 +33,25 @@ x = 1.5 assert w_model.getvalue("x").value == 1.5 -def test_double_operations(): +def test_double_addition_subtraction(): + ast = parse(""" +x = 2.5 sub(2.4) +y = 2.5 add(2.4) +z = 3 sub(2.5) +""") + interpreter = Interpreter() + w_model = interpreter.make_module() + interpreter.eval(ast, w_model) + + assert isinstance(w_model.getvalue("x"), W_Double) + assert w_model.getvalue("x").value == (2.5 - 2.4) + assert isinstance(w_model.getvalue("y"), W_Double) + assert w_model.getvalue("y").value == (2.5 + 2.4) + assert isinstance(w_model.getvalue("z"), W_Integer) + assert w_model.getvalue("z").value == 0 + + +def test_double_division(): ast = parse(""" x = 3 div(2) y = 3 div(2.0) @@ -33,6 +69,24 @@ z = 3.0 div(2.0) assert w_model.getvalue("z").value == 1.5 +def test_double_multiplication(): + ast = parse(""" +x = 3.3 mul(3.0) +y = 3.3 mul(3) +z = 3 mul(3.3) +""") + interpreter = Interpreter() + w_model = interpreter.make_module() + interpreter.eval(ast, w_model) + + assert isinstance(w_model.getvalue("x"), W_Double) + assert w_model.getvalue("x").value == (3.3 * 3.0) + assert isinstance(w_model.getvalue("y"), W_Double) + assert w_model.getvalue("y").value == (3.3 * 3) + assert isinstance(w_model.getvalue("z"), W_Integer) + assert w_model.getvalue("z").value == 9 + + def test_double_conversion(): ast = parse(""" x = 1.5 toint