From 86cd7027f307ce9d1068782595f75d61b522225d Mon Sep 17 00:00:00 2001 From: Inte Vleminckx Date: Wed, 5 Feb 2025 15:20:25 +0100 Subject: [PATCH 1/4] Adding bytes as a type --- api/od.py | 8 ++++++++ bootstrap/primitive.py | 4 +++- bootstrap/scd.py | 4 +++- concrete_syntax/textual_od/parser.py | 9 ++++++++- concrete_syntax/textual_od/renderer.py | 2 +- services/od.py | 10 ++++++++++ services/primitives/bytes_type.py | 24 ++++++++++++++++++++++++ 7 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 services/primitives/bytes_type.py diff --git a/api/od.py b/api/od.py index 2740772..ffdef1c 100644 --- a/api/od.py +++ b/api/od.py @@ -5,6 +5,7 @@ from services.primitives.boolean_type import Boolean from services.primitives.integer_type import Integer from services.primitives.string_type import String from services.primitives.actioncode_type import ActionCode +from services.primitives.bytes_type import Bytes from uuid import UUID from typing import Optional from util.timer import Timer @@ -41,6 +42,7 @@ class ODAPI: self.create_integer_value = self.od.create_integer_value self.create_string_value = self.od.create_string_value self.create_actioncode_value = self.od.create_actioncode_value + self.create_bytes_value = self.od.create_bytes_value self.__recompute_mappings() @@ -208,6 +210,8 @@ class ODAPI: tgt = self.create_actioncode_value(name, value) else: tgt = self.create_string_value(name, value) + elif isinstance(value, bytes): + tgt = self.create_bytes_value(name, value) else: raise Exception("Unimplemented type "+value) self.__recompute_mappings() @@ -235,6 +239,10 @@ class ODAPI: if to_overwrite_type != "String": raise Exception(f"Cannot assign string value '{value}' to value of type {to_overwrite_type}.") String(referred_model, self.state).create(value) + elif isinstance(value, bytes): + if to_overwrite_type != "Bytes": + raise Exception(f"Cannot assign bytes value '{value}' to value of type {to_overwrite_type}.") + Bytes(referred_model, self.state).create(value) else: raise Exception("Unimplemented type "+value) diff --git a/bootstrap/primitive.py b/bootstrap/primitive.py index 853b552..4e2b36f 100644 --- a/bootstrap/primitive.py +++ b/bootstrap/primitive.py @@ -47,7 +47,7 @@ def bootstrap_constraint(class_node, type_name: str, python_type: str, scd_root: bottom.create_edge(constraint_node, scd_node, "Morphism") bottom.create_edge(constraint_link, scd_link, "Morphism") -def bootstrap_primitive_types(scd_root, state, integer_type, boolean_type, float_type, string_type, type_type, actioncode_type): +def bootstrap_primitive_types(scd_root, state, integer_type, boolean_type, float_type, string_type, type_type, actioncode_type, bytes_type): # Order is important: Integer must come first class_integer = bootstrap_type("Integer", scd_root, integer_type, state) class_type = bootstrap_type("Type", scd_root, type_type, state) @@ -55,6 +55,7 @@ def bootstrap_primitive_types(scd_root, state, integer_type, boolean_type, float class_float = bootstrap_type("Float", scd_root, float_type, state) class_string = bootstrap_type("String", scd_root, string_type, state) class_actioncode = bootstrap_type("ActionCode", scd_root, actioncode_type, state) + class_bytes = bootstrap_type("Bytes", scd_root, bytes_type, state) # Can only create constraints after ActionCode type has been created: bootstrap_constraint(class_integer, "Integer", "int", scd_root, integer_type, actioncode_type, state) @@ -63,3 +64,4 @@ def bootstrap_primitive_types(scd_root, state, integer_type, boolean_type, float bootstrap_constraint(class_float, "Float", "float", scd_root, float_type, actioncode_type, state) bootstrap_constraint(class_string, "String", "str", scd_root, string_type, actioncode_type, state) bootstrap_constraint(class_actioncode, "ActionCode", "str", scd_root, actioncode_type, actioncode_type, state) + bootstrap_constraint(class_bytes, "Bytes", "bytes", scd_root, bytes_type, actioncode_type, state) diff --git a/bootstrap/scd.py b/bootstrap/scd.py index cac04c6..fb94b21 100644 --- a/bootstrap/scd.py +++ b/bootstrap/scd.py @@ -32,6 +32,7 @@ def bootstrap_scd(state: State) -> UUID: float_type_root = create_model_root(bottom, "Float") type_type_root = create_model_root(bottom, "Type") actioncode_type_root = create_model_root(bottom, "ActionCode") + bytes_type_root = create_model_root(bottom, "Bytes") # create MCL, without morphism links @@ -132,7 +133,8 @@ def bootstrap_scd(state: State) -> UUID: float_type_root, string_type_root, type_type_root, - actioncode_type_root) + actioncode_type_root, + bytes_type_root) # bootstrap_integer_type(mcl_root, integer_type_root, integer_type_root, actioncode_type_root, state) # bootstrap_boolean_type(mcl_root, boolean_type_root, integer_type_root, actioncode_type_root, state) # bootstrap_float_type(mcl_root, float_type_root, integer_type_root, actioncode_type_root, state) diff --git a/concrete_syntax/textual_od/parser.py b/concrete_syntax/textual_od/parser.py index b679210..35ca933 100644 --- a/concrete_syntax/textual_od/parser.py +++ b/concrete_syntax/textual_od/parser.py @@ -21,6 +21,7 @@ literal: INT | STR | BOOL | CODE + | BYTES | INDENTED_CODE INT: /[0-9]+/ @@ -28,6 +29,8 @@ STR: /"[^"]*"/ | /'[^']*'/ BOOL: "True" | "False" CODE: /`[^`]*`/ +BYTES: /b"[^"]*"/ + | /b'[^']*'/ INDENTED_CODE: /```[^`]*```/ type_name: IDENTIFIER @@ -67,7 +70,7 @@ def parse_od(state, primitive_types = { type_name : UUID(state.read_value(state.read_dict(state.read_root(), type_name))) - for type_name in ["Integer", "String", "Boolean", "ActionCode"] + for type_name in ["Integer", "String", "Boolean", "ActionCode", "Bytes"] } class T(Transformer): @@ -89,6 +92,10 @@ def parse_od(state, def CODE(self, token): return (_Code(str(token[1:-1])), token.line) # strip the `` + def BYTES(self, token): + # return (bytes(token[2:-1], "utf-8"), token.line) # Strip b"" or b'' + return (bytes(token[2:-1], "utf-8"), token.line) # Strip b"" or b'' + def INDENTED_CODE(self, token): skip = 4 # strip the ``` and the following newline character space_count = 0 diff --git a/concrete_syntax/textual_od/renderer.py b/concrete_syntax/textual_od/renderer.py index a3fc030..fae1e72 100644 --- a/concrete_syntax/textual_od/renderer.py +++ b/concrete_syntax/textual_od/renderer.py @@ -9,7 +9,7 @@ def render_od(state, m_id, mm_id, hide_names=True): m_od = od.OD(mm_id, m_id, state) - serialized = set(["Integer", "String", "Boolean", "ActionCode"]) # assume these types always already exist + serialized = set(["Integer", "String", "Boolean", "ActionCode", "Bytes"]) # assume these types always already exist def display_name(name: str): # object names that start with "__" are hidden diff --git a/services/od.py b/services/od.py index 3b8700a..816b225 100644 --- a/services/od.py +++ b/services/od.py @@ -5,6 +5,7 @@ from services.primitives.integer_type import Integer from services.primitives.string_type import String from services.primitives.boolean_type import Boolean from services.primitives.actioncode_type import ActionCode +from services.primitives.bytes_type import Bytes from api.cd import CDAPI from typing import Optional @@ -147,6 +148,13 @@ class OD: actioncode_t.create(value) return self.create_model_ref(name, "ActionCode", actioncode_node) + def create_bytes_value(self, name: str, value: str): + from services.primitives.bytes_type import Bytes + bytes_node = self.bottom.create_node() + bytes_t = Bytes(bytes_node, self.bottom.state) + bytes_t.create(value) + return self.create_model_ref(name, "Bytes", bytes_node) + # Identical to the same SCD method: def create_model_ref(self, name: str, type_name: str, model: UUID): # create element + morphism links @@ -389,6 +397,8 @@ def read_primitive_value(bottom, modelref: UUID, mm: UUID): return Boolean(referred_model, bottom.state).read(), typ_name elif typ_name == "ActionCode": return ActionCode(referred_model, bottom.state).read(), typ_name + elif typ_name == "Bytes": + return Bytes(referred_model, bottom.state).read(), typ_name else: raise Exception("Unimplemented type:", typ_name) diff --git a/services/primitives/bytes_type.py b/services/primitives/bytes_type.py new file mode 100644 index 0000000..04f001d --- /dev/null +++ b/services/primitives/bytes_type.py @@ -0,0 +1,24 @@ +from uuid import UUID +from state.base import State +from services.bottom.V0 import Bottom + + +class Bytes: + def __init__(self, model: UUID, state: State): + self.model = model + self.bottom = Bottom(state) + type_model_id_node, = self.bottom.read_outgoing_elements(state.read_root(), "Bytes") + self.type_model = UUID(self.bottom.read_value(type_model_id_node)) + + def create(self, value: bool): + if "bytes" in self.bottom.read_keys(self.model): + instance, = self.bottom.read_outgoing_elements(self.model, "bytes") + self.bottom.delete_element(instance) + _instance = self.bottom.create_node(value) + self.bottom.create_edge(self.model, _instance, "bytes") + _type, = self.bottom.read_outgoing_elements(self.type_model, "Bytes") + self.bottom.create_edge(_instance, _type, "Morphism") + + def read(self): + instance, = self.bottom.read_outgoing_elements(self.model, "bytes") + return self.bottom.read_value(instance) \ No newline at end of file From 98f36c4cf02b684e37a06f7c8153a9f62dd90a7f Mon Sep 17 00:00:00 2001 From: Inte Vleminckx Date: Wed, 5 Feb 2025 16:24:22 +0100 Subject: [PATCH 2/4] Adding bytes as a type --- concrete_syntax/common.py | 5 +++++ state/base.py | 5 +++-- transformation/merger.py | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/concrete_syntax/common.py b/concrete_syntax/common.py index 3427b03..1ab0d3c 100644 --- a/concrete_syntax/common.py +++ b/concrete_syntax/common.py @@ -16,6 +16,8 @@ def display_value(val: any, type_name: str, indentation=0, newline_character='\n return '"'+val+'"'.replace('\n', newline_character) elif type_name == "Integer" or type_name == "Boolean": return str(val) + elif type_name == "Bytes": + return val else: raise Exception("don't know how to display value" + type_name) @@ -48,6 +50,9 @@ class TBase(Transformer): def CODE(self, token): return _Code(str(token[1:-1])) # strip the `` + def BYTES(self, token): + return (bytes(token[2:-1], "utf-8"), token.line) # Strip b"" or b'' + def INDENTED_CODE(self, token): skip = 4 # strip the ``` and the following newline character space_count = 0 diff --git a/state/base.py b/state/base.py index 614ae9e..78c4307 100644 --- a/state/base.py +++ b/state/base.py @@ -2,13 +2,14 @@ from abc import ABC, abstractmethod from typing import Any, List, Tuple, Optional, Union from uuid import UUID, uuid4 -primitive_types = (int, float, str, bool) +primitive_types = (int, float, str, bool, bytes) INTEGER = ("Integer",) FLOAT = ("Float",) STRING = ("String",) BOOLEAN = ("Boolean",) TYPE = ("Type",) -type_values = (INTEGER, FLOAT, STRING, BOOLEAN, TYPE) +BYTES = ("Bytes",) +type_values = (INTEGER, FLOAT, STRING, BOOLEAN, TYPE, BYTES) Node = UUID diff --git a/transformation/merger.py b/transformation/merger.py index 6caecb1..fcf6f81 100644 --- a/transformation/merger.py +++ b/transformation/merger.py @@ -4,7 +4,7 @@ from concrete_syntax.textual_od import parser, renderer from services.scd import SCD from util.timer import Timer -PRIMITIVE_TYPES = set(["Integer", "String", "Boolean", "ActionCode"]) +PRIMITIVE_TYPES = set(["Integer", "String", "Boolean", "ActionCode", "Bytes"]) # Merges N models. The models must have the same meta-model. # Care should be taken to avoid naming collisions before calling this function. @@ -12,7 +12,7 @@ def merge_models(state, mm, models: list[UUID]): with Timer("merge_models"): primitive_types = { type_name : UUID(state.read_value(state.read_dict(state.read_root(), type_name))) - for type_name in ["Integer", "String", "Boolean", "ActionCode"] + for type_name in ["Integer", "String", "Boolean", "ActionCode", "Bytes"] } merged = state.create_node() From e0136937b9f9a30bb085432388a93a85d53c6d96 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Wed, 5 Feb 2025 16:26:22 +0100 Subject: [PATCH 3/4] cleanup --- bootstrap/scd.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/bootstrap/scd.py b/bootstrap/scd.py index fb94b21..facf176 100644 --- a/bootstrap/scd.py +++ b/bootstrap/scd.py @@ -2,15 +2,7 @@ from state.base import State, UUID from services.bottom.V0 import Bottom from services.primitives.boolean_type import Boolean from services.primitives.string_type import String -from bootstrap.primitive import ( - bootstrap_primitive_types - # bootstrap_boolean_type, - # bootstrap_float_type, - # bootstrap_integer_type, - # bootstrap_string_type, - # bootstrap_type_type, - # bootstrap_actioncode_type -) +from bootstrap.primitive import bootstrap_primitive_types def create_model_root(bottom: Bottom, model_name: str) -> UUID: From 51b8bdb00152a401ec7c5a0f7e89178acc1fe633 Mon Sep 17 00:00:00 2001 From: Inte Vleminckx Date: Wed, 5 Feb 2025 16:26:30 +0100 Subject: [PATCH 4/4] Add test for bytes type --- state/test/test_create_nodevalue.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/state/test/test_create_nodevalue.py b/state/test/test_create_nodevalue.py index 2fddbb5..ca4e8b9 100644 --- a/state/test/test_create_nodevalue.py +++ b/state/test/test_create_nodevalue.py @@ -171,3 +171,12 @@ def test_create_nodevalue_string_type(state): def test_create_nodevalue_invalid_type(state): id1 = state.create_nodevalue(("Class",)) assert id1 == None + + +@pytest.mark.usefixtures("state") +def test_create_nodevalue_bytes_type(state): + id1 = state.create_nodevalue(("Bytes",)) + assert id1 != None + + v = state.read_value(id1) + assert v == ("Bytes",) \ No newline at end of file