Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from mpscli.model.builder.SModelBuilderBase import SModelBuilderBase
import uuid


class SModelBuilderBinaryPersistency(SModelBuilderBase):

# the following constants are taked from the MPS sources found at:
# https://github.com/JetBrains/MPS/blob/f9075b2832077358fd85a15a52bba76a9dad07a3/core/persistence/source/jetbrains/mps/persistence/binary/BinaryPersistence.java#L82
HEADER_START = 0x91ABABA9
STREAM_ID_V2 = 0x00000400

# https://github.com/JetBrains/MPS/blob/f9075b2832077358fd85a15a52bba76a9dad07a3/core/kernel/source/jetbrains/mps/util/io/ModelOutputStream.java
MODELREF_INDEX = 9
MODELID_REGULAR = 0x28

def build(self, path_to_model):
print("Building binary persistency model from:", path_to_model)
with open(path_to_model, mode='rb') as file:
fileContent = file.read()
header = int.from_bytes(fileContent[:4], byteorder='big')
if header != self.HEADER_START:
raise ValueError("Invalid file format")
streamId = int.from_bytes(fileContent[4:8], byteorder='big')
if streamId != self.STREAM_ID_V2:
raise ValueError("Unsupported stream ID")
modelRef = self.readModelReference(fileContent, 8)
# Implement the binary persistency model building logic here
pass



def readModelReference(self, fileContent, pos):
modelRefIndex = int.from_bytes(fileContent[pos:pos+2], byteorder='big')
if modelRefIndex != self.MODELREF_INDEX:
return self.readModelId(fileContent, pos+2)
pass

def readModelId(self, fileContent, pos):
print("Reading Model ID at position:", pos)
modelId = int.from_bytes(fileContent[pos:pos+1], byteorder='big')
if modelId == self.MODELID_REGULAR:
return self.readUUID(fileContent, pos+1)
else:
print(f"Error: unhandled model ID type: 0x{modelId:X}")
return None


def readUUID(self, fileContent, pos):
headBits = self.readLong(fileContent, pos)
tailBits = self.readLong(fileContent, pos + 8)
uuid_val = uuid.UUID(int=(headBits << 64) | tailBits)
print("------------ UUID:", uuid_val)
return (headBits, tailBits)

def readLong(self, fileContent, pos):
return int.from_bytes(fileContent[pos:pos+8], byteorder='big')
4 changes: 3 additions & 1 deletion mps-cli-py/src/mpscli/model/builder/SSolutionBuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from mpscli.model.SSolution import SSolution
from mpscli.model.builder.SModelBuilderDefaultPersistency import SModelBuilderDefaultPersistency
from mpscli.model.builder.SModelBuilderFilePerRootPersistency import SModelBuilderFilePerRootPersistency

from mpscli.model.builder.SModelBuilderBinaryPersistency import SModelBuilderBinaryPersistency

class SSolutionBuilder:

Expand All @@ -20,6 +20,8 @@ def build_solution(self, path_to_msd_file):
for path_to_model in path_to_models_dir.iterdir():
if path_to_model.is_dir():
builder = SModelBuilderFilePerRootPersistency()
elif path_to_model.suffix == ".mpb":
builder = SModelBuilderBinaryPersistency()
else:
builder = SModelBuilderDefaultPersistency()
model = builder.build(path_to_model)
Expand Down
18 changes: 18 additions & 0 deletions mps-cli-py/tests/test_binary_persistency_low_level_access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

from mpscli.model.builder.SModelBuilderBinaryPersistency import SModelBuilderBinaryPersistency
from tests.test_base import TestBase


class TestBinaryPersistencyLowLevelAccess(TestBase):

def testReadModelId(self):
path_to_model = "../mps_test_projects/mps_cli_binary_persistency_generated_low_level_access_test_data/mps.cli.lanuse.library_top.binary_persistency.authors_top.mpb"
with open(path_to_model, mode='rb') as file:
fileContent = file.read()
binaryPersistencyReader = SModelBuilderBinaryPersistency()
modelId = binaryPersistencyReader.readModelId(fileContent, 9)

self.assertEqual('cf91f372-8bfd-44b8-8e34-024eb23e64a8', modelId)
# Test reading model reference
pass

4 changes: 3 additions & 1 deletion mps-cli-py/tests/test_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ class TestNodes(TestBase):
('mps_cli_lanuse_default_persistency',
'mps.cli.lanuse.library_top.default_persistency.authors_top'),
('mps_cli_lanuse_binary',
'mps.cli.lanuse.library_top.authors_top')])
'mps.cli.lanuse.library_top.authors_top'),
('mps_cli_binary_persistency_generated',
'mps.cli.lanuse_binary_persistency.library_top.authors_top')])
def test_build_root_nodes(self, test_data_location, library_top_authors_top_model_name):
"""
Test the building of root nodes
Expand Down
109 changes: 108 additions & 1 deletion mps-cli-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions mps-cli-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ std = []
walkdir = "2.5.0"
roxmltree = "0.20.0"
zip = "2.1.5"
byteorder = "1.5.0"
uuid = { version = "1.19.0", features = ["v4"] }

[profile.release]
lto = "fat"
Expand Down
3 changes: 3 additions & 0 deletions mps-cli-rs/src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ mod ssolution_builder;
mod smodel_builder_base;
mod smodel_builder_default_persistency;
mod smodel_builder_file_per_root_persistency;
mod smodel_builder_binary_persistency;
mod smodel_builder_binary_persistency_utils;
mod smodel_builder_binary_persistency_constants;
mod slanguage_builder;
mod node_id_utils;
38 changes: 36 additions & 2 deletions mps-cli-rs/src/builder/node_id_utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::HashMap;

struct NodeIdEncodingUtils {
pub(crate) struct NodeIdEncodingUtils {
my_index_chars : String,
min_char : u8,
my_char_to_value : HashMap<usize, usize>,
Expand All @@ -24,7 +24,8 @@ impl NodeIdEncodingUtils {
my_char_to_value : my_char_to_value,
}
}

// transforms from a string representing java friendly base 64 encoding
// a string representing its decimal encoding
pub(crate) fn decode(&self, uid_string : String) -> String {
let mut res = 0;
let bytes = uid_string.as_bytes();
Expand All @@ -41,6 +42,38 @@ impl NodeIdEncodingUtils {
return res.to_string();
}

// transforms from a string representing a decimal number to
// a string representing its java friendly base 64 encoding
pub(crate) fn encode(&self, uid_string : String) -> String {
let uid_number = match uid_string.parse::<u64>() {
Ok(val) => val,
Err(_) => return String::from("Invalid decimal input"),
};

let mut num = uid_number;
let mut res_size = 0;
while num > 0 {
res_size += 1;
num /= 64;
}
if res_size == 0 {
res_size = 1;
}
let mut res: Vec<char> = Vec::with_capacity(res_size as usize);

let mut num = uid_number;
while num > 0 {
let pos = num % 64;
let crt_char= self.my_index_chars.chars().nth(pos as usize).unwrap();
res.push(crt_char);
num /= 64;
}

res.reverse();
let res: String = res.into_iter().collect();
return res;
}

}

#[cfg(test)]
Expand All @@ -52,5 +85,6 @@ mod tests {
let node_id_utils = NodeIdEncodingUtils::new();

assert_eq!("5731700211660045983", node_id_utils.decode(String::from("4Yb5JA31NUv")));
assert_eq!("4Yb5JA31NUv", node_id_utils.encode(String::from("5731700211660045983")));
}
}
2 changes: 1 addition & 1 deletion mps-cli-rs/src/builder/smodel_builder_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl SModelBuilderCache {
}
}

fn get_model(&mut self, name : String, uuid : String) -> Rc<RefCell<SModel>> {
pub(crate) fn get_model(&mut self, name : String, uuid : String) -> Rc<RefCell<SModel>> {
if let Some(model) = self.index_2_model.get(&uuid) {
Rc::clone(&model)
} else {
Expand Down
Loading
Loading