├── cpp ├── .gitignore ├── README.md ├── CMakeLists.txt └── src │ └── main.cpp ├── golang ├── go.mod └── main.go ├── examples ├── float32_array.json ├── strings_array.beve ├── uint16_array.json ├── float64_array.json ├── strings_array.json ├── nested_object.json ├── complex_numbers.json ├── float32_array.beve ├── float64_array.beve ├── nested_object.beve ├── uint16_array.beve ├── complex_numbers.beve ├── general_object.beve └── general_object.json ├── .gitignore ├── javascript ├── package.json ├── beve.test.js ├── beve_file.js └── beve.js ├── scripts └── serve-docs.sh ├── .github └── workflows │ └── test.yml ├── LICENSE ├── docs ├── README.md ├── implementations.html ├── proposals.html ├── css │ └── style.css └── index.html ├── python └── load_beve.py ├── README.md └── matlab ├── write_beve.m └── load_beve.m /cpp/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /golang/go.mod: -------------------------------------------------------------------------------- 1 | module beve 2 | 3 | go 1.22.3 4 | -------------------------------------------------------------------------------- /examples/float32_array.json: -------------------------------------------------------------------------------- 1 | {"values":[0.1,0.2,0.3,1.5]} -------------------------------------------------------------------------------- /examples/strings_array.beve: -------------------------------------------------------------------------------- 1 | values< cat dog elephant -------------------------------------------------------------------------------- /examples/uint16_array.json: -------------------------------------------------------------------------------- 1 | {"values":[0,1,2,1024,65535]} -------------------------------------------------------------------------------- /examples/float64_array.json: -------------------------------------------------------------------------------- 1 | {"values":[3.14159,2.71828,1.41421]} -------------------------------------------------------------------------------- /examples/strings_array.json: -------------------------------------------------------------------------------- 1 | {"values":["cat","dog","elephant"]} -------------------------------------------------------------------------------- /examples/nested_object.json: -------------------------------------------------------------------------------- 1 | {"id":42,"nested":{"name":"sensor-A","coords":[1,2.5,-3.75]}} -------------------------------------------------------------------------------- /cpp/README.md: -------------------------------------------------------------------------------- 1 | ## C++ example for BEVE using [Glaze](https://github.com/stephenberry/glaze) 2 | -------------------------------------------------------------------------------- /examples/complex_numbers.json: -------------------------------------------------------------------------------- 1 | {"complex_floats":[[1,0.5],[0.1,2]],"complex_int32_t":[[-1,5],[7,-9]]} -------------------------------------------------------------------------------- /examples/float32_array.beve: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beve-org/beve/HEAD/examples/float32_array.beve -------------------------------------------------------------------------------- /examples/float64_array.beve: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beve-org/beve/HEAD/examples/float64_array.beve -------------------------------------------------------------------------------- /examples/nested_object.beve: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beve-org/beve/HEAD/examples/nested_object.beve -------------------------------------------------------------------------------- /examples/uint16_array.beve: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beve-org/beve/HEAD/examples/uint16_array.beve -------------------------------------------------------------------------------- /examples/complex_numbers.beve: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beve-org/beve/HEAD/examples/complex_numbers.beve -------------------------------------------------------------------------------- /examples/general_object.beve: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beve-org/beve/HEAD/examples/general_object.beve -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.DS_Store 3 | *.beve 4 | *.cache 5 | *.vscode 6 | *.json 7 | 8 | # Allow .beve and .json files in examples 9 | !examples/*.json 10 | !examples/*.beve 11 | 12 | # Allow package.json in javascript 13 | !javascript/package.json 14 | !javascript/package-lock.json 15 | 16 | # JavaScript 17 | javascript/node_modules/ 18 | 19 | # Added by cargo 20 | 21 | /target 22 | # RUST 23 | rust/target 24 | rust/Cargo.lock -------------------------------------------------------------------------------- /javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "beve", 3 | "version": "1.0.0", 4 | "description": "BEVE (Binary Efficient Versatile Encoding) JavaScript library", 5 | "main": "beve.js", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "keywords": [ 10 | "beve", 11 | "binary", 12 | "encoding", 13 | "serialization" 14 | ], 15 | "license": "MIT", 16 | "devDependencies": { 17 | "jest": "^29.7.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.30) 2 | project(beve_cpp) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | file(GLOB srcs src/*.cpp include/*.hpp) 7 | 8 | include(FetchContent) 9 | 10 | FetchContent_Declare( 11 | glaze 12 | GIT_REPOSITORY https://github.com/stephenberry/glaze.git 13 | GIT_TAG main 14 | GIT_SHALLOW TRUE 15 | ) 16 | 17 | FetchContent_MakeAvailable(glaze) 18 | 19 | add_executable(${PROJECT_NAME} ${srcs}) 20 | target_link_libraries(${PROJECT_NAME} glaze::glaze) -------------------------------------------------------------------------------- /scripts/serve-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Simple script to serve the BEVE documentation locally 4 | 5 | PORT=8000 6 | DIR="docs" 7 | 8 | echo "========================================" 9 | echo "BEVE Documentation Local Server" 10 | echo "========================================" 11 | echo "" 12 | echo "Starting server on http://localhost:$PORT" 13 | echo "" 14 | echo "Open your browser and visit:" 15 | echo " → http://localhost:$PORT" 16 | echo "" 17 | echo "Press Ctrl+C to stop the server" 18 | echo "========================================" 19 | echo "" 20 | 21 | cd "$DIR" && python3 -m http.server $PORT 22 | -------------------------------------------------------------------------------- /examples/general_object.json: -------------------------------------------------------------------------------- 1 | {"fixed_object":{"int_array":[0,1,2,3,4,5,6],"float_array":[0.1,0.2,0.3,0.4,0.5,0.6],"double_array":[3288398.238,2.33E24,28.9,0.928759872,0.22222848,0.1,0.2,0.3,0.4]},"fixed_name_object":{"name0":"James","name1":"Abraham","name2":"Susan","name3":"Frank","name4":"Alicia"},"another_object":{"string":"here is some text","another_string":"Hello World","boolean":false,"nested_object":{"v3s":[[0.12345,0.23456,0.001345],[0.3894675,97.39827,297.92387],[18.18,87.289,2988.298]],"id":"298728949872"}},"string_array":["Cat","Dog","Elephant","Tiger"],"string":"Hello world","number":3.14,"boolean":true,"another_bool":false} -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | javascript: 11 | name: JavaScript Tests 12 | runs-on: ubuntu-latest 13 | 14 | defaults: 15 | run: 16 | working-directory: javascript 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: '20' 25 | cache: 'npm' 26 | cache-dependency-path: javascript/package-lock.json 27 | 28 | - name: Install dependencies 29 | run: npm ci 30 | 31 | - name: Run tests 32 | run: npm test 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Stephen Berry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # BEVE GitHub Pages Website 2 | 3 | This directory contains the GitHub Pages website for the BEVE specification. 4 | 5 | ## Enabling GitHub Pages 6 | 7 | To enable GitHub Pages for this repository: 8 | 9 | 1. Go to your repository on GitHub 10 | 2. Click on **Settings** 11 | 3. Scroll down to the **Pages** section in the left sidebar 12 | 4. Under **Source**, select **Deploy from a branch** 13 | 5. Under **Branch**, select **main** (or your default branch) and **`/docs`** folder 14 | 6. Click **Save** 15 | 16 | GitHub will automatically build and deploy your site. After a few minutes, your site will be available at: 17 | ``` 18 | https://.github.io// 19 | ``` 20 | 21 | For the BEVE repository, it should be: 22 | ``` 23 | https://beve-org.github.io/beve/ 24 | ``` 25 | 26 | ## Local Development 27 | 28 | To preview the site locally: 29 | 30 | 1. Simply open `docs/index.html` in your web browser, or 31 | 2. Use a local web server: 32 | ```bash 33 | cd docs 34 | python3 -m http.server 8000 35 | ``` 36 | Then visit `http://localhost:8000` in your browser. 37 | 38 | ## Site Structure 39 | 40 | - `index.html` - Main specification page 41 | - `implementations.html` - Implementation guides and links 42 | - `proposals.html` - Extension proposals and working drafts 43 | - `css/style.css` - Stylesheet for all pages 44 | - `css/` - Additional CSS files 45 | - `js/` - JavaScript files (if needed in the future) 46 | 47 | ## Updating the Site 48 | 49 | To update the website: 50 | 51 | 1. Edit the HTML files in the `docs/` directory 52 | 2. Commit and push your changes to the main branch 53 | 3. GitHub Pages will automatically rebuild and deploy the site 54 | 55 | ## External Dependencies 56 | 57 | The site uses the following CDN resources: 58 | - **Highlight.js** (v11.9.0) - For syntax highlighting of code blocks 59 | - CSS: `github-dark.min.css` 60 | - JS: `highlight.min.js` 61 | 62 | These are loaded from CDN and don't require local installation. 63 | 64 | ## Custom Domain (Optional) 65 | 66 | To use a custom domain: 67 | 68 | 1. Add a file named `CNAME` to the `docs/` directory 69 | 2. Put your custom domain in the file (e.g., `beve.dev`) 70 | 3. Configure your DNS settings to point to GitHub Pages 71 | 4. GitHub will automatically serve your site from the custom domain 72 | 73 | For more information, see: https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site 74 | 75 | ## License 76 | 77 | The website content is part of the BEVE project and is licensed under the MIT License. 78 | -------------------------------------------------------------------------------- /cpp/src/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "glaze/glaze.hpp" 4 | #include "glaze/glaze_exceptions.hpp" 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | static constexpr std::string_view json0 = R"( 13 | { 14 | "fixed_object": { 15 | "int_array": [0, 1, 2, 3, 4, 5, 6], 16 | "float_array": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6], 17 | "double_array": [3288398.238, 233e22, 289e-1, 0.928759872, 0.22222848, 0.1, 0.2, 0.3, 0.4] 18 | }, 19 | "fixed_name_object": { 20 | "name0": "James", 21 | "name1": "Abraham", 22 | "name2": "Susan", 23 | "name3": "Frank", 24 | "name4": "Alicia" 25 | }, 26 | "another_object": { 27 | "string": "here is some text", 28 | "another_string": "Hello World", 29 | "boolean": false, 30 | "nested_object": { 31 | "v3s": [[0.12345, 0.23456, 0.001345], 32 | [0.3894675, 97.39827, 297.92387], 33 | [18.18, 87.289, 2988.298]], 34 | "id": "298728949872" 35 | } 36 | }, 37 | "string_array": ["Cat", "Dog", "Elephant", "Tiger"], 38 | "string": "Hello world", 39 | "number": 3.14, 40 | "boolean": true, 41 | "another_bool": false 42 | } 43 | )"; 44 | 45 | struct fixed_object_t 46 | { 47 | std::vector int_array; 48 | std::vector float_array; 49 | std::vector double_array; 50 | }; 51 | 52 | struct fixed_name_object_t 53 | { 54 | std::string name0{}; 55 | std::string name1{}; 56 | std::string name2{}; 57 | std::string name3{}; 58 | std::string name4{}; 59 | }; 60 | 61 | struct nested_object_t 62 | { 63 | std::vector> v3s{}; 64 | std::string id{}; 65 | }; 66 | 67 | struct another_object_t 68 | { 69 | std::string string{}; 70 | std::string another_string{}; 71 | bool boolean{}; 72 | nested_object_t nested_object{}; 73 | }; 74 | 75 | struct obj_t 76 | { 77 | fixed_object_t fixed_object{}; 78 | fixed_name_object_t fixed_name_object{}; 79 | another_object_t another_object{}; 80 | std::vector string_array{}; 81 | std::string string{}; 82 | double number{}; 83 | bool boolean{}; 84 | bool another_bool{}; 85 | }; 86 | 87 | void example_general_object() 88 | { 89 | obj_t obj{}; 90 | glz::ex::read_json(obj, json0); 91 | glz::ex::write_file_beve(obj, "examples/general_object.beve", std::string{}); 92 | 93 | obj = {}; 94 | glz::ex::read_file_beve(obj, "examples/general_object.beve", std::string{}); 95 | glz::ex::write_file_json(obj, "examples/general_object.json", std::string{}); 96 | } 97 | 98 | struct complex_object 99 | { 100 | std::vector> complex_floats{ {1.0f, 0.5f}, {0.1f, 2.0f} }; 101 | std::vector> complex_int32_t{ {-1, 5}, {7, -9} }; 102 | }; 103 | 104 | void complex_numbers() 105 | { 106 | complex_object obj{}; 107 | std::string buffer{}; 108 | glz::ex::write_file_beve(obj, "examples/complex_numbers.beve", buffer); 109 | glz::ex::write_file_json(obj, "examples/complex_numbers.json", buffer); 110 | }; 111 | 112 | struct float_array_t { std::vector values; }; 113 | struct double_array_t { std::vector values; }; 114 | struct uint16_array_t { std::vector values; }; 115 | struct strings_array_t { std::vector values; }; 116 | struct nested_t { std::string name; std::vector coords; }; 117 | struct nested_object_t2 { int id; nested_t nested; }; 118 | 119 | void arrays_and_nested() 120 | { 121 | { 122 | float_array_t obj{ .values = {0.1f, 0.2f, 0.3f, 1.5f} }; 123 | glz::ex::write_file_beve(obj, "examples/float32_array.beve", std::string{}); 124 | glz::ex::write_file_json(obj, "examples/float32_array.json", std::string{}); 125 | } 126 | { 127 | double_array_t obj{ .values = {3.14159, 2.71828, 1.41421} }; 128 | glz::ex::write_file_beve(obj, "examples/float64_array.beve", std::string{}); 129 | glz::ex::write_file_json(obj, "examples/float64_array.json", std::string{}); 130 | } 131 | { 132 | uint16_array_t obj{ .values = {0, 1, 2, 1024, 65535} }; 133 | glz::ex::write_file_beve(obj, "examples/uint16_array.beve", std::string{}); 134 | glz::ex::write_file_json(obj, "examples/uint16_array.json", std::string{}); 135 | } 136 | { 137 | strings_array_t obj{ .values = {"cat", "dog", "elephant"} }; 138 | glz::ex::write_file_beve(obj, "examples/strings_array.beve", std::string{}); 139 | glz::ex::write_file_json(obj, "examples/strings_array.json", std::string{}); 140 | } 141 | { 142 | nested_object_t2 obj{ .id = 42, .nested = nested_t{ .name = "sensor-A", .coords = {1.0, 2.5, -3.75} } }; 143 | glz::ex::write_file_beve(obj, "examples/nested_object.beve", std::string{}); 144 | glz::ex::write_file_json(obj, "examples/nested_object.json", std::string{}); 145 | } 146 | } 147 | 148 | int main() { 149 | example_general_object(); 150 | complex_numbers(); 151 | arrays_and_nested(); 152 | 153 | return 0; 154 | } 155 | -------------------------------------------------------------------------------- /docs/implementations.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Implementations - BEVE 8 | 9 | 10 | 11 | 12 | 28 | 40 | 41 |
42 |
43 |

Implementations

44 |

BEVE libraries and tools across different programming languages

45 |
46 |
47 | 48 |
49 |
50 |

Available Implementations

51 | 52 |

C++

53 |

Glaze — Production-ready library with unified JSON/BEVE API. SIMD-optimized, header-only, C++20. GitHub →

54 | 55 |

Rust

56 |

beve — Official implementation with zero-copy deserialization and Serde support. crates.io →

57 |

serde-beve — Serde integration for seamless use with Serde-compatible types. crates.io →

58 | 59 |

Python

60 |

load_beve.py — Pure Python reference implementation, no dependencies. GitHub →

61 | 62 |

MATLAB

63 |

load_beve.m / write_beve.m — Native MATLAB functions for reading and writing BEVE files. GitHub →

64 | 65 |

JavaScript / Go

66 |

Implementations in development. Check the repository →

67 |
68 | 69 |
70 |

Resources

71 |

Examples: Working code examples and sample data files in the repository.

72 |

Contributing: Follow the specification, include tests, and submit to GitHub discussions.

73 |
74 | 75 |
76 |

Performance Considerations

77 |
78 |
79 |
80 | 81 |
82 |

Use SIMD Where Possible

83 |

BEVE is designed for SIMD operations. Take advantage of vector instructions for array processing.

84 |
85 |
86 |
87 | 88 |
89 |

Zero-Copy Parsing

90 |

Implement zero-copy techniques when deserializing typed arrays for maximum performance.

91 |
92 |
93 |
94 | 95 |
96 |

Little Endian

97 |

Leverage native byte order on x86/ARM for fast integer operations.

98 |
99 |
100 |
101 | 102 |
103 |

Compression Integration

104 |

Consider integrating with LZ4, Zstandard, or Brotli for additional space savings.

105 |
106 |
107 |
108 |
109 | 110 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /python/load_beve.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tkinter as tk 3 | from tkinter import filedialog 4 | 5 | def load_beve(filename): 6 | if filename == None: 7 | filename = filedialog.askopenfilename() 8 | 9 | def read_value(fid): 10 | header = np.fromfile(fid, dtype=np.uint8, count=1) 11 | type_val = np.bitwise_and(header, 0b00000111)[0] 12 | 13 | if type_val == 0: # null or boolean 14 | is_bool = np.bitwise_and(header, 0b00001000) >> 3 15 | if is_bool: 16 | data = bool(np.bitwise_and(header, 0b00010000) >> 5) 17 | else: 18 | data = None 19 | elif type_val == 1: # number 20 | config = np.uint8([1, 2, 4, 8, 16, 32, 64, 128]) 21 | byte_count_index = np.bitwise_and(header, 0b11100000) >> 5 22 | byte_count = config[byte_count_index] 23 | 24 | num_type = np.bitwise_and(header, 0b00011000) >> 3 25 | is_float = num_type == 0 26 | is_signed = num_type == 1 27 | 28 | if is_float: 29 | if byte_count == 4: 30 | data = np.fromfile(fid, dtype=np.float32, count=1, sep="")[0] 31 | elif byte_count == 8: 32 | data = np.fromfile(fid, dtype=np.float64, count=1, sep="")[0] 33 | else: 34 | raise NotImplementedError("float byte count not implemented") 35 | else: 36 | if is_signed: 37 | if byte_count == 1: 38 | data = np.fromfile(fid, dtype=np.int8, count=1, sep="")[0] 39 | elif byte_count == 2: 40 | data = np.fromfile(fid, dtype=np.int16, count=1, sep="")[0] 41 | elif byte_count == 4: 42 | data = np.fromfile(fid, dtype=np.int32, count=1, sep="")[0] 43 | elif byte_count == 8: 44 | data = np.fromfile(fid, dtype=np.int64, count=1, sep="")[0] 45 | else: 46 | raise NotImplementedError("float byte count not implemented") 47 | else: 48 | if byte_count == 1: 49 | data = np.fromfile(fid, dtype=np.uint8, count=1, sep="")[0] 50 | elif byte_count == 2: 51 | data = np.fromfile(fid, dtype=np.uint16, count=1, sep="")[0] 52 | elif byte_count == 4: 53 | data = np.fromfile(fid, dtype=np.uint32, count=1, sep="")[0] 54 | elif byte_count == 8: 55 | data = np.fromfile(fid, dtype=np.uint64, count=1, sep="")[0] 56 | else: 57 | raise NotImplementedError("float byte count not implemented") 58 | elif type_val == 2: # string 59 | string_size = read_compressed(fid) 60 | data = fid.read(string_size).decode("utf-8") 61 | elif type_val == 3: # object 62 | key_type = np.bitwise_and(header, 0b00011000) >> 3 63 | is_string = key_type == 0 64 | is_signed = key_type == 1 65 | 66 | if is_string: 67 | size = read_compressed(fid) 68 | 69 | data = {} 70 | for _ in range(size): 71 | string_size = read_compressed(fid) 72 | string = fid.read(string_size).decode("utf-8") 73 | data[string] = read_value(fid) 74 | else: 75 | byte_count_index = np.bitwise_and(header, 0b11100000) >> 5 76 | byte_count = config[byte_count_index] 77 | 78 | raise NotImplementedError("Integer key support not implemented") 79 | 80 | elif type_val == 4: # typed array 81 | num_type = np.bitwise_and(header, 0b00011000) >> 3 82 | is_float = num_type == 0 83 | is_signed = num_type == 1 84 | 85 | if num_type == 3: # boolean or string 86 | is_string = np.bitwise_and(header, 0b00100000) >> 5 87 | if is_string: 88 | array_size = read_compressed(fid) 89 | data = [] 90 | for _ in range(array_size): 91 | string_size = read_compressed(fid) 92 | data.append(fid.read(string_size).decode("utf-8")) 93 | 94 | else: 95 | raise NotImplementedError("Boolean array support not implemented") 96 | else: 97 | config = np.uint8([1, 2, 4, 8, 16, 32, 64, 128]) 98 | byte_count_index = np.bitwise_and(header, 0b11100000) >> 5 99 | byte_count = config[byte_count_index] 100 | 101 | size = read_compressed(fid) 102 | 103 | if is_float: 104 | data = np.fromfile(fid, dtype=np.float32 if byte_count == 4 else np.float64, count=size, sep="") 105 | else: 106 | if is_signed: 107 | data = np.fromfile(fid, dtype=np.int8 if byte_count == 1 else np.int16 if byte_count == 2 else np.int32 if byte_count == 4 else np.int64, count=size, sep="") 108 | else: 109 | data = np.fromfile(fid, dtype=np.uint8 if byte_count == 1 else np.uint16 if byte_count == 2 else np.uint32 if byte_count == 4 else np.uint64, count=size, sep="") 110 | elif type_val == 5: # untyped array 111 | array_size = read_compressed(fid) 112 | data = [] 113 | for _ in range(array_size): 114 | data.append(read_value(fid)) 115 | elif type_val == 6: # extensions 116 | extension = np.bitwise_and(header, 0b11111000) >> 3 117 | if extension == 1: # variants 118 | read_compressed(fid) 119 | data = read_value(fid) 120 | elif extension == 2: # matrices 121 | layout = np.bitwise_and(np.fromfile(fid, dtype=np.uint8, count=1, sep=""), 0b00000001)[0] 122 | if layout == 0: # row major 123 | raise NotImplementedError("Row major support not implemented") 124 | elif layout == 1: # column major 125 | extents = read_value(fid) 126 | matrix_data = read_value(fid) 127 | data = np.reshape(matrix_data, (extents[0], extents[1]), order ='F') 128 | else: 129 | raise ValueError("Unsupported layout") 130 | elif extension == 3: # complex numbers 131 | data = read_complex(fid) 132 | else: 133 | raise ValueError("Unsupported extension") 134 | else: 135 | raise ValueError("Unsupported type") 136 | 137 | return data 138 | 139 | def read_complex(fid): 140 | complex_header = np.fromfile(fid, dtype=np.uint8, count=1)[0] 141 | type_val = np.bitwise_and(complex_header, 0b00000111) 142 | 143 | num_type = np.bitwise_and(complex_header, 0b00011000) >> 3 144 | is_float = num_type == 0 145 | is_signed = num_type == 1 146 | 147 | byte_count_index = np.bitwise_and(complex_header, 0b11100000) >> 5 148 | byte_count = config[byte_count_index] 149 | 150 | if type_val == 0: # complex number 151 | complex_data = None 152 | if is_float: 153 | complex_data = np.fromfile(fid, dtype=np.float32 if byte_count == 4 else np.float64, count=2, sep="") 154 | else: 155 | if is_signed: 156 | complex_data = np.fromfile(fid, dtype=np.int8 if byte_count == 1 else np.int16 if byte_count == 2 else np.int32 if byte_count == 4 else np.int64, count=2, sep="") 157 | else: 158 | complex_data = np.fromfile(fid, dtype=np.uint8 if byte_count == 1 else np.uint16 if byte_count == 2 else np.uint32 if byte_count == 4 else np.uint64, count=2, sep="") 159 | data = complex(complex_data[0], complex_data[1]) 160 | elif type_val == 1: # complex array 161 | size = read_compressed(fid) 162 | 163 | if is_float: 164 | data = np.fromfile(fid, dtype=np.complex64 if byte_count == 4 else np.complex128, count=size, sep="") 165 | else: 166 | raise ValueError("Unsupported complex integer type") 167 | else: 168 | raise ValueError("Unsupported complex type") 169 | 170 | return data 171 | 172 | def read_compressed(fid): 173 | config = np.uint8([1, 2, 4, 8, 16, 32, 64, 128]) 174 | 175 | compressed = np.fromfile(fid, dtype=np.uint8, count=1)[0] 176 | n_size_bytes = config[np.bitwise_and(compressed, 0b00000011)] 177 | fid.seek(-1, 1) # revert one byte (offset, whence), whence of 1 is relative to the current position 178 | if n_size_bytes == 1: 179 | size = np.fromfile(fid, dtype=np.uint8, count=1)[0] 180 | elif n_size_bytes == 2: 181 | size = np.fromfile(fid, dtype=np.uint16, count=1)[0] 182 | elif n_size_bytes == 4: 183 | size = np.fromfile(fid, dtype=np.uint32, count=1)[0] 184 | elif n_size_bytes == 8: 185 | size = np.fromfile(fid, dtype=np.uint64, count=1)[0] 186 | else: 187 | raise ValueError("Unsupported size") 188 | size = np.right_shift(size, 2) 189 | return size 190 | 191 | config = np.uint8([1, 2, 4, 8, 16, 32, 64, 128]) 192 | 193 | with open(filename, 'rb') as fid: 194 | data = read_value(fid) 195 | 196 | return data 197 | 198 | # Load .beve file 199 | data = load_beve(None) 200 | print(data) -------------------------------------------------------------------------------- /docs/proposals.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Proposals - BEVE 8 | 9 | 10 | 11 | 12 | 13 | 14 | 30 | 42 | 43 |
44 |
45 |

Proposals

46 |

Extensions and enhancements in development for BEVE

47 |
48 |
49 | 50 |
51 |
52 |

About BEVE Extensions

53 |

BEVE supports extensions to handle specialized use cases while maintaining compatibility with the core specification. These proposals are working drafts that may be incorporated into future versions of BEVE.

54 |
55 | 56 |
57 |

Extension 4 — Time (Timestamps)

58 |

Working draft for BEVE v1.0

59 | 60 |
61 |

Status: Working Draft

62 |

Extension ID: 4

63 |
64 | 65 |

Overview

66 |

A compact, SIMD-friendly encoding for instants in time that:

67 |
    68 |
  • Round-trips cleanly to RFC 3339 / ISO-8601 (and IXDTF/RFC 9557) on the text side
  • 69 |
  • Avoids floating point (no drift)
  • 70 |
  • Scales to attoseconds without requiring 128-bit math for "now"
  • 71 |
  • Supports high-throughput typed arrays
  • 72 |
73 | 74 |

Goals

75 |
    76 |
  • Exact representation of instants using integer ticks
  • 77 |
  • Two-integer split for precision and range without bigints on the hot path
  • 78 |
  • Single-value and array encodings with one set of metadata per array (epoch, unit, optional TZ)
  • 79 |
  • Straightforward JSON projections: human (strings) and lossless (object)
  • 80 |
81 | 82 |

Placement in BEVE Header Space

83 |

This is a BEVE Extension (HEADER top 3 bits = 6).

84 |

Within the extension space, id = 4 denotes Time (timestamps).

85 |
86 |

Existing ids: 0=data delimiter, 1=type tag, 2=matrices, 3=complex numbers.
87 | New: 4=time

88 |
89 |

All multi-byte integers in this extension are little endian.

90 | 91 |

TIME HEADER (1 byte)

92 |
bits 0..1  SHAPE
 93 |            0 = single timestamp
 94 |            1 = timestamp array (exactly length 2)
 95 |            2..3 = reserved
 96 | 
 97 | bits 2..4  UNIT (decimal powers around seconds)
 98 |            0 = ksec (10^3 s)
 99 |            1 = s
100 |            2 = ms   (10^-3 s)
101 |            3 = µs   (10^-6 s)
102 |            4 = ns   (10^-9 s)
103 |            5 = ps   (10^-12 s)
104 |            6 = fs   (10^-15 s)
105 |            7 = as   (10^-18 s)
106 | 
107 | bit 5      TZ_PRESENT
108 |            0 = no TZ field follows
109 |            1 = int16_t minutes east of UTC follows (fixed 2 bytes)
110 | 
111 | bits 6..7  EPOCH/SCALE
112 |            0 = UNIX/POSIX (no leap seconds)
113 |            1 = UTC scale  (permits leap seconds)
114 |            2 = TAI
115 |            3 = GPS
116 | 117 |

Timezone field (optional; fixed width)

118 |

If TZ_PRESENT=1, immediately write a 2-byte int16_t: minutes east of UTC.

119 |
    120 |
  • Valid range to emit: −1439..+1439 (encoders MUST enforce)
  • 121 |
  • Semantics: presentation metadata only; does not affect the instant encoded by the numeric fields
  • 122 |
123 | 124 |

Payload Layouts

125 | 126 |

Single timestamp

127 |
EXT(4) | TIME_HEADER | [TZ:int16]? |
128 | NUMBER (signed)   SECONDS_OFFSET |
129 | NUMBER (unsigned) PRECISION
130 |
    131 |
  • NUMBER is a BEVE integer value (the usual number header chooses width: 1/2/4/8/16/32 bytes, etc.)
  • 132 |
133 | 134 |

Timestamp array

135 |
EXT(4) | TIME_HEADER | [TZ:int16]? |
136 | ARRAY(len=2) |
137 | TYPED_ARRAY (signed int)   SECONDS_OFFSET[] |
138 | TYPED_ARRAY (unsigned int) PRECISION[]
139 |
    140 |
  • The BEVE array header MUST report length = 2 (seconds array first, precision array second)
  • 141 |
  • Both typed arrays MUST have the same SIZE and are written back-to-back
  • 142 |
  • Each typed array uses standard BEVE typed-array headers
  • 143 |
144 | 145 |

JSON/Text Projections

146 | 147 |

Human-oriented (default): RFC 3339 strings

148 |
    149 |
  • Single: "2025-10-16T12:34:56.123456789Z"
  • 150 |
  • Array: ["2025-10-16T12:34:56Z", "..."]
  • 151 |
  • Fractional digits match the UNIT (e.g., ns → up to 9 digits)
  • 152 |
  • If TZ_PRESENT=1, use that offset for printing (e.g., -05:00), while the instant remains absolute
  • 153 |
154 | 155 |

Lossless structured object

156 |

Single:

157 |
{
158 |   "epoch": "unix",                 // "unix" | "utc" | "tai" | "gps"
159 |   "unit": "ps",                    // "ksec","s","ms","us","ns","ps","fs","as"
160 |   "seconds": 1750000000,           // signed integer
161 |   "precision": 123456789012,       // unsigned integer
162 |   "offset_minutes": -300           // optional; omitted if TZ not present
163 | }
164 | 165 |

Array:

166 |
{
167 |   "epoch": "unix",
168 |   "unit": "ns",
169 |   "offset_minutes": 0,
170 |   "seconds":   [1697463296, 1697466896],
171 |   "precision": [        42,  987654321]
172 | }
173 | 174 |

Examples

175 | 176 |

Single, UNIX epoch, nanoseconds, UTC

177 |

Meaning: 2025-10-16T12:34:56.000000789Z

178 |
EXT(4) | TH(shape=single, unit=ns, tz=0, epoch=unix) |
179 | NUMBER(int64) seconds=1697463296 |
180 | NUMBER(uint32) precision=789
181 | 182 |

Array, UNIX epoch, microseconds, TZ = −05:00

183 |

Meaning: local offset preserved for printing; instants absolute.

184 |
EXT(4) | TH(shape=array, unit=us, tz=1, epoch=unix) | int16(-300) |
185 | TYPED_ARRAY(int64)   seconds[ N ] |
186 | TYPED_ARRAY(uint32)  precision[ N ]
187 | 188 |

ksec example

189 |
    190 |
  • unit = ksec
  • 191 |
  • seconds = 2 (→ 2000 s), precision = 17 → instant = epoch + 2017 s
  • 192 |
193 | 194 |

Key Features

195 |
196 |
197 |
198 | 199 |
200 |

Integer-Only Math

201 |

No floating point drift, exact representations

202 |
203 |
204 |
205 | 206 |
207 |

Attosecond Precision

208 |

Scales to 10^-18 seconds without bigints for current dates

209 |
210 |
211 |
212 | 213 |
214 |

SIMD-Friendly Arrays

215 |

Metadata shared across arrays for high throughput

216 |
217 |
218 |
219 | 220 |
221 |

Multiple Epochs

222 |

Support for UNIX, UTC, TAI, and GPS time scales

223 |
224 |
225 |
226 | 227 |
228 |

Timezone Preservation

229 |

Optional timezone field for presentation without affecting instant

230 |
231 |
232 |
233 | 234 |
235 |

RFC 3339 Compatible

236 |

Clean round-trip to ISO-8601 and IXDTF formats

237 |
238 |
239 | 240 |
241 |

Full Specification: For the complete technical specification, validation rules, and reference algorithms, see the full proposal on GitHub.

242 |
243 |
244 | 245 |
246 |

Contributing to Proposals

247 |

We welcome feedback and contributions to BEVE proposals. To participate:

248 | 253 |
254 |
255 | 256 | 265 | 266 | 267 | 268 | 269 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BEVE - Binary Efficient Versatile Encoding 2 | Version 1.0 3 | 4 | *High performance, tagged binary data specification like JSON, MessagePack, CBOR, etc. But, designed for higher performance and scientific computing.* 5 | 6 | > See [Discussions](https://github.com/stephenberry/eve/discussions) for polls and active development on the specification. 7 | 8 | - Maps to and from JSON 9 | - Schema less, fully described, like JSON (can be used in documents) 10 | - Little endian for maximum performance on modern CPUs 11 | - Blazingly fast, designed for SIMD 12 | - Future proof, supports large numerical types (such as 128 bit integers and higher) 13 | - Designed for scientific computing, supports [brain floats](https://en.wikipedia.org/wiki/Bfloat16_floating-point_format), matrices, and complex numbers 14 | - Simple, designed to be easy to integrate 15 | 16 | > BEVE is designed to be faster on modern hardware than CBOR, BSON, and MessagePack, but it is also more space efficient for typed arrays. 17 | 18 | ## Performance 19 | 20 | BEVE with [Glaze](https://github.com/stephenberry/glaze) versus [JSON](https://www.json.org/), [MessagePack](https://github.com/msgpack/msgpack-c), and [CBOR](https://cbor.io/) (via [Glaze](https://github.com/stephenberry/glaze)). 21 | 22 | ### Speedup vs BEVE (Baseline) 23 | 24 | Higher means BEVE is faster by that factor. Format: Write/Read 25 | 26 | | Test | JSON | MsgPack | CBOR | 27 | |------|------|---------|------| 28 | | Complex Nested Object | 2.6x/2.2x | 2.2x/10.1x | 1.0x/1.1x | 29 | | std::vector\ (10K) | 150.8x/147.6x | 17.5x/37.8x | 1.0x/1.0x | 30 | | std::vector\ (10K) | 221.8x/231.6x | 33.1x/73.8x | 1.0x/1.0x | 31 | | std::vector\ (10K) | 45.6x/85.5x | 17.9x/36.5x | 1.0x/1.0x | 32 | | std::vector\ (10K) | 53.8x/88.8x | 34.0x/72.1x | 1.0x/1.0x | 33 | | std::vector\ (10K) | 91.7x/130.8x | 67.6x/164.3x | 1.0x/0.9x | 34 | 35 | > CBOR benchmarks use [RFC 8746](https://datatracker.ietf.org/doc/rfc8746/) typed arrays via Glaze. 36 | 37 | [Performance test code](https://github.com/stephenberry/binary_performance) 38 | 39 | ### Message Sizes 40 | 41 | | Test | JSON | BEVE | MessagePack | CBOR | 42 | |------|------|------|-------------|------| 43 | | Complex Nested Object | 616 B | 564 B | 545 B | 560 B | 44 | | std::vector\ (10K) | 219.02 KB | 78.13 KB | 87.89 KB | 78.13 KB | 45 | | std::vector\ (10K) | 124.11 KB | 39.07 KB | 48.83 KB | 39.07 KB | 46 | | std::vector\ (10K) | 199.23 KB | 78.13 KB | 87.89 KB | 78.13 KB | 47 | | std::vector\ (10K) | 104.97 KB | 39.07 KB | 48.83 KB | 39.07 KB | 48 | | std::vector\ (10K) | 56.96 KB | 19.53 KB | 29.26 KB | 19.54 KB | 49 | 50 | BEVE and CBOR (with RFC 8746 typed arrays) store contiguous arrays as raw memory blocks, achieving the same message sizes and throughput when using optimized implementations like Glaze. MessagePack encodes each element individually with type tags, resulting in larger messages and slower performance for numeric arrays. 51 | 52 | ## Why Tagged Messages? 53 | 54 | *Flexibility and efficiency* 55 | 56 | JSON is ubiquitous because it is tagged (has keys), and therefore messages can be sent in part. Furthermore, extending specifications and adding more fields is far easier with tagged messages and unordered mapping. Tags also make the format more human friendly. However, tags are entirely optional, and structs can be serialized as generic arrays. 57 | 58 | ## Endianness 59 | 60 | The endianness must be `little endian`. 61 | 62 | ## File Extension 63 | 64 | The standard extension for BEVE files is `.beve` 65 | 66 | ## MIME Type 67 | 68 | The official media type for BEVE payloads is `application/beve`. Producers SHOULD advertise `Content-Type: application/beve` when emitting BEVE binaries, and consumers SHOULD express support via `Accept: application/beve`. This mirrors the role of `application/json` for JSON, with the distinction that the payload is encoded using the BEVE specification. 69 | 70 | ## Implementations 71 | 72 | ### C++ 73 | 74 | - [Glaze](https://github.com/stephenberry/glaze) (supports JSON and BEVE through the same API) 75 | 76 | ### Matlab 77 | 78 | - [load_beve.m](https://github.com/stephenberry/eve/blob/main/matlab/load_beve.m) (this repository) 79 | - [write_beve.m](https://github.com/stephenberry/eve/blob/main/matlab/write_beve.m) (this repository) 80 | 81 | ### Python 82 | 83 | - [load_beve.py](https://github.com/stephenberry/eve/blob/main/python/load_beve.py) (this repository) 84 | 85 | ### Rust 86 | 87 | - [beve crate](https://crates.io/crates/beve) (developed by author) 88 | 89 | - [serde-beve crate](https://crates.io/crates/serde-beve) 90 | 91 | ## Right Most Bit Ordering 92 | 93 | The right most bit is denoted as the first bit, or bit of index 0. 94 | 95 | ## Concerning Compression 96 | 97 | Note that BEVE is not a compression algorithm. It uses some bit packing to be more space efficient, but strings and numerical values see no compression. This means that BEVE binary is very compressible, like JSON, and it is encouraged to use compression algorithms like [LZ4](https://lz4.org), [Zstandard](https://github.com/facebook/zstd), [Brotli](https://github.com/google/brotli), etc. where size is critical. 98 | 99 | ## Compressed Unsigned Integer 100 | 101 | A compressed unsigned integer uses the first two bits to denote the number of bytes used to express an integer. The rest of the bits indicate the integer value. 102 | 103 | > Wherever all caps `SIZE` is used in the specification, it refers to a size indicator that uses a compressed unsigned integer. 104 | > 105 | > `SIZE` refers to the count of array members, object members, or bytes in a string. It does **not** refer to the number of raw bytes except for UTF-8 strings. 106 | 107 | | # | Number of Bytes | Integer Value (N) | 108 | | ---- | --------------- | -------------------------------- | 109 | | 0 | 1 | N < 64 `[2^6]` | 110 | | 1 | 2 | N < 16384 `[2^14]` | 111 | | 2 | 4 | N < 1073741824 `[2^30]` | 112 | | 3 | 8 | N < 4611686018427387904 `[2^62]` | 113 | 114 | ## Byte Count Indicator 115 | 116 | > Wherever all caps `BYTE COUNT` is used, it describes this mapping. 117 | 118 | ```c++ 119 | # Number of bytes 120 | 0 1 121 | 1 2 122 | 2 4 123 | 3 8 124 | 4 16 125 | 5 32 126 | 6 64 127 | 7 128 128 | ... 129 | ``` 130 | 131 | ## Header 132 | 133 | Every `VALUE` begins with a byte header. Any unspecified bits must be set to zero. 134 | 135 | > Wherever all caps `HEADER` is used, it describes this header. 136 | 137 | The first three bits denote types: 138 | 139 | ```c++ 140 | 0 -> null or boolean 0b00000'000 141 | 1 -> number 0b00000'001 142 | 2 -> string 0b00000'010 143 | 3 -> object 0b00000'011 144 | 4 -> typed array 0b00000'100 145 | 5 -> generic array 0b00000'101 146 | 6 -> extension 0b00000'110 147 | 7 -> reserved 0b00000'111 148 | ``` 149 | 150 | ## Nomenclature 151 | 152 | Wherever `DATA` is used, it denotes bytes of data without a `HEADER`. 153 | 154 | Wherever `VALUE` is used, it denotes a binary structure that begins with a `HEADER`. 155 | 156 | Wherever `SIZE` is used, it refers to a compressed unsigned integer that denotes a count of array members, object members, or bytes in a string. 157 | 158 | ## 0 - Null 159 | 160 | Null is simply `0` 161 | 162 | ## 0 - Boolean 163 | 164 | The next bit is set to indicate a boolean. The 5th bit is set to denote true or false. 165 | 166 | ```c++ 167 | false 0b000'01'000 168 | true 0b000'11'000 169 | ``` 170 | 171 | ## 1 - Number 172 | 173 | The next two bits of the HEADER indicates whether the number is floating point, signed integer, or unsigned integer. 174 | 175 | Float point types must conform to the IEEE-754 standard. 176 | 177 | ```c++ 178 | 0 -> floating point 0b000'00'001 179 | 1 -> signed integer 0b000'01'001 180 | 2 -> unsigned integer 0b000'10'001 181 | ``` 182 | 183 | The next three bits of the HEADER are used as the BYTE COUNT. 184 | 185 | > Note: brain floats use a byte count indicator of 1, even though they use 2 bytes per value. This is used because float8_t is not supported and not typically useful. 186 | 187 | > See [Fixed width integer types](https://en.cppreference.com/w/cpp/types/integer) for integer specification. 188 | 189 | ```c++ 190 | bfloat16_t 0b000'00'001 // brain float 191 | float16_t 0b001'00'001 192 | float32_t 0b010'00'001 // float 193 | float64_t 0b011'00'001 // double 194 | float128_t 0b100'00'001 195 | ``` 196 | 197 | ```c++ 198 | int8_t 0b000'01'001 199 | int16_t 0b001'01'001 200 | int32_t 0b010'01'001 201 | int64_t 0b011'01'001 202 | int128_t 0b100'01'001 203 | ``` 204 | 205 | ```c++ 206 | uint8_t 0b000'10'001 207 | uint16_t 0b001'10'001 208 | uint32_t 0b010'10'001 209 | uint64_t 0b011'10'001 210 | uint128_t 0b100'10'001 211 | ``` 212 | 213 | ## 2 - Strings 214 | 215 | Strings must be encoded with UTF-8. 216 | 217 | Layout: `HEADER | SIZE | DATA` 218 | 219 | ### Strings as Object Keys or Typed String Arrays 220 | 221 | When strings are used as keys in objects or typed string arrays the HEADER is not included. 222 | 223 | Layout: `SIZE | DATA` 224 | 225 | ## 3 - Object 226 | 227 | The next two bits of the HEADER indicates the type of key. 228 | 229 | ```c++ 230 | 0 -> string 231 | 1 -> signed integer 232 | 2 -> unsigned integer 233 | ``` 234 | 235 | For integer keys the next three bits of the HEADER indicate the BYTE COUNT. 236 | 237 | > An object `KEY` must not contain a HEADER as the type of the key has already been defined. 238 | 239 | Layout: `HEADER | SIZE | KEY[0] | VALUE[0] | ... KEY[N] | VALUE[N]` 240 | 241 | ## 4 - Typed Array 242 | 243 | The next two bits indicate the type stored in the array: 244 | 245 | ```c++ 246 | 0 -> floating point 247 | 1 -> signed integer 248 | 2 -> unsigned integer 249 | 3 -> boolean or string 250 | ``` 251 | 252 | For integral and floating point types, the next three bits of the type header are the BYTE COUNT. 253 | 254 | For boolean or string types the next bit indicates whether the type is a boolean or a string 255 | 256 | ```c++ 257 | 0 -> boolean // packed as single bits to the nearest byte 258 | 1 -> string // an array of strings (not an array of characters) 259 | ``` 260 | 261 | Layout: `HEADER | SIZE | data` 262 | 263 | ### Boolean Arrays 264 | 265 | Boolean arrays are stored as single bits and packed into consecutive bytes. 266 | 267 | - `SIZE` is the number of booleans; the payload length is `ceil(SIZE / 8)` bytes. 268 | - Bits are packed per byte in LSB-first order. Within each byte, bit 0 (the least-significant bit) corresponds to the lowest array index for that byte; bit `i` corresponds to array index `byte_index * 8 + i`. 269 | - Bytes are written in order: the first byte packs indices 0–7, the second 8–15, and so on. 270 | - A bit value of 1 encodes `true`; 0 encodes `false`. 271 | - If `SIZE` is not a multiple of 8, the remaining high bits of the final byte are padding and must be zero. 272 | 273 | Examples 274 | 275 | ```text 276 | 0b00000001 -> index 0 is true (indices 1–7 are false) 277 | 0b00000010 -> index 1 is true 278 | [true, false, true] -> 0b00000101 279 | ``` 280 | 281 | ### String Arrays 282 | 283 | String arrays do not include the string HEADER for each element. 284 | 285 | Layout: `HEADER | SIZE | string[0] | ... string[N]` 286 | 287 | ## 5 - Generic Array 288 | 289 | Generic arrays expect elements to have headers. 290 | 291 | Layout: `HEADER | SIZE | VALUE[0] | ... VALUE[N]` 292 | 293 | ## 6 - Extensions 294 | 295 | Extensions are considered to be a formal part of the BEVE specification, but are not expected to be as broadly implemented. 296 | 297 | Following the first three HEADER bits, the next five bits denote various extensions. These extensions are not expected to be implemented in every parser/serializer, but they provide convenient binary storage for more specialized use cases, such as variants, matrices, and complex numbers. 298 | 299 | ```c++ 300 | 0 -> data delimiter // for specs like Newline Delimited JSON 301 | 1 -> type tag // for variant like structures 302 | 2 -> matrices 303 | 3 -> complex numbers 304 | ``` 305 | 306 | ### 0 - Data Delimiter 307 | 308 | Used to separate chunks of data to match specifications like [NDJSON](http://ndjson.org). 309 | 310 | When converted to JSON this should add a new line (`'\n'`) character to the JSON. 311 | 312 | ### 1 - Type Tag (Variants) 313 | 314 | Expects a subsequent compressed unsigned integer to denote a type tag. A compressed SIZE indicator is used to efficiently store the tag. 315 | 316 | Layout : `HEADER | SIZE (i.e. type tag) | VALUE` 317 | 318 | The converted JSON format should look like: 319 | 320 | ```json 321 | { 322 | "index": 0, 323 | "value": "the JSON value" 324 | } 325 | ``` 326 | 327 | The `"index"` should refer to an array of types, from zero to one less than the count of types. 328 | 329 | The `"value"` is any JSON value. 330 | 331 | ### 2 - Matrices 332 | 333 | Matrices can be stored as object or array types. However, this tag provides a more compact mechanism to introspect matrices. 334 | 335 | Matrices add a one byte MATRIX HEADER. 336 | 337 | The first bit of the matrix header denotes the data layout of the matrix. 338 | 339 | ```c++ 340 | 0 -> layout_right // row-major 341 | 1 -> layout_left // column-major 342 | ``` 343 | 344 | Layout: `HEADER | MATRIX HEADER | EXTENTS | VALUE` 345 | 346 | EXTENTS are written out as a typed array of integers. 347 | 348 | > The VALUE in the matrix must be a typed array of numerical data. 349 | 350 | The converted JSON format should look like: 351 | 352 | ```json 353 | { 354 | "layout": "layout_right", 355 | "extents": [3, 3], 356 | "value": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 357 | } 358 | ``` 359 | 360 | ### 3 - Complex Numbers 361 | 362 | An additional COMPLEX HEADER byte is used. 363 | 364 | - Complex numbers are stored as pairs of numerical types. 365 | 366 | The first three bits denote whether this is a single complex number or a complex array. 367 | 368 | ```c++ 369 | 0 -> complex number 370 | 1 -> complex array 371 | ``` 372 | 373 | For a single complex number the layout is: `HEADER | COMPLEX HEADER | DATA` 374 | 375 | > A complex value is a pair of numbers. 376 | 377 | For a complex array the layout is: `HEADER | COMPLEX HEADER | SIZE | DATA` 378 | 379 | > Three bits are used to align the left bits with the layouts for numbers. 380 | 381 | The next two bits denote the numerical type: 382 | 383 | ```c++ 384 | 0 -> floating point 0b000'00'000 385 | 1 -> signed integer 0b000'01'000 386 | 2 -> unsigned integer 0b000'10'000 387 | ``` 388 | 389 | The next three bits are used to indicate the BYTE COUNT. This is the same specification for BEVE numbers. 390 | 391 | The converted JSON format should look like: 392 | 393 | ```json 394 | [1, 2] // for a complex number 395 | [[1, 2], [2.0, 3]] // for a complex array 396 | ``` 397 | -------------------------------------------------------------------------------- /matlab/write_beve.m: -------------------------------------------------------------------------------- 1 | % Write a .beve file 2 | % Reference: https://github.com/stephenberry/beve 3 | function write_beve(data, filename) 4 | % Open dialog box if filename isn't provided. 5 | if ( ~exist('filename','var') || isempty(filename) ) 6 | [file, path] = uiputfile('*.beve', 'Save BEVE file as'); 7 | if isequal(file, 0) || isequal(path, 0) 8 | error('File selection cancelled'); 9 | end 10 | filename = fullfile(path, file); 11 | fprintf("BEVE File Selected for Writing:\n'%s'\n", filename); 12 | end 13 | 14 | fid = fopen(filename, 'wb'); 15 | if fid == -1 16 | error('Failed to open file for writing'); 17 | end 18 | 19 | write_value(fid, data); 20 | 21 | fclose(fid); 22 | end 23 | 24 | function write_value(fid, value) 25 | if isnumeric(value) && isscalar(value) && isnan(value) 26 | % Handle null value (NaN in MATLAB) 27 | header = uint8(0); % Type 0 = null 28 | write_byte(fid, header); 29 | elseif ischar(value) 30 | % Handle string values 31 | header = uint8(2); % Type 2 = string 32 | write_byte(fid, header); 33 | 34 | % Write string length 35 | write_compressed(fid, length(value)); 36 | 37 | % Write string content 38 | fwrite(fid, value, 'char', 'l'); 39 | elseif iscell(value) 40 | header = uint8(5); 41 | write_byte(fid, header); 42 | write_compressed(fid, numel(value)); 43 | for ii = 1:numel(value) 44 | write_value(fid, value{ii}); 45 | end 46 | elseif isnumeric(value) && ~isreal(value) 47 | % Handle complex numbers 48 | header = uint8(6); % Type 6 = extensions 49 | header = bitor(header, bitshift(3, 3)); % Extension 3 = complex numbers 50 | write_byte(fid, header); 51 | write_complex(fid, value); 52 | elseif isstring(value) && length(value) > 1 53 | % Handle string arrays 54 | header = uint8(4); % Type 4 = typed array 55 | header = bitor(header, bitshift(3, 3)); % element_type = 3 (bool_or_string) 56 | header = bitor(header, bitshift(1, 5)); % string_flag = 1 57 | write_byte(fid, header); 58 | 59 | % Write array size 60 | write_compressed(fid, numel(value)); 61 | 62 | % Write each string with its length 63 | for i = 1:numel(value) 64 | str = char(value(i)); 65 | write_compressed(fid, length(str)); 66 | fwrite(fid, str, 'char', 'l'); 67 | end 68 | elseif islogical(value) && length(value) > 1 69 | % Handle logical arrays - packed as bits (8 per byte) 70 | header = uint8(4); % Type 4 = typed array 71 | header = bitor(header, bitshift(3, 3)); % element_type = 3 (bool_or_string) 72 | header = bitor(header, bitshift(0, 5)); % string_flag = 0 (boolean) 73 | write_byte(fid, header); 74 | 75 | % Write array size 76 | write_compressed(fid, numel(value)); 77 | 78 | % Pack booleans into bits (8 per byte) 79 | num_values = numel(value); 80 | num_bytes = ceil(num_values / 8); 81 | packed_bytes = zeros(num_bytes, 1, 'uint8'); 82 | 83 | % Pack each boolean into the appropriate bit 84 | for i = 1:num_values 85 | byte_index = floor((i-1) / 8) + 1; 86 | bit_position = mod(i-1, 8); 87 | if value(i) 88 | packed_bytes(byte_index) = bitor(packed_bytes(byte_index), bitshift(uint8(1), bit_position)); 89 | end 90 | end 91 | 92 | % Write the packed bytes 93 | fwrite(fid, packed_bytes, 'uint8', 'l'); 94 | elseif isnumeric(value) && isempty(value) 95 | % Handle empty numeric arrays - this is the fix for empty arrays 96 | header = uint8(4); % Type 4 = typed array 97 | 98 | % Determine element type 99 | if isfloat(value) 100 | header = bitor(header, bitshift(0, 3)); % element_type = 0 (float) 101 | elseif any(strcmp(class(value), {'int8', 'int16', 'int32', 'int64'})) 102 | header = bitor(header, bitshift(1, 3)); % element_type = 1 (signed) 103 | else 104 | header = bitor(header, bitshift(2, 3)); % element_type = 2 (unsigned) 105 | end 106 | 107 | % Determine byte count 108 | if isa(value, 'single') || isa(value, 'int32') || isa(value, 'uint32') 109 | header = bitor(header, bitshift(2, 5)); % byte_count_index = 2 (4 bytes) 110 | elseif isa(value, 'double') || isa(value, 'int64') || isa(value, 'uint64') 111 | header = bitor(header, bitshift(3, 5)); % byte_count_index = 3 (8 bytes) 112 | elseif isa(value, 'int16') || isa(value, 'uint16') 113 | header = bitor(header, bitshift(1, 5)); % byte_count_index = 1 (2 bytes) 114 | else % int8 or uint8 115 | header = bitor(header, bitshift(0, 5)); % byte_count_index = 0 (1 byte) 116 | end 117 | 118 | write_byte(fid, header); 119 | 120 | % Write array size (0 for empty array) 121 | write_compressed(fid, 0); 122 | elseif isvector(value) && length(value) > 1 123 | if iscell(value) && all(cellfun(@ischar, value)) 124 | % Handle cell array of strings as a string array 125 | header = uint8(4); % Type 4 = typed array 126 | header = bitor(header, bitshift(3, 3)); % element_type = 3 (bool_or_string) 127 | header = bitor(header, bitshift(1, 5)); % string_flag = 1 128 | write_byte(fid, header); 129 | 130 | % Write array size 131 | write_compressed(fid, numel(value)); 132 | 133 | % Write each string with its length 134 | for i = 1:numel(value) 135 | str = value{i}; 136 | write_compressed(fid, length(str)); 137 | fwrite(fid, str, 'char', 'l'); 138 | end 139 | else 140 | % Handle numeric vectors 141 | header = uint8(4); 142 | if isfloat(value) 143 | write_float(fid, header, value, 1); 144 | else 145 | write_integer(fid, header, value, 1); 146 | end 147 | end 148 | elseif islogical(value) 149 | header = uint8(0); 150 | if value 151 | header = bitor(header, 0b00011000); 152 | else 153 | header = bitor(header, 0b00001000); 154 | end 155 | write_byte(fid, header); 156 | elseif ismatrix(value) && ~isvector(value) && ~isstruct(value) 157 | % Handle matrices 158 | header = uint8(6); % Type 6 = extensions 159 | header = bitor(header, bitshift(2, 3)); % Extension 2 = matrices 160 | write_byte(fid, header); 161 | 162 | % Write layout (column major = 1) 163 | write_byte(fid, uint8(1)); 164 | 165 | % Write dimensions 166 | write_value(fid, uint32([size(value,1), size(value,2)])); 167 | 168 | % Write matrix data 169 | write_value(fid, value(:)); 170 | elseif isnumeric(value) 171 | header = uint8(1); 172 | if isfloat(value) 173 | write_float(fid, header, value, 0); 174 | else 175 | write_integer(fid, header, value, 0); 176 | end 177 | elseif isstring(value) && length(value) == 1 178 | % Handle single MATLAB string (convert to char) 179 | write_value(fid, char(value)); 180 | elseif isstruct(value) && isfield(value, 'tag') && isfield(value, 'value') && numel(fieldnames(value)) == 2 181 | % Handle variant (Extension 1 - Type Tag) 182 | write_variant(fid, value.tag, value.value); 183 | elseif isstruct(value) 184 | header = uint8(3); 185 | key_type = 0; % Assuming keys are always strings 186 | header = bitor(header, bitshift(key_type, 3)); 187 | write_byte(fid, header); 188 | 189 | write_compressed(fid, numel(fieldnames(value))); 190 | fields = fieldnames(value); 191 | for ii = 1:numel(fields) 192 | field_name = fields{ii}; 193 | write_compressed(fid, length(field_name)); 194 | fwrite(fid, field_name, 'char', 'l'); 195 | write_value(fid, value.(field_name)); 196 | end 197 | else 198 | error('Unsupported data type: %s', class(value)); 199 | end 200 | end 201 | 202 | function write_variant(fid, tag, value) 203 | % Write a variant (Extension 1 - Type Tag) 204 | header = uint8(6); % Type 6 = extensions 205 | header = bitor(header, bitshift(1, 3)); % Extension 1 = type tag (variant) 206 | write_byte(fid, header); 207 | 208 | % Write the type tag as a compressed unsigned integer 209 | write_compressed(fid, tag); 210 | 211 | % Write the value 212 | write_value(fid, value); 213 | end 214 | 215 | function write_complex(fid, value) 216 | is_array = ~isscalar(value); 217 | type = uint8(is_array); 218 | 219 | % Determine numeric type 220 | if isfloat(real(value)) 221 | num_type = 0; % is_float = true 222 | elseif isinteger(real(value)) && any(real(value(:)) < 0) 223 | num_type = 1; % is_signed = true 224 | else 225 | num_type = 2; % is_unsigned = true 226 | end 227 | 228 | % Determine byte count 229 | if isa(real(value), 'single') 230 | byte_count_index = 2; % 4 bytes 231 | elseif isa(real(value), 'double') 232 | byte_count_index = 3; % 8 bytes 233 | elseif isa(real(value), 'int8') || isa(real(value), 'uint8') 234 | byte_count_index = 0; % 1 byte 235 | elseif isa(real(value), 'int16') || isa(real(value), 'uint16') 236 | byte_count_index = 1; % 2 bytes 237 | elseif isa(real(value), 'int32') || isa(real(value), 'uint32') 238 | byte_count_index = 2; % 4 bytes 239 | elseif isa(real(value), 'int64') || isa(real(value), 'uint64') 240 | byte_count_index = 3; % 8 bytes 241 | else 242 | error('Unsupported complex number type: %s', class(real(value))); 243 | end 244 | 245 | % Build the header 246 | complex_header = type; 247 | complex_header = bitor(complex_header, bitshift(num_type, 3)); 248 | complex_header = bitor(complex_header, bitshift(byte_count_index, 5)); 249 | 250 | % Write the header 251 | write_byte(fid, complex_header); 252 | 253 | % If it's an array, write the size 254 | if is_array 255 | write_compressed(fid, numel(value)); 256 | end 257 | 258 | % Write the complex values 259 | % For performance: 260 | % Reshape data into a real array with alternating real/imag parts 261 | value_type = class(real(value)); 262 | 263 | % Create array of correct type for the data 264 | flat_data = zeros(2*numel(value), 1, value_type); 265 | 266 | % Populate the array with alternating real and imaginary parts 267 | flat_data(1:2:end) = real(value(:)); 268 | flat_data(2:2:end) = imag(value(:)); 269 | 270 | % Convert MATLAB type names to fwrite format specifiers where needed 271 | if strcmp(value_type, 'single') 272 | format_specifier = 'float32'; 273 | elseif strcmp(value_type, 'double') 274 | format_specifier = 'float64'; 275 | else 276 | % For integer types, the format specifier is the same as the MATLAB type name 277 | format_specifier = value_type; 278 | end 279 | 280 | fwrite(fid, flat_data, format_specifier, 'l'); 281 | end 282 | 283 | function write_float(fid, header, value, is_array) 284 | if isa(value, 'single') 285 | % Single precision (4 bytes) 286 | header = bitor(header, bitshift(0, 3)); % is_float = true 287 | header = bitor(header, bitshift(2, 5)); % byte_count_index = 2 (4 bytes) 288 | write_byte(fid, header); 289 | if is_array 290 | write_compressed(fid, length(value)); 291 | end 292 | fwrite(fid, value, 'float32', 'l'); 293 | elseif isa(value, 'double') 294 | % Double precision (8 bytes) 295 | header = bitor(header, bitshift(0, 3)); % is_float = true 296 | header = bitor(header, bitshift(3, 5)); % byte_count_index = 3 (8 bytes) 297 | write_byte(fid, header); 298 | if is_array 299 | write_compressed(fid, length(value)); 300 | end 301 | fwrite(fid, value, 'float64', 'l'); 302 | else 303 | error('Unsupported floating-point type: %s', class(value)); 304 | end 305 | end 306 | 307 | function write_integer(fid, header, value, is_array) 308 | if isa(value, 'uint8') 309 | header = bitor(header, bitshift(2, 3)); % is_unsigned = true 310 | header = bitor(header, bitshift(0, 5)); % byte_count_index = 0 (1 byte) 311 | write_byte(fid, header); 312 | if is_array 313 | write_compressed(fid, length(value)); 314 | end 315 | fwrite(fid, value, 'uint8', 'l'); 316 | elseif isa(value, 'uint16') 317 | header = bitor(header, bitshift(2, 3)); % is_unsigned = true 318 | header = bitor(header, bitshift(1, 5)); % byte_count_index = 1 (2 bytes) 319 | write_byte(fid, header); 320 | if is_array 321 | write_compressed(fid, length(value)); 322 | end 323 | fwrite(fid, value, 'uint16', 'l'); 324 | elseif isa(value, 'uint32') 325 | header = bitor(header, bitshift(2, 3)); % is_unsigned = true 326 | header = bitor(header, bitshift(2, 5)); % byte_count_index = 2 (4 bytes) 327 | write_byte(fid, header); 328 | if is_array 329 | write_compressed(fid, length(value)); 330 | end 331 | fwrite(fid, value, 'uint32', 'l'); 332 | elseif isa(value, 'uint64') 333 | header = bitor(header, bitshift(2, 3)); % is_unsigned = true 334 | header = bitor(header, bitshift(3, 5)); % byte_count_index = 3 (8 bytes) 335 | write_byte(fid, header); 336 | if is_array 337 | write_compressed(fid, length(value)); 338 | end 339 | fwrite(fid, value, 'uint64', 'l'); 340 | elseif isa(value, 'int8') 341 | header = bitor(header, bitshift(1, 3)); % is_signed = true 342 | header = bitor(header, bitshift(0, 5)); % byte_count_index = 0 (1 byte) 343 | write_byte(fid, header); 344 | if is_array 345 | write_compressed(fid, length(value)); 346 | end 347 | fwrite(fid, value, 'int8', 'l'); 348 | elseif isa(value, 'int16') 349 | header = bitor(header, bitshift(1, 3)); % is_signed = true 350 | header = bitor(header, bitshift(1, 5)); % byte_count_index = 1 (2 bytes) 351 | write_byte(fid, header); 352 | if is_array 353 | write_compressed(fid, length(value)); 354 | end 355 | fwrite(fid, value, 'int16', 'l'); 356 | elseif isa(value, 'int32') 357 | header = bitor(header, bitshift(1, 3)); % is_signed = true 358 | header = bitor(header, bitshift(2, 5)); % byte_count_index = 2 (4 bytes) 359 | write_byte(fid, header); 360 | if is_array 361 | write_compressed(fid, length(value)); 362 | end 363 | fwrite(fid, value, 'int32', 'l'); 364 | elseif isa(value, 'int64') 365 | header = bitor(header, bitshift(1, 3)); % is_signed = true 366 | header = bitor(header, bitshift(3, 5)); % byte_count_index = 3 (8 bytes) 367 | write_byte(fid, header); 368 | if is_array 369 | write_compressed(fid, length(value)); 370 | end 371 | fwrite(fid, value, 'int64', 'l'); 372 | else 373 | error('Unsupported integer type: %s', class(value)); 374 | end 375 | end 376 | 377 | function write_byte(fid, byte) 378 | fwrite(fid, byte, 'uint8', 'l'); 379 | end 380 | 381 | function write_compressed(fid, N) 382 | if N < 64 383 | compressed = bitor(bitshift(N, 2), uint8(0)); 384 | fwrite(fid, compressed, 'uint8', 'l'); 385 | elseif N < 16384 386 | compressed = bitor(bitshift(N, 2), uint16(1)); 387 | fwrite(fid, compressed, 'uint16', 'l'); 388 | elseif N < 1073741824 389 | compressed = bitor(bitshift(N, 2), uint32(2)); 390 | fwrite(fid, compressed, 'uint32', 'l'); 391 | else 392 | compressed = bitor(bitshift(N, 2), uint64(3)); 393 | fwrite(fid, compressed, 'uint64', 'l'); 394 | end 395 | end 396 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | /* ===== CSS Reset & Base Styles ===== */ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | } 7 | 8 | :root { 9 | --primary-color: #3b82f6; 10 | --primary-hover: #2563eb; 11 | --accent-color: #0ea5e9; 12 | --bg-color: #0f172a; 13 | --bg-secondary: #1e293b; 14 | --bg-tertiary: #334155; 15 | --card-bg: #1e293b; 16 | --text-color: #f1f5f9; 17 | --text-secondary: #cbd5e1; 18 | --text-muted: #94a3b8; 19 | --border-color: #334155; 20 | --code-bg: #0f172a; 21 | --success-color: #10b981; 22 | --warning-color: #f59e0b; 23 | --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px -1px rgba(0, 0, 0, 0.3); 24 | --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -2px rgba(0, 0, 0, 0.3); 25 | --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -4px rgba(0, 0, 0, 0.4); 26 | --shadow-glow: 0 0 20px rgba(59, 130, 246, 0.15); 27 | } 28 | 29 | html { 30 | scroll-behavior: smooth; 31 | } 32 | 33 | body { 34 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 35 | line-height: 1.6; 36 | color: var(--text-color); 37 | background-color: var(--bg-color); 38 | } 39 | 40 | .container { 41 | max-width: 1200px; 42 | margin: 0 auto; 43 | padding: 0 2rem; 44 | } 45 | 46 | /* ===== Navigation ===== */ 47 | .navbar { 48 | background-color: rgba(15, 23, 42, 0.95); 49 | backdrop-filter: blur(10px); 50 | border-bottom: 1px solid var(--border-color); 51 | position: sticky; 52 | top: 0; 53 | z-index: 100; 54 | box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3); 55 | } 56 | 57 | .nav-container { 58 | max-width: 1200px; 59 | margin: 0 auto; 60 | padding: 0 2rem; 61 | display: flex; 62 | justify-content: space-between; 63 | align-items: center; 64 | height: 70px; 65 | } 66 | 67 | .nav-logo { 68 | font-size: 1.75rem; 69 | font-weight: 800; 70 | color: var(--primary-color); 71 | text-decoration: none; 72 | letter-spacing: -0.05em; 73 | transition: all 0.2s; 74 | text-shadow: 0 0 20px rgba(59, 130, 246, 0.3); 75 | } 76 | 77 | .nav-logo:hover { 78 | color: var(--accent-color); 79 | text-shadow: 0 0 25px rgba(59, 130, 246, 0.5); 80 | } 81 | 82 | .nav-menu { 83 | display: flex; 84 | list-style: none; 85 | gap: 0.5rem; 86 | align-items: center; 87 | margin: 0; 88 | } 89 | 90 | .nav-menu li { 91 | display: flex; 92 | align-items: center; 93 | } 94 | 95 | .nav-menu li:last-child a { 96 | background-color: var(--primary-color); 97 | color: white; 98 | padding: 0.625rem 1.25rem; 99 | border-radius: 0.5rem; 100 | font-weight: 600; 101 | transition: all 0.2s; 102 | display: inline-flex; 103 | align-items: center; 104 | } 105 | 106 | .nav-menu li:last-child a:hover { 107 | background-color: var(--primary-hover); 108 | transform: translateY(-1px); 109 | box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4); 110 | text-decoration: none; 111 | } 112 | 113 | .nav-menu a { 114 | color: var(--text-secondary); 115 | text-decoration: none; 116 | font-weight: 500; 117 | transition: all 0.2s; 118 | padding: 0.625rem 1rem; 119 | border-radius: 0.375rem; 120 | display: inline-flex; 121 | align-items: center; 122 | } 123 | 124 | .nav-menu a:hover { 125 | color: var(--text-color); 126 | background-color: rgba(59, 130, 246, 0.1); 127 | text-decoration: none; 128 | } 129 | 130 | /* ===== Hero Section ===== */ 131 | .hero { 132 | background: linear-gradient(135deg, #0f172a 0%, #1e3a8a 50%, #0c4a6e 100%); 133 | color: white; 134 | min-height: 500px; 135 | text-align: center; 136 | border-bottom: 1px solid var(--border-color); 137 | position: relative; 138 | display: flex; 139 | align-items: center; 140 | } 141 | 142 | .hero .container { 143 | width: 100%; 144 | } 145 | 146 | .hero::before { 147 | content: ''; 148 | position: absolute; 149 | top: 0; 150 | left: 0; 151 | right: 0; 152 | bottom: 0; 153 | background: radial-gradient(circle at 50% 50%, rgba(59, 130, 246, 0.1) 0%, transparent 50%); 154 | pointer-events: none; 155 | } 156 | 157 | .hero h1 { 158 | font-size: 4rem; 159 | font-weight: 800; 160 | margin-bottom: 0.5rem; 161 | letter-spacing: -0.025em; 162 | position: relative; 163 | z-index: 1; 164 | } 165 | 166 | .hero .subtitle { 167 | font-size: 1.5rem; 168 | font-weight: 600; 169 | margin-bottom: 0.5rem; 170 | position: relative; 171 | z-index: 1; 172 | color: #e0e7ff; 173 | } 174 | 175 | .hero .version { 176 | font-size: 1rem; 177 | margin-bottom: 1rem; 178 | position: relative; 179 | z-index: 1; 180 | color: #cbd5e1; 181 | } 182 | 183 | .hero .tagline { 184 | font-size: 1.125rem; 185 | max-width: 800px; 186 | margin: 0 auto 2rem; 187 | line-height: 1.7; 188 | position: relative; 189 | z-index: 1; 190 | color: #e2e8f0; 191 | } 192 | 193 | .cta-buttons { 194 | display: flex; 195 | gap: 1rem; 196 | justify-content: center; 197 | flex-wrap: wrap; 198 | } 199 | 200 | .btn { 201 | display: inline-block; 202 | padding: 0.875rem 2.5rem; 203 | font-size: 1.0625rem; 204 | font-weight: 600; 205 | text-decoration: none; 206 | border-radius: 0.5rem; 207 | transition: all 0.3s ease; 208 | position: relative; 209 | z-index: 1; 210 | letter-spacing: 0.025em; 211 | } 212 | 213 | .btn-primary { 214 | background: linear-gradient(135deg, var(--primary-color) 0%, var(--accent-color) 100%); 215 | color: white; 216 | box-shadow: 0 4px 15px rgba(59, 130, 246, 0.4); 217 | border: 2px solid transparent; 218 | } 219 | 220 | .btn-primary:hover { 221 | transform: translateY(-3px); 222 | box-shadow: 0 6px 25px rgba(59, 130, 246, 0.6); 223 | text-decoration: none; 224 | } 225 | 226 | .btn-primary:active { 227 | transform: translateY(-1px); 228 | } 229 | 230 | .btn-secondary { 231 | background-color: rgba(255, 255, 255, 0.05); 232 | color: white; 233 | border: 2px solid rgba(255, 255, 255, 0.3); 234 | backdrop-filter: blur(10px); 235 | } 236 | 237 | .btn-secondary:hover { 238 | background-color: rgba(255, 255, 255, 0.1); 239 | border-color: rgba(255, 255, 255, 0.6); 240 | transform: translateY(-3px); 241 | box-shadow: 0 4px 15px rgba(255, 255, 255, 0.1); 242 | text-decoration: none; 243 | } 244 | 245 | .btn-secondary:active { 246 | transform: translateY(-1px); 247 | } 248 | 249 | /* ===== Main Content ===== */ 250 | main { 251 | padding: 4rem 0; 252 | } 253 | 254 | section { 255 | margin-bottom: 4rem; 256 | } 257 | 258 | h2 { 259 | font-size: 2.5rem; 260 | font-weight: 700; 261 | margin-bottom: 1.5rem; 262 | color: var(--text-color); 263 | border-bottom: 3px solid var(--primary-color); 264 | padding-bottom: 0.5rem; 265 | } 266 | 267 | h3 { 268 | font-size: 1.875rem; 269 | font-weight: 600; 270 | margin-top: 2rem; 271 | margin-bottom: 1rem; 272 | color: var(--text-color); 273 | } 274 | 275 | h4 { 276 | font-size: 1.5rem; 277 | font-weight: 600; 278 | margin-top: 1.5rem; 279 | margin-bottom: 0.75rem; 280 | color: var(--text-color); 281 | } 282 | 283 | h5 { 284 | font-size: 1.25rem; 285 | font-weight: 600; 286 | margin-top: 1.25rem; 287 | margin-bottom: 0.5rem; 288 | color: var(--text-color); 289 | } 290 | 291 | p { 292 | margin-bottom: 1rem; 293 | color: var(--text-secondary); 294 | } 295 | 296 | a { 297 | color: var(--primary-color); 298 | text-decoration: none; 299 | } 300 | 301 | a:hover { 302 | text-decoration: underline; 303 | } 304 | 305 | /* ===== Feature Grid ===== */ 306 | .feature-grid { 307 | display: grid; 308 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 309 | gap: 1.5rem; 310 | margin: 2rem 0; 311 | } 312 | 313 | .feature { 314 | background-color: var(--card-bg); 315 | border: 1px solid var(--border-color); 316 | border-radius: 0.75rem; 317 | padding: 1.5rem; 318 | transition: all 0.3s; 319 | } 320 | 321 | .feature:hover { 322 | transform: translateY(-4px); 323 | box-shadow: var(--shadow-glow); 324 | border-color: var(--primary-color); 325 | background-color: var(--bg-tertiary); 326 | } 327 | 328 | .feature-icon { 329 | width: 48px; 330 | height: 48px; 331 | background: linear-gradient(135deg, var(--primary-color) 0%, var(--accent-color) 100%); 332 | border-radius: 0.5rem; 333 | display: flex; 334 | align-items: center; 335 | justify-content: center; 336 | margin-bottom: 1rem; 337 | box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); 338 | } 339 | 340 | .feature-icon i { 341 | font-size: 1.5rem; 342 | color: white; 343 | } 344 | 345 | .feature:hover .feature-icon { 346 | box-shadow: 0 6px 16px rgba(59, 130, 246, 0.5); 347 | } 348 | 349 | .feature h3 { 350 | font-size: 1.25rem; 351 | margin-top: 0; 352 | margin-bottom: 0.5rem; 353 | color: var(--text-color); 354 | } 355 | 356 | .feature p { 357 | margin-bottom: 0; 358 | font-size: 0.95rem; 359 | color: var(--text-secondary); 360 | } 361 | 362 | /* ===== Tables ===== */ 363 | .table-wrapper { 364 | overflow-x: auto; 365 | margin: 1.5rem 0; 366 | border-radius: 0.5rem; 367 | box-shadow: var(--shadow); 368 | } 369 | 370 | table { 371 | width: 100%; 372 | border-collapse: collapse; 373 | background-color: var(--card-bg); 374 | } 375 | 376 | thead { 377 | background-color: var(--bg-tertiary); 378 | color: var(--text-color); 379 | } 380 | 381 | th, td { 382 | padding: 1rem; 383 | text-align: left; 384 | border-bottom: 1px solid var(--border-color); 385 | color: var(--text-secondary); 386 | } 387 | 388 | th { 389 | font-weight: 600; 390 | color: var(--text-color); 391 | } 392 | 393 | tbody tr:hover { 394 | background-color: var(--bg-tertiary); 395 | } 396 | 397 | tbody tr:last-child td { 398 | border-bottom: none; 399 | } 400 | 401 | .perf-number { 402 | font-weight: 600; 403 | color: var(--success-color); 404 | } 405 | 406 | .size-number { 407 | font-weight: 600; 408 | color: var(--text-secondary); 409 | } 410 | 411 | /* ===== Code Blocks ===== */ 412 | code { 413 | background-color: var(--bg-tertiary); 414 | padding: 0.2rem 0.4rem; 415 | border-radius: 0.25rem; 416 | font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; 417 | font-size: 0.875em; 418 | color: #60a5fa; 419 | border: 1px solid var(--border-color); 420 | } 421 | 422 | pre { 423 | background-color: #0a0f1e; 424 | border-radius: 0.5rem; 425 | padding: 1.5rem; 426 | overflow-x: auto; 427 | margin: 1.5rem 0; 428 | box-shadow: var(--shadow); 429 | border: 1px solid var(--border-color); 430 | } 431 | 432 | pre code { 433 | background-color: transparent; 434 | padding: 0; 435 | color: #e2e8f0; 436 | font-size: 0.875rem; 437 | line-height: 1.7; 438 | border: none; 439 | } 440 | 441 | /* ===== Notes & Alerts ===== */ 442 | .note { 443 | background-color: rgba(59, 130, 246, 0.1); 444 | border-left: 4px solid var(--primary-color); 445 | padding: 1rem 1.5rem; 446 | margin: 1.5rem 0; 447 | border-radius: 0.25rem; 448 | border: 1px solid rgba(59, 130, 246, 0.3); 449 | border-left: 4px solid var(--primary-color); 450 | } 451 | 452 | .note p { 453 | margin-bottom: 0.5rem; 454 | color: var(--text-color); 455 | } 456 | 457 | .note p:last-child { 458 | margin-bottom: 0; 459 | } 460 | 461 | .note strong { 462 | color: var(--primary-color); 463 | } 464 | 465 | /* ===== Lists ===== */ 466 | ul, ol { 467 | margin-left: 2rem; 468 | margin-bottom: 1rem; 469 | } 470 | 471 | li { 472 | margin-bottom: 0.5rem; 473 | color: var(--text-secondary); 474 | } 475 | 476 | li strong { 477 | color: var(--text-color); 478 | } 479 | 480 | /* ===== Footer ===== */ 481 | footer { 482 | background-color: var(--bg-secondary); 483 | border-top: 1px solid var(--border-color); 484 | padding: 2rem 0; 485 | text-align: center; 486 | margin-top: 4rem; 487 | } 488 | 489 | footer p { 490 | margin-bottom: 0.5rem; 491 | color: var(--text-muted); 492 | } 493 | 494 | footer a { 495 | color: var(--primary-color); 496 | margin: 0 0.5rem; 497 | transition: color 0.2s; 498 | } 499 | 500 | footer a:hover { 501 | color: var(--accent-color); 502 | } 503 | 504 | /* ===== Hamburger Menu Button ===== */ 505 | .nav-toggle { 506 | display: none; 507 | -webkit-appearance: none; 508 | appearance: none; 509 | background: transparent; 510 | border: none; 511 | cursor: pointer; 512 | padding: 0.75rem; 513 | z-index: 101; 514 | width: 44px; 515 | height: 44px; 516 | position: relative; 517 | flex-shrink: 0; 518 | -webkit-tap-highlight-color: transparent; 519 | } 520 | 521 | .hamburger { 522 | display: block; 523 | width: 22px; 524 | height: 3px; 525 | background-color: var(--text-color); 526 | position: absolute; 527 | top: 50%; 528 | left: 50%; 529 | transform: translate(-50%, -50%); 530 | transition: background-color 0.3s; 531 | border-radius: 2px; 532 | } 533 | 534 | .hamburger::before, 535 | .hamburger::after { 536 | content: ''; 537 | position: absolute; 538 | width: 22px; 539 | height: 3px; 540 | background-color: var(--text-color); 541 | left: 0; 542 | transition: transform 0.3s, top 0.3s; 543 | border-radius: 2px; 544 | } 545 | 546 | .hamburger::before { 547 | top: -7px; 548 | } 549 | 550 | .hamburger::after { 551 | top: 7px; 552 | } 553 | 554 | /* Hamburger animation when open */ 555 | .nav-toggle.nav-open .hamburger { 556 | background-color: transparent; 557 | } 558 | 559 | .nav-toggle.nav-open .hamburger::before { 560 | top: 0; 561 | transform: rotate(45deg); 562 | } 563 | 564 | .nav-toggle.nav-open .hamburger::after { 565 | top: 0; 566 | transform: rotate(-45deg); 567 | } 568 | 569 | /* ===== Responsive Design ===== */ 570 | @media (max-width: 768px) { 571 | .nav-toggle { 572 | display: flex; 573 | align-items: center; 574 | justify-content: center; 575 | } 576 | 577 | .nav-menu { 578 | position: fixed; 579 | top: 0; 580 | right: -100%; 581 | width: 280px; 582 | height: 100%; 583 | height: 100dvh; 584 | background-color: var(--bg-secondary); 585 | display: flex; 586 | flex-direction: column; 587 | align-items: stretch; 588 | padding: 5rem 1.5rem 2rem; 589 | gap: 0.5rem; 590 | transition: right 0.3s ease; 591 | box-shadow: -4px 0 20px rgba(0, 0, 0, 0.3); 592 | border-left: 1px solid var(--border-color); 593 | overflow-y: auto; 594 | -webkit-overflow-scrolling: touch; 595 | } 596 | 597 | .nav-menu.nav-open { 598 | right: 0; 599 | } 600 | 601 | .nav-menu li { 602 | width: 100%; 603 | } 604 | 605 | .nav-menu a { 606 | display: block; 607 | padding: 0.875rem 1rem; 608 | font-size: 1rem; 609 | border-radius: 0.5rem; 610 | } 611 | 612 | .nav-menu li:last-child a { 613 | margin-top: 0.5rem; 614 | text-align: center; 615 | } 616 | 617 | .nav-container { 618 | padding: 0 1rem; 619 | height: 60px; 620 | } 621 | 622 | .hero { 623 | min-height: 400px; 624 | padding: 2rem 0; 625 | } 626 | 627 | .hero h1 { 628 | font-size: 2.5rem; 629 | } 630 | 631 | .hero .subtitle { 632 | font-size: 1.25rem; 633 | } 634 | 635 | .hero .tagline { 636 | font-size: 1rem; 637 | padding: 0 1rem; 638 | } 639 | 640 | .container { 641 | padding: 0 1rem; 642 | } 643 | 644 | h2 { 645 | font-size: 2rem; 646 | } 647 | 648 | h3 { 649 | font-size: 1.5rem; 650 | } 651 | 652 | .feature-grid { 653 | grid-template-columns: 1fr; 654 | } 655 | 656 | table { 657 | font-size: 0.875rem; 658 | } 659 | 660 | th, td { 661 | padding: 0.75rem; 662 | } 663 | 664 | .btn { 665 | padding: 0.75rem 2rem; 666 | font-size: 1rem; 667 | } 668 | 669 | .nav-logo { 670 | font-size: 1.5rem; 671 | } 672 | } 673 | 674 | @media (max-width: 480px) { 675 | .hero { 676 | min-height: auto; 677 | padding: 3rem 0; 678 | } 679 | 680 | .hero h1 { 681 | font-size: 2rem; 682 | } 683 | 684 | .hero .subtitle { 685 | font-size: 1.125rem; 686 | } 687 | 688 | .cta-buttons { 689 | flex-direction: column; 690 | align-items: stretch; 691 | gap: 0.75rem; 692 | padding: 0 1rem; 693 | } 694 | 695 | .btn { 696 | width: 100%; 697 | text-align: center; 698 | } 699 | } 700 | -------------------------------------------------------------------------------- /matlab/load_beve.m: -------------------------------------------------------------------------------- 1 | % Load a .beve file 2 | % Reference: https://github.com/stephenberry/beve 3 | % Given Path to file: Load 4 | % Given Path to folder: Open load dialog box in folder 5 | % Given no arguments: Open load dialog box in working directory 6 | function data = load_beve(path) 7 | 8 | isPathDirectory = false; 9 | defaultFolder = ''; % present working directory 10 | isPathGiven = exist('path','var') && ~isempty(path); 11 | if(isPathGiven) 12 | %isPathFile = isfile(path); 13 | isPathDirectory = isfolder(path); 14 | if(isPathDirectory) 15 | defaultFolder = path; 16 | end 17 | end 18 | 19 | % Open Dialog Box 20 | if ( ~isPathGiven || isPathDirectory ) 21 | [file,path] = uigetfile(fullfile(defaultFolder,'*.beve'),... 22 | 'Select a BEVE file to load.'); 23 | path = fullfile(path,file); 24 | fprintf("BEVE File Selected:\n'%s'\n",path); 25 | end 26 | 27 | fid = fopen(path, 'rb'); 28 | if fid == -1 29 | error('Failed to open file'); 30 | end 31 | 32 | data = read_value(fid); 33 | 34 | fclose(fid); 35 | end 36 | 37 | % 'l' denotes little endian format 38 | % (https://www.mathworks.com/help/matlab/ref/fread.html) 39 | function data = read_value(fid) 40 | % Read the header 41 | header = fread(fid, 1, '*uint8', 'l'); 42 | assert(~isempty(header), 'Unexpected end of data'); 43 | 44 | % Configuration mapping 45 | config = uint8([1, 2, 4, 8]); 46 | 47 | % Extract header components 48 | type = bitand(header, 0b00000111); 49 | switch type 50 | case 0 % null or boolean 51 | is_bool = bitshift(bitand(header, 0b00001000), -3); 52 | if is_bool 53 | data = logical(bitshift(bitand(header, 0b11110000), -4)); 54 | else 55 | data = NaN; 56 | end 57 | case 1 % number 58 | element_type = bitshift(bitand(header, 0b00011000), -3); 59 | is_float = false; 60 | is_signed = false; 61 | switch element_type 62 | case 0 63 | is_float = true; 64 | case 1 65 | is_signed = true; 66 | end 67 | 68 | byte_count_index = bitshift(bitand(header, 0b11100000), -5); 69 | byte_count = config(byte_count_index + 1); 70 | 71 | if is_float 72 | switch byte_count 73 | case 4 74 | data = fread(fid, 1, '*float32', 'l'); 75 | case 8 76 | data = fread(fid, 1, '*float64', 'l'); 77 | end 78 | else 79 | if is_signed 80 | switch byte_count 81 | case 1 82 | data = fread(fid, 1, '*int8', 'l'); 83 | case 2 84 | data = fread(fid, 1, '*int16', 'l'); 85 | case 4 86 | data = fread(fid, 1, '*int32', 'l'); 87 | case 8 88 | data = fread(fid, 1, '*int64', 'l'); 89 | end 90 | else 91 | switch byte_count 92 | case 1 93 | data = fread(fid, 1, '*uint8', 'l'); 94 | case 2 95 | data = fread(fid, 1, '*uint16', 'l'); 96 | case 4 97 | data = fread(fid, 1, '*uint32', 'l'); 98 | case 8 99 | data = fread(fid, 1, '*uint64', 'l'); 100 | end 101 | end 102 | end 103 | case 2 % string 104 | string_size = read_compressed(fid); 105 | data = fread(fid, string_size, 'char=>char', 'l')'; 106 | case 3 % object 107 | key_type = bitshift(bitand(header, 0b00011000), -3); 108 | is_string = (key_type == 0); 109 | is_signed = (key_type == 1); 110 | 111 | % Get byte count for integer keys 112 | byte_count_index = bitshift(bitand(header, 0b11100000), -5); 113 | byte_count = config(byte_count_index + 1); 114 | 115 | N = read_compressed(fid); 116 | 117 | % Initialize empty struct if there are fields 118 | if N > 0 119 | data = struct(); 120 | else 121 | data = []; % Empty object 122 | empty_key = 'object'; 123 | try 124 | empty_key = evalin('caller','legal_string'); 125 | catch 126 | end 127 | warning("Zero object keys found for %s", empty_key); 128 | return; 129 | end 130 | 131 | for ii = 1:N 132 | if is_string 133 | % Read string key 134 | string_size = read_compressed(fid); 135 | string = fread(fid, string_size, 'char=>char', 'l')'; 136 | legal_string = makeValidFieldName(string); 137 | data.(legal_string) = read_value(fid); 138 | else 139 | % Handle integer keys (signed or unsigned) 140 | if is_signed 141 | switch byte_count 142 | case 1 143 | key = fread(fid, 1, '*int8', 'l'); 144 | case 2 145 | key = fread(fid, 1, '*int16', 'l'); 146 | case 4 147 | key = fread(fid, 1, '*int32', 'l'); 148 | case 8 149 | key = fread(fid, 1, '*int64', 'l'); 150 | end 151 | else % unsigned 152 | switch byte_count 153 | case 1 154 | key = fread(fid, 1, '*uint8', 'l'); 155 | case 2 156 | key = fread(fid, 1, '*uint16', 'l'); 157 | case 4 158 | key = fread(fid, 1, '*uint32', 'l'); 159 | case 8 160 | key = fread(fid, 1, '*uint64', 'l'); 161 | end 162 | end 163 | % Convert integer key to valid MATLAB field name 164 | key_str = sprintf('int_%d', key); 165 | data.(key_str) = read_value(fid); 166 | end 167 | end 168 | 169 | case 4 % typed array 170 | element_type = bitshift(bitand(header, 0b00011000), -3); 171 | [is_float, is_signed, is_bool_or_string] = ... 172 | deal(element_type == 0, element_type == 1, element_type == 3); 173 | is_numeric = not(is_bool_or_string); 174 | string_flag = bitshift(bitand(header, 0b00100000), -5); 175 | is_string = is_bool_or_string && string_flag; 176 | is_bool = is_bool_or_string && not(string_flag); 177 | 178 | %% Only used for numeric types 179 | byte_count_index = bitshift(bitand(header, 0b11100000), -5); 180 | byte_count = config(byte_count_index + 1); 181 | 182 | % Read the N of the array 183 | N = read_compressed(fid); 184 | 185 | if is_numeric 186 | if is_float 187 | switch byte_count 188 | case 4 189 | data = fread(fid, N, '*float32', 'l'); 190 | case 8 191 | data = fread(fid, N, '*float64', 'l'); 192 | end 193 | else 194 | if is_signed 195 | switch byte_count 196 | case 1 197 | data = fread(fid, N, '*int8', 'l'); 198 | case 2 199 | data = fread(fid, N, '*int16', 'l'); 200 | case 4 201 | data = fread(fid, N, '*int32', 'l'); 202 | case 8 203 | data = fread(fid, N, '*int64', 'l'); 204 | end 205 | else 206 | switch byte_count 207 | case 1 208 | data = fread(fid, N, '*uint8', 'l'); 209 | case 2 210 | data = fread(fid, N, '*uint16', 'l'); 211 | case 4 212 | data = fread(fid, N, '*uint32', 'l'); 213 | case 8 214 | data = fread(fid, N, '*uint64', 'l'); 215 | end 216 | end 217 | end 218 | elseif is_string 219 | % Read an array of strings (or cell array of character vectors?) 220 | % For each element... (there are N) 221 | % Read the compressed size, then read that number of chars 222 | data=strings(N,1); % Initialize string array 223 | for ii=1:N 224 | string_size = read_compressed(fid); 225 | data{ii} = fread(fid, string_size, 'char=>char', 'l')'; 226 | end 227 | elseif is_bool 228 | % Read packed boolean values (8 per byte) 229 | num_bytes = ceil(N / 8); 230 | packed_bytes = fread(fid, num_bytes, '*uint8', 'l'); 231 | 232 | % Unpack the booleans from the bytes 233 | data = false(N, 1); 234 | for i = 1:N 235 | byte_index = floor((i-1) / 8) + 1; 236 | bit_position = mod(i-1, 8); 237 | data(i) = bitand(bitshift(packed_bytes(byte_index), -bit_position), 1) == 1; 238 | end 239 | end 240 | 241 | case 5 % untyped array 242 | N = read_compressed(fid); 243 | 244 | data = cell(N, 1); 245 | for ii = 1:N 246 | data{ii} = read_value(fid); 247 | end 248 | case 6 % extensions 249 | extension = bitshift(bitand(header, 0b11111000), -3); 250 | switch extension 251 | case 1 % variants 252 | read_compressed(fid); 253 | data = read_value(fid); 254 | case 2 % matrices 255 | layout = bitand(fread(fid, 1, '*uint8', 'l'), 0b00000001); 256 | extents = read_value(fid); 257 | matrix_data = read_value(fid); 258 | 259 | switch layout 260 | case 0 % row major 261 | % For row-major, we need to reshape and transpose 262 | data = reshape(matrix_data, extents(2), extents(1))'; 263 | case 1 % column major 264 | data = reshape(matrix_data, extents(1), extents(2)); 265 | otherwise 266 | error('Unsupported layout'); 267 | end 268 | case 3 % complex numbers 269 | data = read_complex(fid); 270 | otherwise 271 | error('Unsupported extension'); 272 | end 273 | otherwise 274 | error('Unsupported type'); 275 | end 276 | end 277 | 278 | function data = read_complex(fid) 279 | complex_header = fread(fid, 1, '*uint8', 'l'); 280 | type = bitand(complex_header, 0b00000111); 281 | 282 | num_type = bitshift(bitand(complex_header, 0b00011000), -3); 283 | is_float = false; 284 | is_signed = false; 285 | switch num_type 286 | case 0 287 | is_float = true; 288 | case 1 289 | is_signed = true; 290 | end 291 | 292 | byte_count_index = bitshift(bitand(complex_header, 0b11100000), -5); 293 | config = uint8([1, 2, 4, 8]); 294 | byte_count = config(byte_count_index + 1); 295 | 296 | switch type 297 | case 0 % complex number 298 | if is_float 299 | switch byte_count 300 | case 4 301 | data = complex(fread(fid, 2, '*float32', 'l')); 302 | case 8 303 | data = complex(fread(fid, 2, '*float64', 'l')); 304 | end 305 | else 306 | if is_signed 307 | switch byte_count 308 | case 1 309 | data = complex(fread(fid, 2, '*int8', 'l')); 310 | case 2 311 | data = complex(fread(fid, 2, '*int16', 'l')); 312 | case 4 313 | data = complex(fread(fid, 2, '*int32', 'l')); 314 | case 8 315 | data = complex(fread(fid, 2, '*int64', 'l')); 316 | end 317 | else 318 | switch byte_count 319 | case 1 320 | data = complex(fread(fid, 2, '*uint8', 'l')); 321 | case 2 322 | data = complex(fread(fid, 2, '*uint16', 'l')); 323 | case 4 324 | data = complex(fread(fid, 2, '*uint32', 'l')); 325 | case 8 326 | data = complex(fread(fid, 2, '*uint64', 'l')); 327 | end 328 | end 329 | end 330 | case 1 % complex array 331 | % Read the N of the array 332 | N = read_compressed(fid); 333 | 334 | if is_float 335 | switch byte_count 336 | case 4 337 | raw = fread(fid, [2, N], '*float32', 'l'); 338 | data = complex(raw(1, :), raw(2, :)); 339 | case 8 340 | raw = fread(fid, [2, N], '*float64', 'l'); 341 | data = complex(raw(1, :), raw(2, :)); 342 | end 343 | else 344 | if is_signed 345 | switch byte_count 346 | case 1 347 | raw = fread(fid, [2, N], '*int8', 'l'); 348 | case 2 349 | raw = fread(fid, [2, N], '*int16', 'l'); 350 | case 4 351 | raw = fread(fid, [2, N], '*int32', 'l'); 352 | case 8 353 | raw = fread(fid, [2, N], '*int64', 'l'); 354 | end 355 | else 356 | switch byte_count 357 | case 1 358 | raw = fread(fid, N, '*uint8', 'l'); 359 | case 2 360 | raw = fread(fid, N, '*uint16', 'l'); 361 | case 4 362 | raw = fread(fid, N, '*uint32', 'l'); 363 | case 8 364 | raw = fread(fid, N, '*uint64', 'l'); 365 | end 366 | end 367 | data = complex(raw(1, :), raw(2, :)); % remap to complex 368 | end 369 | end 370 | end 371 | 372 | function N = read_compressed(fid) 373 | config = uint8([1, 2, 4, 8]); 374 | 375 | compressed = fread(fid, 1, '*uint8', 'l'); 376 | n_size_bytes = config(bitand(compressed, 0b00000011) + 1); 377 | fseek(fid, -1, 'cof'); % 'cof' means current position 378 | switch n_size_bytes 379 | case 1 380 | N = fread(fid, 1, '*uint8', 'l'); 381 | case 2 382 | N = fread(fid, 1, '*uint16', 'l'); 383 | case 4 384 | N = fread(fid, 1, '*uint32', 'l'); 385 | case 8 386 | N = fread(fid, 1, '*uint64', 'l'); 387 | otherwise 388 | error('Unsupported N'); 389 | end 390 | N = bitshift(N, -2); 391 | end 392 | 393 | 394 | function validName = makeValidFieldName(fieldName) 395 | % Convert string to char array if necessary 396 | if isstring(fieldName) 397 | fieldName = char(fieldName); 398 | end 399 | 400 | % Transpose if is a column vector 401 | if(size(fieldName,1) > 1 && size(fieldName,2) == 1) 402 | fieldName = fieldName'; 403 | end 404 | 405 | % Replace invalid characters with underscores 406 | validName = regexprep(fieldName, '[^a-zA-Z0-9_]', ''); 407 | 408 | % Ensure the name starts with a letter 409 | if isempty(regexp(validName, '^[a-zA-Z]', 'once')) 410 | validName = ['A', validName]; % Prepend 'A' if the name does not start with a letter 411 | end 412 | 413 | % Truncate if the name is too long 414 | maxNameLength = namelengthmax(); 415 | if length(validName) > maxNameLength 416 | validName = validName(1:maxNameLength); 417 | end 418 | end -------------------------------------------------------------------------------- /golang/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "math" 8 | "math/big" 9 | "os" 10 | ) 11 | 12 | type Beve struct { 13 | buffer []byte 14 | cursor int 15 | } 16 | 17 | // Helper functions to simulate reading different data types from the buffer 18 | func (b *Beve) readUInt8() uint8 { 19 | val := b.buffer[b.cursor] 20 | b.cursor++ 21 | return val 22 | } 23 | 24 | func (b *Beve) readInt8() int8 { 25 | return int8(b.readUInt8()) 26 | } 27 | 28 | func (b *Beve) readUInt16() uint16 { 29 | val := binary.LittleEndian.Uint16(b.buffer[b.cursor:]) 30 | b.cursor += 2 31 | return val 32 | } 33 | 34 | func (b *Beve) readInt16() int16 { 35 | return int16(b.readUInt16()) 36 | } 37 | 38 | // ... (similar functions for readUInt32, readInt32, readFloat, readDouble, etc.) 39 | func (b *Beve) readUInt32() uint32 { 40 | val := binary.LittleEndian.Uint32(b.buffer[b.cursor:]) 41 | b.cursor += 4 42 | return val 43 | } 44 | func (b *Beve) readInt32() int32 { 45 | return int32(b.readUInt32()) 46 | } 47 | func (b *Beve) readUInt64() uint64 { 48 | val := binary.LittleEndian.Uint64(b.buffer[b.cursor:]) 49 | b.cursor += 8 50 | return val 51 | } 52 | 53 | func (b *Beve) readInt64() int64 { 54 | return int64(b.readUInt64()) 55 | } 56 | func (b *Beve) readFloat() float32 { 57 | return math.Float32frombits(b.readUInt32()) 58 | } 59 | func (b *Beve) readDouble() float64 { 60 | return math.Float64frombits(b.readUInt64()) 61 | } 62 | func (b *Beve) readBigInt64() *big.Int { 63 | // Read and construct big.Int from buffer 64 | bytes := make([]byte, 8) 65 | copy(bytes, b.buffer[b.cursor:b.cursor+8]) 66 | b.cursor += 8 67 | return new(big.Int).SetBytes(bytes) 68 | } 69 | 70 | func (b *Beve) readBigUInt64() *big.Int { 71 | // Read and construct big.Int from buffer 72 | bytes := make([]byte, 8) 73 | copy(bytes, b.buffer[b.cursor:b.cursor+8]) 74 | b.cursor += 8 75 | return new(big.Int).SetBytes(bytes) 76 | } 77 | func (b *Beve) readFloat32() float32 { 78 | bits := binary.LittleEndian.Uint32(b.buffer[b.cursor:]) 79 | b.cursor += 4 80 | return math.Float32frombits(bits) 81 | } 82 | 83 | func (b *Beve) readFloat64() float64 { 84 | bits := binary.LittleEndian.Uint64(b.buffer[b.cursor:]) 85 | b.cursor += 8 86 | return math.Float64frombits(bits) 87 | } 88 | 89 | func (b *Beve) readCompressed() int { 90 | header := b.buffer[b.cursor] 91 | b.cursor++ 92 | config := header & 0b00000011 93 | 94 | switch config { 95 | case 0: 96 | return int(header >> 2) 97 | case 1: 98 | value := binary.LittleEndian.Uint16(b.buffer[b.cursor:]) 99 | b.cursor += 2 100 | return int(value >> 2) 101 | case 2: 102 | value := binary.LittleEndian.Uint32(b.buffer[b.cursor:]) 103 | b.cursor += 4 104 | return int(value >> 2) 105 | case 3: 106 | var val big.Int 107 | for i := 0; i < 8; i++ { 108 | val.Or(&val, new(big.Int).Lsh( 109 | new(big.Int).SetUint64(uint64(b.buffer[b.cursor])), 110 | uint(8*i), 111 | )) 112 | b.cursor++ 113 | } 114 | val.Rsh(&val, 2) 115 | return int(val.Int64()) // Büyük sayıyı int'e dönüştür 116 | default: 117 | return 0 118 | } 119 | } 120 | 121 | func (b *Beve) readString() string { 122 | size := b.readCompressed() 123 | // Assuming you have a way to decode the string from the buffer 124 | str := string(b.buffer[b.cursor : b.cursor+size]) 125 | b.cursor += size 126 | return str 127 | } 128 | 129 | // TODO Implement readValue function 130 | func reshape[T int8 | int | int16 | int32 | int64 | uint8 | uint | uint16 | uint32 | uint64 | float32 | float64](data []T, rows, cols int) [][]T { 131 | // Varsayalım ki data, []float64 şeklinde bir dilim olarak geliyor 132 | 133 | if rows*cols != len(data) { 134 | panic("Invalid dimensions for matrix reshape") 135 | } 136 | 137 | result := make([][]T, rows) 138 | for i := range result { 139 | result[i] = make([]T, cols) 140 | copy(result[i], data[i*cols:(i+1)*cols]) 141 | } 142 | return result 143 | } 144 | 145 | // TODO Implement readComplex function 146 | func (b *Beve) readComplex() complex128 { 147 | real := b.readFloat64() 148 | imag := b.readFloat64() 149 | return complex(real, imag) 150 | } 151 | 152 | func (b *Beve) readValue() (interface{}, error) { 153 | header := b.buffer[b.cursor] 154 | b.cursor++ 155 | config := []uint8{1, 2, 4, 8} 156 | typ := header & 0b00000111 157 | 158 | switch typ { 159 | case 0: // null or boolean 160 | isBool := (header & 0b00001000) >> 3 161 | if isBool > 0 { 162 | return (header&0b11110000)>>4 > 0, nil 163 | } else { 164 | return nil, nil 165 | } 166 | case 1: // number 167 | numType := (header & 0b00011000) >> 3 168 | isFloat := numType == 0 169 | isSigned := numType == 1 170 | byteCountIndex := (header & 0b11100000) >> 5 171 | byteCount := config[byteCountIndex] 172 | 173 | if isFloat { 174 | switch byteCount { 175 | case 4: 176 | return b.readFloat(), nil 177 | case 8: 178 | return b.readDouble(), nil 179 | } 180 | } else { 181 | 182 | if isSigned { 183 | switch byteCount { 184 | case 1: 185 | return b.readInt8(), nil 186 | case 2: 187 | return b.readInt16(), nil 188 | case 4: 189 | return b.readInt32(), nil 190 | case 8: 191 | return b.readInt64(), nil 192 | } 193 | } else { 194 | switch byteCount { 195 | case 1: 196 | return b.readUInt8(), nil 197 | case 2: 198 | return b.readUInt16(), nil 199 | case 4: 200 | return b.readUInt32(), nil 201 | case 8: 202 | return b.readUInt64(), nil 203 | } 204 | } 205 | } 206 | case 2: // string 207 | size := b.readCompressed() 208 | // Assuming you have a way to decode the string from the buffer 209 | str := string(b.buffer[b.cursor : b.cursor+size]) 210 | b.cursor += size 211 | return str, nil 212 | 213 | case 3: // object 214 | keyType := (header & 0b00011000) >> 3 215 | isString := keyType == 0 216 | // isSigned := keyType == 1 // Kullanılmayan değişken, kaldırıldı 217 | // byteCountIndex := (header & 0b11100000) >> 5 218 | // byteCount := config[byteCountIndex] 219 | N := b.readCompressed() 220 | 221 | // objectData'yı Go'da map[string]interface{} olarak temsil edelim 222 | objectData := make(map[string]interface{}) 223 | 224 | for i := 0; i < N; i++ { 225 | if isString { 226 | size := b.readCompressed() 227 | key := string(b.buffer[b.cursor : b.cursor+size]) 228 | b.cursor += size 229 | value, err := b.readValue() 230 | if err != nil { 231 | return nil, err // Hata durumunu işleyin 232 | } 233 | objectData[key] = value 234 | } else { 235 | return nil, errors.New("TODO: support integer keys") // Hata fırlatın 236 | } 237 | } 238 | return objectData, nil 239 | 240 | case 4: // typed array 241 | numType := (header & 0b00011000) >> 3 242 | isFloat := numType == 0 243 | isSigned := numType == 1 244 | byteCountIndexArray := (header & 0b11100000) >> 5 245 | byteCountArray := config[byteCountIndexArray] 246 | 247 | if numType == 3 { 248 | isString := (header & 0b00100000) >> 5 249 | if isString != 0 { // ">" ile kontrol edebiliriz, "!= 0" ile aynı 250 | N := b.readCompressed() 251 | array := make([]string, N) 252 | for i := 0; i < N; i++ { 253 | size := b.readCompressed() 254 | // UTF-8 varsayımı ile stringe çevirme 255 | array[i] = string(b.buffer[b.cursor : b.cursor+size]) 256 | b.cursor += size 257 | } 258 | return array, nil 259 | } else { 260 | return nil, errors.New("Boolean array support not implemented") 261 | } 262 | } else if isFloat { 263 | N := b.readCompressed() 264 | var array interface{} 265 | 266 | switch byteCountArray { 267 | case 4: 268 | array = make([]float32, N) 269 | for i := 0; i < N; i++ { 270 | array.([]float32)[i] = b.readFloat32() 271 | } 272 | case 8: 273 | array = make([]float64, N) 274 | for i := 0; i < N; i++ { 275 | array.([]float64)[i] = b.readFloat64() 276 | } 277 | default: 278 | return nil, errors.New("Unsupported float size") 279 | } 280 | // array tip dönüşümü 281 | return array, nil 282 | } else { 283 | N := b.readCompressed() 284 | // array tip belirleme 285 | var array interface{} 286 | if isSigned { 287 | switch byteCountArray { 288 | case 1: 289 | array = make([]int8, N) 290 | for i := 0; i < N; i++ { 291 | array.([]int8)[i] = b.readInt8() 292 | } 293 | case 2: 294 | array = make([]int16, N) 295 | for i := 0; i < N; i++ { 296 | array.([]int16)[i] = b.readInt16() 297 | } 298 | case 4: 299 | array = make([]int32, N) 300 | for i := 0; i < N; i++ { 301 | array.([]int32)[i] = b.readInt32() 302 | } 303 | case 8: 304 | array = make([]*big.Int, N) 305 | for i := 0; i < N; i++ { 306 | array.([]*big.Int)[i] = b.readBigInt64() 307 | } 308 | default: 309 | return nil, errors.New("Unsupported signed integer size") 310 | } 311 | } else { // unsigned 312 | switch byteCountArray { 313 | case 1: 314 | array = make([]uint8, N) 315 | for i := 0; i < N; i++ { 316 | array.([]uint8)[i] = b.readUInt8() 317 | } 318 | case 2: 319 | array = make([]uint16, N) 320 | for i := 0; i < N; i++ { 321 | array.([]uint16)[i] = b.readUInt16() 322 | } 323 | case 4: 324 | array = make([]uint32, N) 325 | for i := 0; i < N; i++ { 326 | array.([]uint32)[i] = b.readUInt32() 327 | } 328 | case 8: 329 | array = make([]*big.Int, N) 330 | for i := 0; i < N; i++ { 331 | array.([]*big.Int)[i] = b.readBigUInt64() 332 | } 333 | default: 334 | return nil, errors.New("Unsupported unsigned integer size") 335 | } 336 | } 337 | return array, nil 338 | } 339 | 340 | case 5: // untyped array 341 | N := b.readCompressed() 342 | arr := make([]interface{}, N) 343 | for i := 0; i < N; i++ { 344 | val, err := b.readValue() 345 | if err != nil { 346 | return nil, err 347 | } 348 | arr[i] = val 349 | } 350 | return arr, nil 351 | 352 | case 6: // extensions 353 | extension := (header & 0b11111000) >> 3 354 | switch extension { 355 | case 1: // variants 356 | _ = b.readCompressed() // Skip variant tag 357 | return b.readValue() 358 | case 2: // matrices 359 | layout := b.buffer[b.cursor] & 0b00000001 360 | b.cursor++ 361 | switch layout { 362 | case 0: // row major 363 | return nil, errors.New("Row major matrix layout not implemented") 364 | case 1: // column major 365 | extents, err := b.readValue() 366 | if err != nil { 367 | return nil, err 368 | } 369 | extentsSlice, ok := extents.([]interface{}) 370 | if !ok || len(extentsSlice) != 2 { 371 | return nil, errors.New("Invalid extents for matrix") 372 | } 373 | rows, _ := extentsSlice[0].(int) 374 | cols, _ := extentsSlice[1].(int) 375 | 376 | matrixData, err := b.readValue() 377 | if err != nil { 378 | return nil, err 379 | } 380 | // reshape function implementation needed here 381 | switch matrixData.(type) { 382 | case []float64: 383 | return reshape[float64](matrixData.([]float64), rows, cols), nil 384 | case []int: 385 | return reshape[int](matrixData.([]int), rows, cols), nil 386 | case []int16: 387 | return reshape[int16](matrixData.([]int16), rows, cols), nil 388 | case []int32: 389 | return reshape[int32](matrixData.([]int32), rows, cols), nil 390 | case []int64: 391 | return reshape[int64](matrixData.([]int64), rows, cols), nil 392 | case []uint8: 393 | return reshape[uint8](matrixData.([]uint8), rows, cols), nil 394 | case []uint: 395 | return reshape[uint](matrixData.([]uint), rows, cols), nil 396 | case []uint16: 397 | return reshape[uint16](matrixData.([]uint16), rows, cols), nil 398 | case []uint32: 399 | return reshape[uint32](matrixData.([]uint32), rows, cols), nil 400 | case []uint64: 401 | return reshape[uint64](matrixData.([]uint64), rows, cols), nil 402 | case []float32: 403 | return reshape[float32](matrixData.([]float32), rows, cols), nil 404 | default: 405 | return nil, errors.New("Unsupported matrix data type") 406 | } 407 | default: 408 | return nil, errors.New("Unsupported matrix layout") 409 | } 410 | case 3: // complex numbers 411 | return b.readComplex(), nil // readComplex function implementation needed here 412 | default: 413 | return nil, errors.New("Unsupported extension") 414 | } 415 | 416 | default: 417 | return nil, errors.New("Unsupported type") 418 | } 419 | 420 | return nil, errors.New("Shouldn't reach here") 421 | } 422 | 423 | // Writer struct 424 | type Writer struct { 425 | buffer []byte 426 | offset int 427 | } 428 | 429 | func NewWriter(size int) *Writer { 430 | if size <= 0 { 431 | size = 256 // Default size 432 | } 433 | return &Writer{buffer: make([]byte, size), offset: 0} 434 | } 435 | 436 | func (w *Writer) ensureCapacity(size int) { 437 | if w.offset+size > len(w.buffer) { 438 | newBuffer := make([]byte, (len(w.buffer)+size)*2) 439 | copy(newBuffer, w.buffer) 440 | w.buffer = newBuffer 441 | } 442 | } 443 | 444 | // append uint8 445 | func (w *Writer) appendUint8(value uint8) error { 446 | if value > 255 { 447 | return errors.New("Value must be an integer between 0 and 255") 448 | } 449 | w.ensureCapacity(1) 450 | w.buffer[w.offset] = value 451 | w.offset++ 452 | return nil 453 | } 454 | 455 | // append uint16 456 | func (w *Writer) appendUint16(value uint16) error { 457 | if value > 65535 { 458 | return errors.New("Value must be an integer between 0 and 65535") 459 | } 460 | w.ensureCapacity(2) 461 | binary.LittleEndian.PutUint16(w.buffer[w.offset:], value) 462 | w.offset += 2 463 | return nil 464 | } 465 | 466 | // append uint32 467 | func (w *Writer) appendUint32(value uint32) error { 468 | if value > 4294967295 { 469 | return errors.New("Value must be an integer between 0 and 4294967295") 470 | } 471 | w.ensureCapacity(4) 472 | binary.LittleEndian.PutUint32(w.buffer[w.offset:], value) 473 | w.offset += 4 474 | return nil 475 | } 476 | 477 | // append uint64 478 | func (w *Writer) appendUint64(value *big.Int) error { 479 | if value.Cmp(big.NewInt(0)) == -1 || value.Cmp(new(big.Int).Lsh(big.NewInt(1), 64)) == 1 { 480 | return errors.New("Value must be an integer between 0 and 18446744073709551615") 481 | } 482 | w.ensureCapacity(8) 483 | low := new(big.Int).And(value, big.NewInt(0xffffffff)) 484 | high := new(big.Int).Rsh(value, 32) 485 | 486 | binary.LittleEndian.PutUint32(w.buffer[w.offset:], uint32(low.Uint64())) 487 | binary.LittleEndian.PutUint32(w.buffer[w.offset+4:], uint32(high.Uint64())) 488 | w.offset += 8 489 | return nil 490 | } 491 | 492 | // append 493 | func (w *Writer) Append(value interface{}) error { 494 | switch v := value.(type) { 495 | case []interface{}: 496 | for _, element := range v { 497 | err := w.Append(element) 498 | if err != nil { 499 | return err 500 | } 501 | } 502 | case string: 503 | bytes := []byte(v) 504 | w.ensureCapacity(len(bytes)) 505 | copy(w.buffer[w.offset:], bytes) 506 | w.offset += len(bytes) 507 | case int, int8, int16, int32: 508 | w.ensureCapacity(4) 509 | binary.LittleEndian.PutUint32(w.buffer[w.offset:], uint32(v.(int32))) 510 | w.offset += 4 511 | case float32, float64: 512 | w.ensureCapacity(8) 513 | binary.LittleEndian.PutUint64(w.buffer[w.offset:], math.Float64bits(v.(float64))) 514 | w.offset += 8 515 | default: 516 | return errors.New("Unsupported value type") 517 | } 518 | return nil 519 | } 520 | 521 | // writeBeve 522 | func WriteBeve(data interface{}) ([]byte, error) { 523 | writer := NewWriter(0) // Use default size 524 | err := writeValue(writer, data) 525 | return writer.buffer[:writer.offset], err // Return the actual used part of the buffer 526 | } 527 | 528 | // writeValue 529 | func writeValue(writer *Writer, value interface{}) error { 530 | switch v := value.(type) { 531 | case []float64: 532 | writer.appendUint8(0b01100000 | 4) // float64_t, 8 bytes 533 | writeCompressed(writer, len(v)) 534 | for _, f := range v { 535 | writer.Append(f) 536 | } 537 | case []int: 538 | writer.appendUint8(0b01001000 | 4) // int32_t, 4 bytes 539 | writeCompressed(writer, len(v)) 540 | for _, i := range v { 541 | writer.Append(i) 542 | } 543 | case bool: 544 | if v { 545 | writer.appendUint8(0b00011000) 546 | } else { 547 | writer.appendUint8(0b00001000) 548 | } 549 | case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: 550 | if v.(float64)-float64(int(v.(float64))) != 0 { 551 | writer.appendUint8(0b01100000) 552 | } else { 553 | writer.appendUint8(0b01001000) 554 | } 555 | writer.Append(v) 556 | case string: 557 | writer.appendUint8(2) 558 | writeCompressed(writer, len(v)) 559 | writer.Append(v) 560 | case []interface{}: 561 | writer.appendUint8(5) 562 | writeCompressed(writer, len(v)) 563 | for _, val := range v { 564 | writeValue(writer, val) 565 | } 566 | case map[string]interface{}: 567 | writer.appendUint8(3) // Assume string keys 568 | writeCompressed(writer, len(v)) 569 | for key, val := range v { 570 | writeCompressed(writer, len(key)) 571 | writer.Append(key) 572 | writeValue(writer, val) 573 | } 574 | default: 575 | return errors.New("Unsupported data type") 576 | } 577 | 578 | return nil 579 | } 580 | 581 | // writeCompressed 582 | func writeCompressed(writer *Writer, N int) { 583 | if N < 64 { 584 | compressed := (N << 2) | 0 585 | writer.appendUint8(uint8(compressed)) 586 | } else if N < 16384 { 587 | compressed := (N << 2) | 1 588 | writer.appendUint16(uint16(compressed)) 589 | } else if N < 1073741824 { 590 | compressed := (N << 2) | 2 591 | writer.appendUint32(uint32(compressed)) 592 | } else { 593 | compressed := (N << 2) | 3 594 | writer.appendUint64(big.NewInt(int64(compressed))) 595 | } 596 | } 597 | 598 | func ReadFromBuffer(buffer []byte) (interface{}, error) { 599 | beve := Beve{buffer: buffer, cursor: 0} 600 | return beve.readValue() 601 | } 602 | 603 | func WriteToBuffer(data interface{}) ([]byte, error) { 604 | writer := NewWriter(0) 605 | err := writeValue(writer, data) 606 | if err != nil { 607 | return nil, err 608 | } 609 | return writer.buffer[:writer.offset], nil 610 | } 611 | 612 | func ReadFromFile(filename string) (interface{}, error) { 613 | buffer, err := os.ReadFile(filename) 614 | if err != nil { 615 | return nil, err 616 | } 617 | return ReadFromBuffer(buffer) 618 | } 619 | 620 | func WriteToFile(filename string, data interface{}) error { 621 | buffer, err := WriteToBuffer(data) 622 | if err != nil { 623 | return err 624 | } 625 | return os.WriteFile(filename, buffer, 0644) 626 | } 627 | 628 | // TODO toJSON,fromJSON 629 | 630 | func main() { 631 | // ... initialize the buffer 632 | buffer := []byte{0x81, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 633 | beve := Beve{buffer: buffer, cursor: 0} 634 | value, err := beve.readValue() 635 | if err != nil { 636 | fmt.Println("Error reading value:", err) 637 | return 638 | } 639 | fmt.Println("Decoded value:", value) 640 | } 641 | -------------------------------------------------------------------------------- /javascript/beve.test.js: -------------------------------------------------------------------------------- 1 | const beve = require('./beve.js'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | // Helper to get actual buffer from over-allocated write buffer 6 | function getActualBuffer(encoded) { 7 | // Find the last non-zero byte (accounting for legitimate zero data) 8 | // For a more reliable approach, we encode and decode in one step 9 | return encoded; 10 | } 11 | 12 | // Helper to round-trip test: encode then decode 13 | function roundTrip(value) { 14 | const encoded = beve.write_beve(value); 15 | // Find actual buffer size by trimming trailing zeros 16 | // This is tricky because zeros can be valid data 17 | // Instead, we'll rely on the decoder to stop at the right place 18 | let size = encoded.length; 19 | for (let i = encoded.length - 1; i >= 0; i--) { 20 | if (encoded[i] !== 0) { 21 | size = i + 1; 22 | break; 23 | } 24 | } 25 | const buffer = new Uint8Array(encoded.buffer, 0, size); 26 | return beve.read_beve(buffer); 27 | } 28 | 29 | describe('BEVE JavaScript Library', () => { 30 | 31 | describe('Primitive Types', () => { 32 | 33 | test('boolean true', () => { 34 | expect(roundTrip(true)).toBe(true); 35 | }); 36 | 37 | test('boolean false', () => { 38 | expect(roundTrip(false)).toBe(false); 39 | }); 40 | 41 | test('integer zero', () => { 42 | expect(roundTrip(0)).toBe(0); 43 | }); 44 | 45 | test('positive integer', () => { 46 | expect(roundTrip(42)).toBe(42); 47 | }); 48 | 49 | test('negative integer', () => { 50 | expect(roundTrip(-42)).toBe(-42); 51 | }); 52 | 53 | test('large positive integer', () => { 54 | expect(roundTrip(1000000)).toBe(1000000); 55 | }); 56 | 57 | test('large negative integer', () => { 58 | expect(roundTrip(-1000000)).toBe(-1000000); 59 | }); 60 | 61 | test('floating point number', () => { 62 | expect(roundTrip(3.14159)).toBeCloseTo(3.14159); 63 | }); 64 | 65 | test('negative floating point', () => { 66 | expect(roundTrip(-2.71828)).toBeCloseTo(-2.71828); 67 | }); 68 | 69 | test('very small floating point', () => { 70 | expect(roundTrip(0.000001)).toBeCloseTo(0.000001); 71 | }); 72 | 73 | test('large floating point', () => { 74 | // Use a clear non-integer to ensure it's encoded as float64 75 | expect(roundTrip(1234567890.123456)).toBeCloseTo(1234567890.123456); 76 | }); 77 | }); 78 | 79 | describe('Null and Undefined Handling (Issue #7)', () => { 80 | 81 | test('null value', () => { 82 | expect(roundTrip(null)).toBe(null); 83 | }); 84 | 85 | test('undefined converts to null', () => { 86 | expect(roundTrip(undefined)).toBe(null); 87 | }); 88 | 89 | test('object with null value', () => { 90 | const input = { value: null }; 91 | expect(roundTrip(input)).toEqual(input); 92 | }); 93 | 94 | test('object with undefined value is omitted', () => { 95 | const input = { defined: 'yes', notDefined: undefined }; 96 | const expected = { defined: 'yes' }; 97 | expect(roundTrip(input)).toEqual(expected); 98 | }); 99 | 100 | test('object with only undefined values becomes empty', () => { 101 | const input = { a: undefined, b: undefined }; 102 | expect(roundTrip(input)).toEqual({}); 103 | }); 104 | 105 | test('array with null elements', () => { 106 | const input = [1, null, 3]; 107 | expect(roundTrip(input)).toEqual([1, null, 3]); 108 | }); 109 | 110 | test('array with undefined elements converts to null', () => { 111 | const input = [1, undefined, 3]; 112 | const expected = [1, null, 3]; 113 | expect(roundTrip(input)).toEqual(expected); 114 | }); 115 | 116 | test('nested object with null', () => { 117 | const input = { outer: { inner: null } }; 118 | expect(roundTrip(input)).toEqual(input); 119 | }); 120 | 121 | test('mixed null and undefined in object', () => { 122 | const input = { 123 | present: 'value', 124 | isNull: null, 125 | isUndefined: undefined 126 | }; 127 | const expected = { 128 | present: 'value', 129 | isNull: null 130 | }; 131 | expect(roundTrip(input)).toEqual(expected); 132 | }); 133 | }); 134 | 135 | describe('String Handling', () => { 136 | 137 | test('empty string', () => { 138 | expect(roundTrip('')).toBe(''); 139 | }); 140 | 141 | test('single character', () => { 142 | expect(roundTrip('a')).toBe('a'); 143 | }); 144 | 145 | test('short string', () => { 146 | expect(roundTrip('hello')).toBe('hello'); 147 | }); 148 | 149 | test('string with spaces', () => { 150 | expect(roundTrip('hello world')).toBe('hello world'); 151 | }); 152 | 153 | test('string with special characters', () => { 154 | expect(roundTrip('hello\nworld\ttab')).toBe('hello\nworld\ttab'); 155 | }); 156 | }); 157 | 158 | describe('String Length Boundaries (Issue #9)', () => { 159 | 160 | test('62-character string (boundary - 2)', () => { 161 | const input = 'A'.repeat(62); 162 | expect(roundTrip(input)).toBe(input); 163 | }); 164 | 165 | test('63-character string (boundary - 1)', () => { 166 | const input = 'A'.repeat(63); 167 | expect(roundTrip(input)).toBe(input); 168 | }); 169 | 170 | test('64-character string (boundary)', () => { 171 | const input = 'A'.repeat(64); 172 | expect(roundTrip(input)).toBe(input); 173 | }); 174 | 175 | test('65-character string (boundary + 1)', () => { 176 | const input = 'A'.repeat(65); 177 | expect(roundTrip(input)).toBe(input); 178 | }); 179 | 180 | test('100-character string', () => { 181 | const input = 'A'.repeat(100); 182 | expect(roundTrip(input)).toBe(input); 183 | }); 184 | 185 | test('127-character string (from bug report)', () => { 186 | const input = 'A'.repeat(127); 187 | expect(roundTrip(input)).toBe(input); 188 | }); 189 | 190 | test('500-character string', () => { 191 | const input = 'A'.repeat(500); 192 | expect(roundTrip(input)).toBe(input); 193 | }); 194 | 195 | test('1000-character string', () => { 196 | const input = 'A'.repeat(1000); 197 | expect(roundTrip(input)).toBe(input); 198 | }); 199 | 200 | test('16383-character string (4-byte boundary - 1)', () => { 201 | const input = 'A'.repeat(16383); 202 | expect(roundTrip(input)).toBe(input); 203 | }); 204 | 205 | test('16384-character string (4-byte boundary)', () => { 206 | const input = 'A'.repeat(16384); 207 | expect(roundTrip(input)).toBe(input); 208 | }); 209 | }); 210 | 211 | describe('UTF-8 Multi-byte Characters', () => { 212 | 213 | test('string with emoji (4-byte UTF-8)', () => { 214 | const input = 'Hello 😀 World'; 215 | expect(roundTrip(input)).toBe(input); 216 | }); 217 | 218 | test('emoji-only string', () => { 219 | const input = '😀😁😂🤣😃😄😅😆'; 220 | expect(roundTrip(input)).toBe(input); 221 | }); 222 | 223 | test('20 emojis (80 UTF-8 bytes, triggers 2-byte compressed)', () => { 224 | const input = '😀'.repeat(20); 225 | expect(roundTrip(input)).toBe(input); 226 | }); 227 | 228 | test('Chinese characters (3-byte UTF-8)', () => { 229 | const input = '你好世界'; 230 | expect(roundTrip(input)).toBe(input); 231 | }); 232 | 233 | test('mixed ASCII and multi-byte', () => { 234 | const input = 'Hello 你好 World 🌍'; 235 | expect(roundTrip(input)).toBe(input); 236 | }); 237 | 238 | test('string where char count != byte count at boundary', () => { 239 | // 21 characters but 63 bytes (21 * 3 for Chinese) 240 | const input = '中'.repeat(21); 241 | expect(roundTrip(input)).toBe(input); 242 | }); 243 | 244 | test('string where char count < 64 but byte count >= 64', () => { 245 | // 22 Chinese characters = 66 bytes, triggers 2-byte compressed 246 | const input = '中'.repeat(22); 247 | expect(roundTrip(input)).toBe(input); 248 | }); 249 | }); 250 | 251 | describe('Objects', () => { 252 | 253 | test('empty object', () => { 254 | expect(roundTrip({})).toEqual({}); 255 | }); 256 | 257 | test('simple object with string value', () => { 258 | const input = { name: 'test' }; 259 | expect(roundTrip(input)).toEqual(input); 260 | }); 261 | 262 | test('object with number value', () => { 263 | const input = { count: 42 }; 264 | expect(roundTrip(input)).toEqual(input); 265 | }); 266 | 267 | test('object with boolean value', () => { 268 | const input = { active: true }; 269 | expect(roundTrip(input)).toEqual(input); 270 | }); 271 | 272 | test('object with multiple fields', () => { 273 | const input = { name: 'test', count: 42, active: true }; 274 | expect(roundTrip(input)).toEqual(input); 275 | }); 276 | 277 | test('nested object', () => { 278 | const input = { 279 | outer: { 280 | inner: 'value' 281 | } 282 | }; 283 | expect(roundTrip(input)).toEqual(input); 284 | }); 285 | 286 | test('deeply nested object', () => { 287 | const input = { 288 | a: { 289 | b: { 290 | c: { 291 | d: 'deep' 292 | } 293 | } 294 | } 295 | }; 296 | expect(roundTrip(input)).toEqual(input); 297 | }); 298 | }); 299 | 300 | describe('Object Key Boundaries', () => { 301 | 302 | test('object with 63-char key', () => { 303 | const key = 'k'.repeat(63); 304 | const input = {}; 305 | input[key] = 'value'; 306 | expect(roundTrip(input)).toEqual(input); 307 | }); 308 | 309 | test('object with 64-char key', () => { 310 | const key = 'k'.repeat(64); 311 | const input = {}; 312 | input[key] = 'value'; 313 | expect(roundTrip(input)).toEqual(input); 314 | }); 315 | 316 | test('object with 65-char key', () => { 317 | const key = 'k'.repeat(65); 318 | const input = {}; 319 | input[key] = 'value'; 320 | expect(roundTrip(input)).toEqual(input); 321 | }); 322 | 323 | test('object with UTF-8 key', () => { 324 | const input = { '你好': 'world' }; 325 | expect(roundTrip(input)).toEqual(input); 326 | }); 327 | 328 | test('object with emoji key', () => { 329 | const input = { '😀': 'smile' }; 330 | expect(roundTrip(input)).toEqual(input); 331 | }); 332 | }); 333 | 334 | describe('Objects with Long String Values (Issue #9 Scenario)', () => { 335 | 336 | test('object with 64-char string field', () => { 337 | const input = { content: 'A'.repeat(64) }; 338 | expect(roundTrip(input)).toEqual(input); 339 | }); 340 | 341 | test('object with 127-char string field (bug report case)', () => { 342 | const input = { content: 'A'.repeat(127) }; 343 | expect(roundTrip(input)).toEqual(input); 344 | }); 345 | 346 | test('object with multiple long string fields', () => { 347 | const input = { 348 | field1: 'A'.repeat(100), 349 | field2: 'B'.repeat(100), 350 | field3: 'C'.repeat(100) 351 | }; 352 | expect(roundTrip(input)).toEqual(input); 353 | }); 354 | 355 | test('mixed content like bug report', () => { 356 | const input = { 357 | content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', 358 | createdAt: 'Sun Jan 14 2024', 359 | id: 12345 360 | }; 361 | expect(roundTrip(input)).toEqual(input); 362 | }); 363 | }); 364 | 365 | describe('Typed Arrays', () => { 366 | 367 | test('integer array', () => { 368 | const input = [1, 2, 3, 4, 5]; 369 | expect(roundTrip(input)).toEqual(input); 370 | }); 371 | 372 | test('float array', () => { 373 | const input = [1.1, 2.2, 3.3, 4.4, 5.5]; 374 | const result = roundTrip(input); 375 | for (let i = 0; i < input.length; i++) { 376 | expect(result[i]).toBeCloseTo(input[i]); 377 | } 378 | }); 379 | 380 | test('large integer array', () => { 381 | const input = Array.from({ length: 100 }, (_, i) => i); 382 | expect(roundTrip(input)).toEqual(input); 383 | }); 384 | 385 | test('array with negative integers', () => { 386 | const input = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]; 387 | expect(roundTrip(input)).toEqual(input); 388 | }); 389 | }); 390 | 391 | describe('Untyped Arrays', () => { 392 | 393 | test('single element array', () => { 394 | const input = ['single']; 395 | expect(roundTrip(input)).toEqual(input); 396 | }); 397 | 398 | test('mixed type array', () => { 399 | const input = ['string', 42, true, false]; 400 | expect(roundTrip(input)).toEqual(input); 401 | }); 402 | 403 | test('array of objects', () => { 404 | const input = [{ a: 1 }, { b: 2 }]; 405 | expect(roundTrip(input)).toEqual(input); 406 | }); 407 | 408 | test('nested arrays', () => { 409 | // Note: nested arrays may be treated as typed if inner arrays are numeric 410 | const input = [['a', 'b'], ['c', 'd']]; 411 | expect(roundTrip(input)).toEqual(input); 412 | }); 413 | }); 414 | 415 | describe('Edge Cases', () => { 416 | 417 | test('object with empty string value', () => { 418 | const input = { empty: '' }; 419 | expect(roundTrip(input)).toEqual(input); 420 | }); 421 | 422 | test('object with zero value', () => { 423 | const input = { zero: 0 }; 424 | expect(roundTrip(input)).toEqual(input); 425 | }); 426 | 427 | test('object with false value', () => { 428 | const input = { flag: false }; 429 | expect(roundTrip(input)).toEqual(input); 430 | }); 431 | 432 | test('string with null character', () => { 433 | const input = 'hello\0world'; 434 | expect(roundTrip(input)).toBe(input); 435 | }); 436 | 437 | test('unicode string with surrogate pairs', () => { 438 | const input = '𝟙𝟚𝟛'; // Mathematical bold digits (4-byte UTF-8 each) 439 | expect(roundTrip(input)).toBe(input); 440 | }); 441 | }); 442 | 443 | describe('Compatibility Tests with Example Files', () => { 444 | const examplesDir = path.join(__dirname, '..', 'examples'); 445 | 446 | test('general_object.beve can be read', () => { 447 | const filePath = path.join(examplesDir, 'general_object.beve'); 448 | if (fs.existsSync(filePath)) { 449 | const buffer = fs.readFileSync(filePath); 450 | const data = beve.read_beve(new Uint8Array(buffer)); 451 | expect(data).toBeDefined(); 452 | expect(typeof data).toBe('object'); 453 | } 454 | }); 455 | 456 | test('nested_object.beve can be read', () => { 457 | const filePath = path.join(examplesDir, 'nested_object.beve'); 458 | if (fs.existsSync(filePath)) { 459 | const buffer = fs.readFileSync(filePath); 460 | const data = beve.read_beve(new Uint8Array(buffer)); 461 | expect(data).toBeDefined(); 462 | expect(typeof data).toBe('object'); 463 | } 464 | }); 465 | 466 | test('float32_array.beve can be read', () => { 467 | const filePath = path.join(examplesDir, 'float32_array.beve'); 468 | if (fs.existsSync(filePath)) { 469 | const buffer = fs.readFileSync(filePath); 470 | const data = beve.read_beve(new Uint8Array(buffer)); 471 | expect(data).toBeDefined(); 472 | expect(data.values).toBeDefined(); 473 | expect(Array.isArray(data.values)).toBe(true); 474 | } 475 | }); 476 | 477 | test('float64_array.beve can be read', () => { 478 | const filePath = path.join(examplesDir, 'float64_array.beve'); 479 | if (fs.existsSync(filePath)) { 480 | const buffer = fs.readFileSync(filePath); 481 | const data = beve.read_beve(new Uint8Array(buffer)); 482 | expect(data).toBeDefined(); 483 | expect(data.values).toBeDefined(); 484 | expect(Array.isArray(data.values)).toBe(true); 485 | } 486 | }); 487 | 488 | test('strings_array.beve can be read', () => { 489 | const filePath = path.join(examplesDir, 'strings_array.beve'); 490 | if (fs.existsSync(filePath)) { 491 | const buffer = fs.readFileSync(filePath); 492 | const data = beve.read_beve(new Uint8Array(buffer)); 493 | expect(data).toBeDefined(); 494 | expect(data.values).toBeDefined(); 495 | expect(Array.isArray(data.values)).toBe(true); 496 | expect(data.values).toEqual(['cat', 'dog', 'elephant']); 497 | } 498 | }); 499 | 500 | test('uint16_array.beve can be read', () => { 501 | const filePath = path.join(examplesDir, 'uint16_array.beve'); 502 | if (fs.existsSync(filePath)) { 503 | const buffer = fs.readFileSync(filePath); 504 | const data = beve.read_beve(new Uint8Array(buffer)); 505 | expect(data).toBeDefined(); 506 | expect(data.values).toBeDefined(); 507 | expect(Array.isArray(data.values)).toBe(true); 508 | } 509 | }); 510 | }); 511 | 512 | describe('Regression Tests', () => { 513 | 514 | test('Issue #9: 64+ char string in object does not corrupt other fields', () => { 515 | const input = { 516 | content: 'A'.repeat(127), 517 | name: 'test', 518 | count: 42 519 | }; 520 | const result = roundTrip(input); 521 | expect(result.content).toBe(input.content); 522 | expect(result.name).toBe(input.name); 523 | expect(result.count).toBe(input.count); 524 | }); 525 | 526 | test('Issue #9: no spurious empty key appears', () => { 527 | const input = { 528 | content: 'A'.repeat(127), 529 | name: 'test' 530 | }; 531 | const result = roundTrip(input); 532 | expect(Object.keys(result)).toEqual(Object.keys(input)); 533 | expect(result['']).toBeUndefined(); 534 | }); 535 | 536 | test('Issue #9: string content is not truncated or corrupted', () => { 537 | const content = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.'; 538 | const input = { content }; 539 | const result = roundTrip(input); 540 | expect(result.content.length).toBe(content.length); 541 | expect(result.content).toBe(content); 542 | }); 543 | }); 544 | }); 545 | -------------------------------------------------------------------------------- /javascript/beve_file.js: -------------------------------------------------------------------------------- 1 | // Reference: https://github.com/stephenberry/beve 2 | 3 | const fs = require('fs'); 4 | 5 | // Reading BEVE 6 | function read_beve(filename) { 7 | // Open dialog box if filename isn't provided. 8 | if (!filename) { 9 | throw new Error('No filename provided.'); 10 | } 11 | 12 | const fid = fs.openSync(filename, 'r'); 13 | if (!fid) { 14 | throw new Error('Failed to open file'); 15 | } 16 | 17 | const data = read_value(fid); 18 | 19 | fs.closeSync(fid); 20 | return data; 21 | } 22 | 23 | function read_value(fid) { 24 | const header = Buffer.alloc(1); 25 | fs.readSync(fid, header, 0, 1, null); 26 | 27 | const config = [1, 2, 4, 8]; 28 | 29 | const type = header[0] & 0b00000111; 30 | 31 | switch (type) { 32 | case 0: // null or boolean 33 | { 34 | const is_bool = (header[0] & 0b00001000) >> 3; 35 | if (is_bool) { 36 | return Boolean((header[0] & 0b11110000) >> 4); 37 | } else { 38 | return null; 39 | } 40 | } 41 | case 1: // number 42 | { 43 | const num_type = (header[0] & 0b00011000) >> 3; 44 | const is_float = num_type === 0; 45 | const is_signed = num_type === 1; 46 | 47 | const byte_count_index = (header[0] & 0b11100000) >> 5; 48 | const byte_count = config[byte_count_index]; 49 | 50 | if (is_float) { 51 | switch (byte_count) { 52 | case 4: 53 | return readFloat(fid); 54 | case 8: 55 | return readDouble(fid); 56 | } 57 | } else { 58 | if (is_signed) { 59 | switch (byte_count) { 60 | case 1: 61 | return readInt8(fid); 62 | case 2: 63 | return readInt16(fid); 64 | case 4: 65 | return readInt32(fid); 66 | case 8: 67 | return readBigInt64(fid); 68 | } 69 | } else { 70 | switch (byte_count) { 71 | case 1: 72 | return readUInt8(fid); 73 | case 2: 74 | return readUInt16(fid); 75 | case 4: 76 | return readUInt32(fid); 77 | case 8: 78 | return readBigUInt64(fid); 79 | } 80 | } 81 | } 82 | break; 83 | } 84 | case 2: // string 85 | { 86 | const size = read_compressed(fid); 87 | const buffer = Buffer.alloc(size); 88 | fs.readSync(fid, buffer, 0, size, null); 89 | return buffer.toString('utf8'); 90 | } 91 | case 3: // object 92 | { 93 | const key_type = (header[0] & 0b00011000) >> 3; 94 | const is_string = key_type === 0; 95 | const is_signed = key_type === 1; 96 | 97 | const byte_count_index = (header[0] & 0b11100000) >> 5; 98 | const byte_count = config[byte_count_index]; 99 | 100 | const N = read_compressed(fid); 101 | const objectData = {}; 102 | 103 | for (let i = 0; i < N; ++i) { 104 | if (is_string) { 105 | const size = read_compressed(fid); 106 | const buffer = Buffer.alloc(size); 107 | fs.readSync(fid, buffer, 0, size, null); 108 | const key = buffer.toString('utf8'); 109 | objectData[key] = read_value(fid); 110 | } else { 111 | throw new Error('TODO: support integer keys'); 112 | } 113 | } 114 | 115 | return objectData; 116 | } 117 | case 4: // typed array 118 | { 119 | const num_type = (header[0] & 0b00011000) >> 3; 120 | const is_float = num_type === 0; 121 | const is_signed = num_type === 1; 122 | 123 | const byte_count_index_array = (header[0] & 0b11100000) >> 5; 124 | const byte_count_array = config[byte_count_index_array]; 125 | 126 | if (num_type === 3) { 127 | const is_string = (header[0] & 0b00100000) >> 5; 128 | if (is_string) { 129 | const N = read_compressed(fid); 130 | const array = new Array(N); 131 | for (let i = 0; i < N; ++i) { 132 | const size = read_compressed(fid); 133 | const buffer = Buffer.alloc(size); 134 | fs.readSync(fid, buffer, 0, size, null); 135 | array[i] = buffer.toString('utf8'); 136 | } 137 | return array; 138 | } 139 | else { 140 | throw new Error("Boolean array support not implemented"); 141 | } 142 | } else if (is_float) { 143 | const N = read_compressed(fid); 144 | const array = new Array(N); 145 | switch (byte_count_array) { 146 | case 4: 147 | for (let i = 0; i < N; ++i) { 148 | array[i] = readFloat(fid); 149 | } 150 | break; 151 | case 8: 152 | for (let i = 0; i < N; ++i) { 153 | array[i] = readDouble(fid); 154 | } 155 | break; 156 | } 157 | return array; 158 | } else { 159 | const N = read_compressed(fid); 160 | const array = new Array(N); 161 | 162 | if (is_signed) { 163 | switch (byte_count_array) { 164 | case 1: 165 | for (let i = 0; i < N; ++i) { 166 | array[i] = readInt8(fid); 167 | } 168 | break; 169 | case 2: 170 | for (let i = 0; i < N; ++i) { 171 | array[i] = readInt16(fid); 172 | } 173 | break; 174 | case 4: 175 | for (let i = 0; i < N; ++i) { 176 | array[i] = readInt32(fid); 177 | } 178 | break; 179 | case 8: 180 | for (let i = 0; i < N; ++i) { 181 | array[i] = readBigInt64(fid); 182 | } 183 | break; 184 | } 185 | } else { 186 | switch (byte_count_array) { 187 | case 1: 188 | for (let i = 0; i < N; ++i) { 189 | array[i] = readUInt8(fid); 190 | } 191 | break; 192 | case 2: 193 | for (let i = 0; i < N; ++i) { 194 | array[i] = readUInt16(fid); 195 | } 196 | break; 197 | case 4: 198 | for (let i = 0; i < N; ++i) { 199 | array[i] = readUInt32(fid); 200 | } 201 | break; 202 | case 8: 203 | for (let i = 0; i < N; ++i) { 204 | array[i] = readBigUInt64(fid); 205 | } 206 | break; 207 | } 208 | } 209 | return array; 210 | } 211 | } 212 | case 5: // untyped array 213 | { 214 | const N = read_compressed(fid); 215 | const unarray = new Array(N); 216 | 217 | for (let i = 0; i < N; ++i) { 218 | unarray[i] = read_value(fid); 219 | } 220 | 221 | return unarray; 222 | } 223 | case 6: // extensions 224 | { 225 | const extension = (header[0] & 0b11111000) >> 3; 226 | switch (extension) { 227 | case 1: // variants 228 | read_compressed(fid); // Skipping variant tag 229 | return read_value(fid); 230 | case 2: // matrices 231 | const layout = fs.readSync(fid, Buffer.alloc(1), 0, 1, null)[0] & 0b00000001; 232 | switch (layout) { 233 | case 0: // row major 234 | throw new Error('TODO: add row major support'); 235 | case 1: // column major 236 | const extents = read_value(fid); 237 | const matrix_data = read_value(fid); 238 | return reshape(matrix_data, extents[0], extents[1]); 239 | default: 240 | throw new Error('Unsupported layout'); 241 | } 242 | case 3: // complex numbers 243 | return read_complex_ext(fid); 244 | default: 245 | throw new Error('Unsupported extension'); 246 | } 247 | } 248 | default: 249 | throw new Error('Unsupported type'); 250 | } 251 | } 252 | 253 | function readFloat(fid) { 254 | const buffer = Buffer.alloc(4); 255 | fs.readSync(fid, buffer, 0, 4, null); 256 | return buffer.readFloatLE(); 257 | } 258 | 259 | function readDouble(fid) { 260 | const buffer = Buffer.alloc(8); 261 | fs.readSync(fid, buffer, 0, 8, null); 262 | return buffer.readDoubleLE(); 263 | } 264 | 265 | function readInt8(fid) { 266 | const buffer = Buffer.alloc(1); 267 | fs.readSync(fid, buffer, 0, 1, null); 268 | return buffer.readInt8(); 269 | } 270 | 271 | function readInt16(fid) { 272 | const buffer = Buffer.alloc(2); 273 | fs.readSync(fid, buffer, 0, 2, null); 274 | return buffer.readInt16LE(); 275 | } 276 | 277 | function readInt32(fid) { 278 | const buffer = Buffer.alloc(4); 279 | fs.readSync(fid, buffer, 0, 4, null); 280 | return buffer.readInt32LE(); 281 | } 282 | 283 | function readBigInt64(fid) { 284 | const buffer = Buffer.alloc(8); 285 | fs.readSync(fid, buffer, 0, 8, null); 286 | return buffer.readBigInt64LE(); 287 | } 288 | 289 | function readUInt8(fid) { 290 | const buffer = Buffer.alloc(1); 291 | fs.readSync(fid, buffer, 0, 1, null); 292 | return buffer.readUInt8(); 293 | } 294 | 295 | function readUInt16(fid) { 296 | const buffer = Buffer.alloc(2); 297 | fs.readSync(fid, buffer, 0, 2, null); 298 | return buffer.readUInt16LE(); 299 | } 300 | 301 | function readUInt32(fid) { 302 | const buffer = Buffer.alloc(4); 303 | fs.readSync(fid, buffer, 0, 4, null); 304 | return buffer.readUInt32LE(); 305 | } 306 | 307 | function readBigUInt64(fid) { 308 | const buffer = Buffer.alloc(8); 309 | fs.readSync(fid, buffer, 0, 8, null); 310 | return buffer.readBigUInt64LE(); 311 | } 312 | 313 | const byte_count_lookup = [1, 2, 4, 8, 16, 32, 64, 128]; 314 | 315 | function read_compressed(fid) { 316 | let header = Buffer.alloc(1); 317 | fs.readSync(fid, header, 0, 1, null); 318 | const config = header[0] & 0b00000011; 319 | 320 | switch (config) { 321 | case 0: 322 | return header[0] >> 2; 323 | case 1: { 324 | // Read 1 more byte, combine with header for 2-byte value 325 | const extra = Buffer.alloc(1); 326 | fs.readSync(fid, extra, 0, 1, null); 327 | const value = header[0] | (extra[0] << 8); 328 | return value >> 2; 329 | } 330 | case 2: { 331 | // Read 3 more bytes, combine with header for 4-byte value 332 | const extra = Buffer.alloc(3); 333 | fs.readSync(fid, extra, 0, 3, null); 334 | const value = header[0] | (extra[0] << 8) | (extra[1] << 16) | (extra[2] << 24); 335 | return value >>> 2; // Use >>> for unsigned right shift 336 | } 337 | case 3: { 338 | // Read 7 more bytes, combine with header for 8-byte value 339 | const extra = Buffer.alloc(7); 340 | fs.readSync(fid, extra, 0, 7, null); 341 | let val = BigInt(header[0]); 342 | for (let i = 0; i < 7; ++i) { 343 | val |= BigInt(extra[i]) << BigInt(8 * (i + 1)); 344 | } 345 | return Number(val >> BigInt(2)); 346 | } 347 | default: 348 | return 0; 349 | } 350 | } 351 | 352 | function read_complex_ext(fid) { 353 | // Read complex header 354 | const chBuf = Buffer.alloc(1); 355 | fs.readSync(fid, chBuf, 0, 1, null); 356 | const ch = chBuf[0]; 357 | const kind = ch & 0b00000111; // 0: single complex, 1: complex array 358 | const num_type = (ch & 0b00011000) >> 3; // 0 float, 1 signed, 2 unsigned 359 | const bc_idx = (ch & 0b11100000) >> 5; // byte count index 360 | const bc_map = [1, 2, 4, 8]; 361 | const byte_count = bc_map[bc_idx]; 362 | 363 | function read_num() { 364 | if (num_type === 0) { 365 | if (byte_count === 4) return readFloat(fid); 366 | if (byte_count === 8) return readDouble(fid); 367 | } else if (num_type === 1) { 368 | if (byte_count === 1) return readInt8(fid); 369 | if (byte_count === 2) return readInt16(fid); 370 | if (byte_count === 4) return readInt32(fid); 371 | if (byte_count === 8) return readBigInt64(fid); 372 | } else if (num_type === 2) { 373 | if (byte_count === 1) return readUInt8(fid); 374 | if (byte_count === 2) return readUInt16(fid); 375 | if (byte_count === 4) return readUInt32(fid); 376 | if (byte_count === 8) return readBigUInt64(fid); 377 | } 378 | throw new Error('Unsupported complex number type'); 379 | } 380 | 381 | if (kind === 0) { 382 | const real = read_num(); 383 | const imag = read_num(); 384 | return [real, imag]; 385 | } else if (kind === 1) { 386 | const N = read_compressed(fid); 387 | const re = new Array(N); 388 | const im = new Array(N); 389 | for (let i = 0; i < N; ++i) re[i] = read_num(); 390 | for (let i = 0; i < N; ++i) im[i] = read_num(); 391 | return [re, im]; 392 | } else { 393 | throw new Error('Unsupported complex kind'); 394 | } 395 | } 396 | 397 | // Writing BEVE 398 | function write_beve(data, filename) { 399 | const file = new BinaryFile(filename); 400 | if (!file) { 401 | throw new Error('Failed to open file for writing'); 402 | } 403 | try { 404 | write_value(file, data); 405 | } finally { 406 | file.close(); 407 | } 408 | } 409 | 410 | function write_value(file, value) { 411 | if (Array.isArray(value) && value.length > 1) { 412 | const header = 4; 413 | if (typeof value[0] === 'number' && !Number.isInteger(value[0])) { 414 | write_float(file, header, value, 1); 415 | } else { 416 | write_integer(file, header, value, 1); 417 | } 418 | } else if (typeof value === 'boolean') { 419 | let header = 0; 420 | if (value) { 421 | header |= 0b00011000; 422 | } else { 423 | header |= 0b00001000; 424 | } 425 | file.writeUint8(header); 426 | } else if (typeof value === 'number') { 427 | let header = 1; 428 | if (!Number.isInteger(value)) { 429 | write_float(file, header, value, 0); 430 | } else { 431 | write_integer(file, header, value, 0); 432 | } 433 | } else if (typeof value === 'string') { 434 | throw new Error('Unsupported data type'); 435 | } else if (typeof value === 'object' && Object.keys(value).length > 0) { 436 | let header = 3; 437 | let keyType = 0; // Assuming keys are always strings 438 | let isSigned = false; 439 | header |= keyType << 3; 440 | header |= isSigned << 5; 441 | file.writeUint8(header); 442 | writeCompressed(file, Object.keys(value).length); 443 | for (const key in value) { 444 | writeCompressed(file, Buffer.byteLength(key, 'utf8')); 445 | file.writeString(key); 446 | write_value(file, value[key]); 447 | } 448 | } else if (Array.isArray(value)) { 449 | let header = 5; 450 | file.writeUint8(header); 451 | writeCompressed(file, value.length); 452 | for (let i = 0; i < value.length; i++) { 453 | write_value(file, value[i]); 454 | } 455 | } else { 456 | throw new Error('Unsupported data type'); 457 | } 458 | } 459 | 460 | function write_float(file, header, value, isArray) { 461 | if (Math.fround(value) === value) { // single precision float 462 | header |= 0b01000000; 463 | file.writeUint8(header); 464 | if (isArray) { 465 | writeCompressed(file, value.length); 466 | } 467 | file.write_float32LE(value); 468 | } else { // double precision float 469 | header |= 0b01100000; 470 | file.writeUint8(header); 471 | if (isArray) { 472 | writeCompressed(file, value.length); 473 | } 474 | file.write_float64LE(value); 475 | } 476 | } 477 | 478 | function write_integer(file, header, value, isArray) { 479 | if (value >= 0 && value <= 255) { // uint8 480 | header |= 0b00010001; 481 | file.writeUint8(header); 482 | if (isArray) { 483 | writeCompressed(file, value.length); 484 | } 485 | file.writeUint8(value); 486 | } else if (value >= 0 && value <= 65535) { // uint16 487 | header |= 0b00110001; 488 | file.writeUint8(header); 489 | if (isArray) { 490 | writeCompressed(file, value.length); 491 | } 492 | file.writeUint16LE(value); 493 | } else if (value >= 0 && value <= 4294967295) { // uint32 494 | header |= 0b01010001; 495 | file.writeUint8(header); 496 | if (isArray) { 497 | writeCompressed(file, value.length); 498 | } 499 | file.writeUint32LE(value); 500 | } else if (value >= 0 && value <= Number.MAX_SAFE_INTEGER) { // uint64 501 | header |= 0b01110001; 502 | file.writeUint8(header); 503 | if (isArray) { 504 | writeCompressed(file, value.length); 505 | } 506 | file.writeBigInt64LE(BigInt(value)); 507 | } else if (value >= -128 && value <= 127) { // int8 508 | header |= 0b00001001; 509 | file.writeUint8(header); 510 | if (isArray) { 511 | writeCompressed(file, value.length); 512 | } 513 | file.writeInt8(value); 514 | } else if (value >= -32768 && value <= 32767) { // int16 515 | header |= 0b00101001; 516 | file.writeUint8(header); 517 | if (isArray) { 518 | writeCompressed(file, value.length); 519 | } 520 | } else if (value >= -2147483648 && value <= 2147483647) { // int32 521 | header |= 0b01001001; 522 | file.writeUint8(header); 523 | if (isArray) { 524 | writeCompressed(file, value.length); 525 | } 526 | file.writeInt32LE(value); 527 | } else if (value >= -9223372036854775808 && value <= 9223372036854775807) { // int64 528 | header |= 0b01101001; 529 | file.writeUint8(header); 530 | if (isArray) { 531 | writeCompressed(file, value.length); 532 | } 533 | file.writeBigInt64LE(BigInt(value)); 534 | } else { 535 | throw new Error('Unsupported type'); 536 | } 537 | } 538 | 539 | function writeCompressed(file, N) { 540 | if (N < 64) { 541 | const compressed = (N << 2) | 0; 542 | file.writeUint8(compressed); 543 | } else if (N < 16384) { 544 | const compressed = (N << 2) | 1; 545 | file.writeUint16LE(compressed); 546 | } else if (N < 1073741824) { 547 | const compressed = (N << 2) | 2; 548 | file.writeUint32LE(compressed); 549 | } else if (N < 4611686018427387904) { 550 | const compressed = (N << 2) | 3; 551 | file.writeUint64LE(BigInt(compressed)); 552 | } 553 | } 554 | 555 | class BinaryFile { 556 | constructor(filename) { 557 | this.filename = filename; 558 | this.fd = require('fs').openSync(filename, 'wb'); 559 | } 560 | writeUint8(value) { 561 | require('fs').writeSync(this.fd, Buffer.from([value])); 562 | } 563 | writeUint16LE(value) { 564 | require('fs').writeSync(this.fd, Buffer.from([value & 0xFF, (value >> 8) & 0xFF])); 565 | } 566 | writeUint32LE(value) { 567 | require('fs').writeSync(this.fd, Buffer.from([(value >> 0) & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF])); 568 | } 569 | writeUint64LE(value) { 570 | require('fs').writeSync(this.fd, Buffer.from([(value >> 0n) & BigInt(0xFF), (value >> 8n) & BigInt(0xFF), (value >> 16n) & BigInt(0xFF), (value >> 24n) & BigInt(0xFF), (value >> 32n) & BigInt(0xFF), (value >> 40n) & BigInt(0xFF), (value >> 48n) & BigInt(0xFF), (value >> 56n) & BigInt(0xFF)])); 571 | } 572 | write_float32LE(value) { 573 | const buffer = Buffer.alloc(4); 574 | buffer.write_floatLE(value, 0); 575 | require('fs').writeSync(this.fd, buffer); 576 | } 577 | write_float64LE(value) { 578 | const buffer = Buffer.alloc(8); 579 | buffer.writeDoubleLE(value, 0); 580 | require('fs').writeSync(this.fd, buffer); 581 | } 582 | writeBigInt64LE(value) { 583 | const buffer = Buffer.alloc(8); 584 | buffer.writeBigInt64LE(value, 0); 585 | require('fs').writeSync(this.fd, buffer); 586 | } 587 | writeString(str) { 588 | require('fs').writeSync(this.fd, Buffer.from(str)); 589 | } 590 | close() { 591 | require('fs').closeSync(this.fd); 592 | } 593 | } 594 | 595 | // Usage example: 596 | try { 597 | const filename = '../examples/general_object.beve'; 598 | const data = read_beve(filename); 599 | console.log(data); 600 | //console.log(JSON.stringify(data)); 601 | //write_beve(data, '../examples/example_from_js.beve'); 602 | } catch (error) { 603 | console.error(error); 604 | } 605 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | BEVE - Binary Efficient Versatile Encoding 8 | 9 | 10 | 11 | 12 | 13 | 14 | 30 | 42 | 43 |
44 |
45 |

BEVE

46 |

Binary Efficient Versatile Encoding

47 |

Version 1.0

48 |

High performance, tagged binary data specification like JSON, MessagePack, CBOR, etc. But, designed for higher performance and scientific computing.

49 | 53 |
54 |
55 | 56 |
57 |
58 |

Overview

59 |
60 |
61 |
62 | 63 |
64 |

Maps to and from JSON

65 |

Full JSON compatibility for easy integration

66 |
67 |
68 |
69 | 70 |
71 |

Schema-less & Fully Described

72 |

Like JSON, can be used in documents without schemas

73 |
74 |
75 |
76 | 77 |
78 |

Little Endian

79 |

Maximum performance on modern CPUs

80 |
81 |
82 |
83 | 84 |
85 |

Extremely Fast

86 |

Designed for SIMD operations

87 |
88 |
89 |
90 | 91 |
92 |

Future Proof

93 |

Supports 128-bit integers and larger types

94 |
95 |
96 |
97 | 98 |
99 |

Scientific Computing

100 |

Brain floats, matrices, and complex numbers

101 |
102 |
103 |
104 |

Note: BEVE is designed to be faster on modern hardware than CBOR, BSON, and MessagePack, but it is also more space efficient for typed arrays.

105 |
106 |
107 | 108 |
109 |

Performance vs MessagePack

110 |

The following table lists the performance increase between BEVE with Glaze versus other libraries and their binary formats.

111 | 112 |
113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 |
TestLibraries (vs Glaze)Read (Times Faster)Write (Times Faster)
Test Objectmsgpack-c (c++)1.9X13X
double arraymsgpack-c (c++)14X50X
float arraymsgpack-c (c++)29X81X
uint16_t arraymsgpack-c (c++)73X167X
149 |
150 | 151 |

Performance test code

152 | 153 |

Binary Message Size Comparison

154 |

The table below shows binary message size versus BEVE. A positive value means the binary produced is larger than BEVE.

155 | 156 |
157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 |
TestLibraries (vs Glaze)Message Size
Test Objectmsgpack-c (c++)-3.4%
double arraymsgpack-c (c++)+12%
float arraymsgpack-c (c++)+25%
uint16_t arraymsgpack-c (c++)+50%
188 |
189 |
190 | 191 |
192 |

Technical Specification

193 | 194 |

File Extension

195 |

The standard extension for BEVE files is .beve

196 | 197 |

MIME Type

198 |

The official media type for BEVE payloads is application/beve. Producers SHOULD advertise Content-Type: application/beve when emitting BEVE binaries, and consumers SHOULD express support via Accept: application/beve. This mirrors the role of application/json for JSON, with the distinction that the payload is encoded using the BEVE specification.

199 | 200 |

Endianness

201 |

The endianness must be little endian.

202 | 203 |

Why Tagged Messages?

204 |

Flexibility and efficiency

205 |

JSON is ubiquitous because it is tagged (has keys), and therefore messages can be sent in part. Furthermore, extending specifications and adding more fields is far easier with tagged messages and unordered mapping. Tags also make the format more human friendly. However, tags are entirely optional, and structs can be serialized as generic arrays.

206 | 207 |

Concerning Compression

208 |

Note that BEVE is not a compression algorithm. It uses some bit packing to be more space efficient, but strings and numerical values see no compression. This means that BEVE binary is very compressible, like JSON, and it is encouraged to use compression algorithms like LZ4, Zstandard, Brotli, etc. where size is critical.

209 | 210 |

Compressed Unsigned Integer

211 |

A compressed unsigned integer uses the first two bits to denote the number of bytes used to express an integer. The rest of the bits indicate the integer value.

212 |
213 |

Wherever all caps SIZE is used in the specification, it refers to a size indicator that uses a compressed unsigned integer.

214 |

SIZE refers to the count of array members, object members, or bytes in a string. It does not refer to the number of raw bytes except for UTF-8 strings.

215 |
216 | 217 |
218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 |
#Number of BytesInteger Value (N)
01N < 64 [2^6]
12N < 16384 [2^14]
24N < 1073741824 [2^30]
38N < 4611686018427387904 [2^62]
249 |
250 | 251 |

Byte Count Indicator

252 |

Wherever all caps BYTE COUNT is used, it describes this mapping:

253 |
#      Number of bytes
254 | 0      1
255 | 1      2
256 | 2      4
257 | 3      8
258 | 4      16
259 | 5      32
260 | 6      64
261 | 7      128
262 | ...
263 | 264 |

Header

265 |

Every VALUE begins with a byte header. Any unspecified bits must be set to zero.

266 |
267 |

Wherever all caps HEADER is used, it describes this header.

268 |
269 |

The first three bits denote types:

270 |
0 -> null or boolean                          0b00000'000
271 | 1 -> number                                   0b00000'001
272 | 2 -> string                                   0b00000'010
273 | 3 -> object                                   0b00000'011
274 | 4 -> typed array                              0b00000'100
275 | 5 -> generic array                            0b00000'101
276 | 6 -> extension                                0b00000'110
277 | 7 -> reserved                                 0b00000'111
278 | 279 |

Nomenclature

280 |
    281 |
  • DATA denotes bytes of data without a HEADER
  • 282 |
  • VALUE denotes a binary structure that begins with a HEADER
  • 283 |
  • SIZE refers to a compressed unsigned integer that denotes a count of array members, object members, or bytes in a string
  • 284 |
285 | 286 |

Type Specifications

287 | 288 |

0 - Null

289 |

Null is simply 0

290 | 291 |

0 - Boolean

292 |

The next bit is set to indicate a boolean. The 5th bit is set to denote true or false.

293 |
false      0b000'01'000
294 | true       0b000'11'000
295 | 296 |

1 - Number

297 |

The next two bits of the HEADER indicates whether the number is floating point, signed integer, or unsigned integer.

298 |

Float point types must conform to the IEEE-754 standard.

299 |
0 -> floating point      0b000'00'001
300 | 1 -> signed integer      0b000'01'001
301 | 2 -> unsigned integer    0b000'10'001
302 | 303 |

The next three bits of the HEADER are used as the BYTE COUNT.

304 |
305 |

Note: brain floats use a byte count indicator of 1, even though they use 2 bytes per value. This is used because float8_t is not supported and not typically useful.

306 |
307 | 308 |
Floating Point Types
309 |
bfloat16_t    0b000'00'001 // brain float
310 | float16_t     0b001'00'001
311 | float32_t     0b010'00'001 // float
312 | float64_t     0b011'00'001 // double
313 | float128_t    0b100'00'001
314 | 315 |
Signed Integer Types
316 |
int8_t        0b000'01'001
317 | int16_t       0b001'01'001
318 | int32_t       0b010'01'001
319 | int64_t       0b011'01'001
320 | int128_t      0b100'01'001
321 | 322 |
Unsigned Integer Types
323 |
uint8_t       0b000'10'001
324 | uint16_t      0b001'10'001
325 | uint32_t      0b010'10'001
326 | uint64_t      0b011'10'001
327 | uint128_t     0b100'10'001
328 | 329 |

2 - Strings

330 |

Strings must be encoded with UTF-8.

331 |

Layout: HEADER | SIZE | DATA

332 | 333 |
Strings as Object Keys or Typed String Arrays
334 |

When strings are used as keys in objects or typed string arrays the HEADER is not included.

335 |

Layout: SIZE | DATA

336 | 337 |

3 - Object

338 |

The next two bits of the HEADER indicates the type of key.

339 |
0 -> string
340 | 1 -> signed integer
341 | 2 -> unsigned integer
342 |

For integer keys the next three bits of the HEADER indicate the BYTE COUNT.

343 |
344 |

An object KEY must not contain a HEADER as the type of the key has already been defined.

345 |
346 |

Layout: HEADER | SIZE | KEY[0] | VALUE[0] | ... KEY[N] | VALUE[N]

347 | 348 |

4 - Typed Array

349 |

The next two bits indicate the type stored in the array:

350 |
0 -> floating point
351 | 1 -> signed integer
352 | 2 -> unsigned integer
353 | 3 -> boolean or string
354 |

For integral and floating point types, the next three bits of the type header are the BYTE COUNT.

355 |

For boolean or string types the next bit indicates whether the type is a boolean or a string:

356 |
0 -> boolean // packed as single bits to the nearest byte
357 | 1 -> string // an array of strings (not an array of characters)
358 |

Layout: HEADER | SIZE | data

359 | 360 |
Boolean Arrays
361 |

Boolean arrays are stored as single bits and packed into consecutive bytes.

362 |
    363 |
  • SIZE is the number of booleans; the payload length is ceil(SIZE / 8) bytes.
  • 364 |
  • Bits are packed per byte in LSB-first order. Within each byte, bit 0 (the least-significant bit) corresponds to the lowest array index for that byte; bit i corresponds to array index byte_index * 8 + i.
  • 365 |
  • Bytes are written in order: the first byte packs indices 0–7, the second 8–15, and so on.
  • 366 |
  • A bit value of 1 encodes true; 0 encodes false.
  • 367 |
  • If SIZE is not a multiple of 8, the remaining high bits of the final byte are padding and must be zero.
  • 368 |
369 |

Examples:

370 |
0b00000001  -> index 0 is true (indices 1–7 are false)
371 | 0b00000010  -> index 1 is true
372 | [true, false, true] -> 0b00000101
373 | 374 |
String Arrays
375 |

String arrays do not include the string HEADER for each element.

376 |

Layout: HEADER | SIZE | string[0] | ... string[N]

377 | 378 |

5 - Generic Array

379 |

Generic arrays expect elements to have headers.

380 |

Layout: HEADER | SIZE | VALUE[0] | ... VALUE[N]

381 | 382 |

6 - Extensions

383 |

Extensions are considered to be a formal part of the BEVE specification, but are not expected to be as broadly implemented.

384 |

Following the first three HEADER bits, the next five bits denote various extensions. These extensions are not expected to be implemented in every parser/serializer, but they provide convenient binary storage for more specialized use cases, such as variants, matrices, and complex numbers.

385 |
0 -> data delimiter // for specs like Newline Delimited JSON
386 | 1 -> type tag // for variant like structures
387 | 2 -> matrices
388 | 3 -> complex numbers
389 | 390 |

0 - Data Delimiter

391 |

Used to separate chunks of data to match specifications like NDJSON.

392 |

When converted to JSON this should add a new line ('\n') character to the JSON.

393 | 394 |

1 - Type Tag (Variants)

395 |

Expects a subsequent compressed unsigned integer to denote a type tag. A compressed SIZE indicator is used to efficiently store the tag.

396 |

Layout: HEADER | SIZE (i.e. type tag) | VALUE

397 |

The converted JSON format should look like:

398 |
{
399 |   "index": 0,
400 |   "value": "the JSON value"
401 | }
402 |

The "index" should refer to an array of types, from zero to one less than the count of types.

403 |

The "value" is any JSON value.

404 | 405 |

2 - Matrices

406 |

Matrices can be stored as object or array types. However, this tag provides a more compact mechanism to introspect matrices.

407 |

Matrices add a one byte MATRIX HEADER.

408 |

The first bit of the matrix header denotes the data layout of the matrix.

409 |
0 -> layout_right // row-major
410 | 1 -> layout_left // column-major
411 |

Layout: HEADER | MATRIX HEADER | EXTENTS | VALUE

412 |

EXTENTS are written out as a typed array of integers.

413 |
414 |

The VALUE in the matrix must be a typed array of numerical data.

415 |
416 |

The converted JSON format should look like:

417 |
{
418 |   "layout": "layout_right",
419 |   "extents": [3, 3],
420 |   "value": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
421 | }
422 | 423 |

3 - Complex Numbers

424 |

An additional COMPLEX HEADER byte is used.

425 |
    426 |
  • Complex numbers are stored as pairs of numerical types.
  • 427 |
428 |

The first three bits denote whether this is a single complex number or a complex array.

429 |
0 -> complex number
430 | 1 -> complex array
431 |

For a single complex number the layout is: HEADER | COMPLEX HEADER | DATA

432 |
433 |

A complex value is a pair of numbers.

434 |
435 |

For a complex array the layout is: HEADER | COMPLEX HEADER | SIZE | DATA

436 |
437 |

Three bits are used to align the left bits with the layouts for numbers.

438 |
439 |

The next two bits denote the numerical type:

440 |
0 -> floating point      0b000'00'000
441 | 1 -> signed integer      0b000'01'000
442 | 2 -> unsigned integer    0b000'10'000
443 |

The next three bits are used to indicate the BYTE COUNT. This is the same specification for BEVE numbers.

444 |

The converted JSON format should look like:

445 |
[1, 2] // for a complex number
446 | [[1, 2], [2.0, 3]] // for a complex array
447 |
448 |
449 | 450 | 459 | 460 | 461 | 462 | 463 | -------------------------------------------------------------------------------- /javascript/beve.js: -------------------------------------------------------------------------------- 1 | // Reference: https://github.com/stephenberry/beve 2 | 3 | (function (root, factory) { 4 | if (typeof define === 'function' && define.amd) { 5 | // AMD. Register as an anonymous module. 6 | define([], factory); 7 | } else if (typeof module === 'object' && module.exports) { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like environments that support module.exports, 10 | // like Node. 11 | module.exports = factory(); 12 | } else { 13 | // Browser globals (root is window) 14 | root.otherFile = factory(); 15 | } 16 | }(typeof self !== 'undefined' ? self : this, function () { 17 | // Reading BEVE 18 | function read_beve(buffer) { 19 | if (!buffer || !(buffer instanceof Uint8Array)) { 20 | throw new Error('Invalid buffer provided.'); 21 | } 22 | 23 | let cursor = 0; 24 | 25 | function read_value() { 26 | const header = buffer[cursor++]; 27 | const config = [1, 2, 4, 8]; 28 | const type = header & 0b00000111; 29 | 30 | switch (type) { 31 | case 0: // null or boolean 32 | { 33 | const is_bool = (header & 0b00001000) >> 3; 34 | if (is_bool) { 35 | return Boolean((header & 0b11110000) >> 4); 36 | } else { 37 | return null; 38 | } 39 | } 40 | case 1: // number 41 | { 42 | const num_type = (header & 0b00011000) >> 3; 43 | const is_float = num_type === 0; 44 | const is_signed = num_type === 1; 45 | const byte_count_index = (header & 0b11100000) >> 5; 46 | const byte_count = config[byte_count_index]; 47 | 48 | if (is_float) { 49 | switch (byte_count) { 50 | case 4: 51 | return readFloat(); 52 | case 8: 53 | return readDouble(); 54 | } 55 | } else { 56 | if (is_signed) { 57 | switch (byte_count) { 58 | case 1: 59 | return readInt8(); 60 | case 2: 61 | return readInt16(); 62 | case 4: 63 | return readInt32(); 64 | case 8: 65 | return readBigInt64(); 66 | } 67 | } else { 68 | switch (byte_count) { 69 | case 1: 70 | return readUInt8(); 71 | case 2: 72 | return readUInt16(); 73 | case 4: 74 | return readUInt32(); 75 | case 8: 76 | return readBigUInt64(); 77 | } 78 | } 79 | } 80 | break; 81 | } 82 | case 2: // string 83 | { 84 | const size = read_compressed(); 85 | const str = new TextDecoder().decode(buffer.subarray(cursor, cursor + size)); 86 | cursor += size; 87 | return str; 88 | } 89 | case 3: // object 90 | { 91 | const key_type = (header & 0b00011000) >> 3; 92 | const is_string = key_type === 0; 93 | const is_signed = key_type === 1; 94 | const byte_count_index = (header & 0b11100000) >> 5; 95 | const byte_count = config[byte_count_index]; 96 | const N = read_compressed(); 97 | const objectData = {}; 98 | 99 | for (let i = 0; i < N; ++i) { 100 | if (is_string) { 101 | const size = read_compressed(); 102 | const key = new TextDecoder().decode(buffer.subarray(cursor, cursor + size)); 103 | cursor += size; 104 | objectData[key] = read_value(); 105 | } else { 106 | throw new Error('TODO: support integer keys'); 107 | } 108 | } 109 | 110 | return objectData; 111 | } 112 | case 4: // typed array 113 | { 114 | const num_type = (header & 0b00011000) >> 3; 115 | const is_float = num_type === 0; 116 | const is_signed = num_type === 1; 117 | const byte_count_index_array = (header & 0b11100000) >> 5; 118 | const byte_count_array = config[byte_count_index_array]; 119 | 120 | if (num_type === 3) { 121 | const is_string = (header & 0b00100000) >> 5; 122 | if (is_string) { 123 | const N = read_compressed(); 124 | const array = new Array(N); 125 | for (let i = 0; i < N; ++i) { 126 | const size = read_compressed(); 127 | const str = new TextDecoder().decode(buffer.subarray(cursor, cursor + size)); 128 | cursor += size; 129 | array[i] = str; 130 | } 131 | return array; 132 | } else { 133 | throw new Error("Boolean array support not implemented"); 134 | } 135 | } else if (is_float) { 136 | const N = read_compressed(); 137 | const array = new Array(N); 138 | switch (byte_count_array) { 139 | case 4: 140 | for (let i = 0; i < N; ++i) { 141 | array[i] = readFloat(); 142 | } 143 | break; 144 | case 8: 145 | for (let i = 0; i < N; ++i) { 146 | array[i] = readDouble(); 147 | } 148 | break; 149 | } 150 | return array; 151 | } else { 152 | const N = read_compressed(); 153 | const array = new Array(N); 154 | 155 | if (is_signed) { 156 | switch (byte_count_array) { 157 | case 1: 158 | for (let i = 0; i < N; ++i) { 159 | array[i] = readInt8(); 160 | } 161 | break; 162 | case 2: 163 | for (let i = 0; i < N; ++i) { 164 | array[i] = readInt16(); 165 | } 166 | break; 167 | case 4: 168 | for (let i = 0; i < N; ++i) { 169 | array[i] = readInt32(); 170 | } 171 | break; 172 | case 8: 173 | for (let i = 0; i < N; ++i) { 174 | array[i] = readBigInt64(); 175 | } 176 | break; 177 | } 178 | } else { 179 | switch (byte_count_array) { 180 | case 1: 181 | for (let i = 0; i < N; ++i) { 182 | array[i] = readUInt8(); 183 | } 184 | break; 185 | case 2: 186 | for (let i = 0; i < N; ++i) { 187 | array[i] = readUInt16(); 188 | } 189 | break; 190 | case 4: 191 | for (let i = 0; i < N; ++i) { 192 | array[i] = readUInt32(); 193 | } 194 | break; 195 | case 8: 196 | for (let i = 0; i < N; ++i) { 197 | array[i] = readBigUInt64(); 198 | } 199 | break; 200 | } 201 | } 202 | return array; 203 | } 204 | } 205 | case 5: // untyped array 206 | { 207 | const N = read_compressed(); 208 | const unarray = new Array(N); 209 | 210 | for (let i = 0; i < N; ++i) { 211 | unarray[i] = read_value(); 212 | } 213 | 214 | return unarray; 215 | } 216 | case 6: // extensions 217 | { 218 | const extension = (header & 0b11111000) >> 3; 219 | switch (extension) { 220 | case 1: // variants 221 | read_compressed(); // Skipping variant tag 222 | return read_value(); 223 | case 2: // matrices 224 | const layout = buffer[cursor++] & 0b00000001; 225 | switch (layout) { 226 | case 0: // row major 227 | throw new Error('TODO: add row major support'); 228 | case 1: // column major 229 | const extents = read_value(); 230 | const matrix_data = read_value(); 231 | return reshape(matrix_data, extents[0], extents[1]); 232 | default: 233 | throw new Error('Unsupported layout'); 234 | } 235 | case 3: // complex numbers 236 | return read_complex_ext(); 237 | default: 238 | throw new Error('Unsupported extension'); 239 | } 240 | } 241 | default: 242 | throw new Error('Unsupported type'); 243 | } 244 | } 245 | 246 | function readFloat() { 247 | const value = new DataView(buffer.buffer, cursor, 4).getFloat32(0, true); 248 | cursor += 4; 249 | return value; 250 | } 251 | 252 | function readDouble() { 253 | const value = new DataView(buffer.buffer, cursor, 8).getFloat64(0, true); 254 | cursor += 8; 255 | return value; 256 | } 257 | 258 | function readInt8() { 259 | const value = new DataView(buffer.buffer, cursor, 1).getInt8(0); 260 | cursor += 1; 261 | return value; 262 | } 263 | 264 | function readInt16() { 265 | const value = new DataView(buffer.buffer, cursor, 2).getInt16(0, true); 266 | cursor += 2; 267 | return value; 268 | } 269 | 270 | function readInt32() { 271 | const value = new DataView(buffer.buffer, cursor, 4).getInt32(0, true); 272 | cursor += 4; 273 | return value; 274 | } 275 | 276 | function readBigInt64() { 277 | const value = new DataView(buffer.buffer, cursor, 8).getBigInt64(0, true); 278 | cursor += 8; 279 | return value; 280 | } 281 | 282 | function readUInt8() { 283 | const value = new DataView(buffer.buffer, cursor, 1).getUint8(0); 284 | cursor += 1; 285 | return value; 286 | } 287 | 288 | function readUInt16() { 289 | const value = new DataView(buffer.buffer, cursor, 2).getUint16(0, true); 290 | cursor += 2; 291 | return value; 292 | } 293 | 294 | function readUInt32() { 295 | const value = new DataView(buffer.buffer, cursor, 4).getUint32(0, true); 296 | cursor += 4; 297 | return value; 298 | } 299 | 300 | function readBigUInt64() { 301 | const value = new DataView(buffer.buffer, cursor, 8).getBigUint64(0, true); 302 | cursor += 8; 303 | return value; 304 | } 305 | 306 | function read_compressed() { 307 | const header = buffer[cursor]; 308 | const config = header & 0b00000011; 309 | 310 | switch (config) { 311 | case 0: 312 | cursor += 1; 313 | return header >> 2; 314 | case 1: { 315 | const h = new DataView(buffer.buffer, cursor, 2); 316 | cursor += 2; 317 | return (h.getUint16(0, true)) >> 2; 318 | } 319 | case 2: { 320 | const h = new DataView(buffer.buffer, cursor, 4); 321 | cursor += 4; 322 | return (h.getUint32(0, true)) >> 2; 323 | } 324 | case 3: { 325 | let val = BigInt(0); 326 | for (let i = 0; i < 8; ++i) { 327 | val |= BigInt(buffer[cursor + i]) << BigInt(8 * i); 328 | } 329 | cursor += 8; 330 | return Number(val >> BigInt(2)); 331 | } 332 | default: 333 | return 0; 334 | } 335 | } 336 | 337 | function read_complex_ext() { 338 | // COMPLEX HEADER 339 | const ch = buffer[cursor++]; 340 | const kind = ch & 0b00000111; // 0: single complex, 1: complex array 341 | const num_type = (ch & 0b00011000) >> 3; // 0 float, 1 signed, 2 unsigned 342 | const bc_idx = (ch & 0b11100000) >> 5; // byte count index 343 | const bc_map = [1, 2, 4, 8]; 344 | const byte_count = bc_map[bc_idx]; 345 | 346 | function read_num() { 347 | if (num_type === 0) { 348 | if (byte_count === 4) return readFloat(); 349 | if (byte_count === 8) return readDouble(); 350 | } else if (num_type === 1) { 351 | if (byte_count === 1) return readInt8(); 352 | if (byte_count === 2) return readInt16(); 353 | if (byte_count === 4) return readInt32(); 354 | if (byte_count === 8) return readBigInt64(); 355 | } else if (num_type === 2) { 356 | if (byte_count === 1) return readUInt8(); 357 | if (byte_count === 2) return readUInt16(); 358 | if (byte_count === 4) return readUInt32(); 359 | if (byte_count === 8) return readBigUInt64(); 360 | } 361 | throw new Error('Unsupported complex number type'); 362 | } 363 | 364 | if (kind === 0) { 365 | // Single complex: real, imag 366 | const real = read_num(); 367 | const imag = read_num(); 368 | return [real, imag]; 369 | } else if (kind === 1) { 370 | // Complex array: SIZE, then real[N], imag[N] 371 | const N = read_compressed(); 372 | const re = new Array(N); 373 | const im = new Array(N); 374 | for (let i = 0; i < N; ++i) re[i] = read_num(); 375 | for (let i = 0; i < N; ++i) im[i] = read_num(); 376 | return [re, im]; 377 | } else { 378 | throw new Error('Unsupported complex kind'); 379 | } 380 | } 381 | 382 | function reshape(array, rows, cols) { 383 | const reshaped = []; 384 | for (let i = 0; i < rows; ++i) { 385 | reshaped.push(array.slice(i * cols, (i + 1) * cols)); 386 | } 387 | return reshaped; 388 | } 389 | 390 | return read_value(); 391 | } 392 | 393 | class Writer { 394 | constructor(size = 256) { 395 | this.buffer = new Uint8Array(size); 396 | this.offset = 0; 397 | } 398 | 399 | ensureCapacity(size) { 400 | if (this.offset + size > this.buffer.length) { 401 | let newBuffer = new Uint8Array((this.buffer.length + size) * 2); 402 | newBuffer.set(this.buffer); 403 | this.buffer = newBuffer; 404 | } 405 | } 406 | 407 | append_uint8(value) { 408 | if (Number.isInteger(value) && value >= 0 && value <= 255) { 409 | this.ensureCapacity(1); 410 | this.buffer[this.offset] = value; 411 | this.offset += 1; 412 | } else { 413 | throw new Error('Value must be an integer between 0 and 255'); 414 | } 415 | } 416 | 417 | append_uint16(value) { 418 | if (Number.isInteger(value) && value >= 0 && value <= 65535) { 419 | // 16-bit unsigned integer 420 | this.ensureCapacity(2); 421 | let view = new DataView(this.buffer.buffer); 422 | view.setUint16(this.offset, value, true); // little-endian 423 | this.offset += 2; 424 | } else { 425 | throw new Error('Value must be an integer between 0 and 65535'); 426 | } 427 | } 428 | 429 | append_uint32(value) { 430 | if (Number.isInteger(value) && value >= 0 && value <= 4294967295) { 431 | // 32-bit unsigned integer 432 | this.ensureCapacity(4); 433 | let view = new DataView(this.buffer.buffer); 434 | view.setUint32(this.offset, value, true); // little-endian 435 | this.offset += 4; 436 | } else { 437 | throw new Error('Value must be an integer between 0 and 4294967295'); 438 | } 439 | } 440 | 441 | append_uint64(value) { 442 | if (Number.isInteger(value) && value >= 0 && value <= 18446744073709551615) { 443 | // 64-bit unsigned integer 444 | this.ensureCapacity(8); 445 | let high = Math.floor(value / 0x100000000); 446 | let low = value % 0x100000000; 447 | let view = new DataView(this.buffer.buffer); 448 | view.setUint32(this.offset, low, true); // little-endian 449 | view.setUint32(this.offset + 4, high, true); // little-endian 450 | this.offset += 8; 451 | } else { 452 | throw new Error('Value must be an integer between 0 and 18446744073709551615'); 453 | } 454 | } 455 | 456 | append(value) { 457 | if (Array.isArray(value)) { 458 | // Iterate over each element in the array and append 459 | for (const element of value) { 460 | this.append(element); 461 | } 462 | } else if (typeof value === 'string') { 463 | // Convert string to UTF-8 byte sequence 464 | const encoder = new TextEncoder(); 465 | const bytes = encoder.encode(value); 466 | const length = bytes.length; 467 | 468 | // Ensure capacity for the string bytes 469 | this.ensureCapacity(length); 470 | 471 | // Append bytes to the buffer 472 | this.buffer.set(bytes, this.offset); 473 | this.offset += length; 474 | 475 | // Debugging: 476 | //const stringFromUint8Array = String.fromCharCode.apply(null, this.buffer); 477 | //console.log(stringFromUint8Array); 478 | } else if (Number.isInteger(value) && value >= -0x80000000 && value <= 0x7FFFFFFF) { 479 | // 32-bit signed integer 480 | this.ensureCapacity(4); 481 | let view = new DataView(this.buffer.buffer); 482 | view.setInt32(this.offset, value, true); // little-endian 483 | this.offset += 4; 484 | } else if (typeof value === 'number') { 485 | // 64-bit floating-point number (double) 486 | this.ensureCapacity(8); 487 | let view = new DataView(this.buffer.buffer); 488 | view.setFloat64(this.offset, value, true); // little-endian 489 | this.offset += 8; 490 | } else { 491 | throw new Error('Unsupported value type'); 492 | } 493 | } 494 | } 495 | 496 | function write_beve(data) { 497 | const writer = new Writer(); 498 | write_value(writer, data); 499 | return writer.buffer; 500 | } 501 | 502 | function write_value(writer, value) { 503 | if (value === null || value === undefined) { 504 | // null header: type 0, is_bool = 0 505 | writer.append_uint8(0); 506 | } else if (Array.isArray(value) && value.length > 1 && value.every(v => typeof v === 'number')) { 507 | let header = 4; 508 | if (typeof value[0] === 'number' && !Number.isInteger(value[0])) { 509 | header |= 0b01100000; // float64_t (double) 510 | } else { 511 | header |= 0b01001000; // int32_t (int) 512 | } 513 | writer.append_uint8(header); 514 | writeCompressed(writer, value.length); 515 | writer.append(value); 516 | } else if (typeof value === 'boolean') { 517 | let header = 0; 518 | if (value) { 519 | header |= 0b00011000; 520 | } else { 521 | header |= 0b00001000; 522 | } 523 | writer.append_uint8(header); 524 | } else if (typeof value === 'number') { 525 | let header = 1; 526 | if (typeof value === 'number' && !Number.isInteger(value)) { 527 | header |= 0b01100000; // float64_t (double) 528 | } else { 529 | header |= 0b01001000; // int32_t (int) 530 | } 531 | writer.append_uint8(header); 532 | writer.append(value); 533 | } else if (typeof value === 'string') { 534 | let header = 2; 535 | writer.append_uint8(header); 536 | const bytes = new TextEncoder().encode(value); 537 | writeCompressed(writer, bytes.length); 538 | writer.ensureCapacity(bytes.length); 539 | writer.buffer.set(bytes, writer.offset); 540 | writer.offset += bytes.length; 541 | } else if (Array.isArray(value)) { 542 | let header = 5; 543 | writer.append_uint8(header); 544 | writeCompressed(writer, value.length); 545 | for (let i = 0; i < value.length; i++) { 546 | write_value(writer, value[i]); 547 | } 548 | } else if (typeof value === 'object') { 549 | // Filter out undefined values (matching JSON.stringify behavior) 550 | const keys = Object.keys(value).filter(k => value[k] !== undefined); 551 | let header = 3; 552 | let keyType = 0; // Assuming keys are always strings 553 | let isSigned = false; 554 | header |= keyType << 3; 555 | header |= isSigned << 5; 556 | writer.append_uint8(header); 557 | writeCompressed(writer, keys.length); 558 | for (const key of keys) { 559 | const keyBytes = new TextEncoder().encode(key); 560 | writeCompressed(writer, keyBytes.length); 561 | writer.ensureCapacity(keyBytes.length); 562 | writer.buffer.set(keyBytes, writer.offset); 563 | writer.offset += keyBytes.length; 564 | write_value(writer, value[key]); 565 | } 566 | } else { 567 | throw new Error('Unsupported data type'); 568 | } 569 | } 570 | 571 | function writeCompressed(writer, N) { 572 | if (N < 64) { 573 | const compressed = (N << 2) | 0; 574 | writer.append_uint8(compressed); 575 | } else if (N < 16384) { 576 | const compressed = (N << 2) | 1; 577 | writer.append_uint16(compressed); 578 | } else if (N < 1073741824) { 579 | const compressed = (N << 2) | 2; 580 | writer.append_uint32(compressed); 581 | } else if (N < 4611686018427387904) { 582 | const compressed = (N << 2) | 3; 583 | writer.append_uint64(BigInt(compressed)); 584 | } 585 | } 586 | 587 | return { 588 | read_beve: read_beve, 589 | write_beve: write_beve 590 | }; 591 | })); 592 | --------------------------------------------------------------------------------