├── .github └── workflows │ └── d.yml ├── .gitignore ├── LICENSE_1_0.txt ├── README.markdown ├── dub.json ├── example ├── array_test.d ├── attribute.d ├── compare_direct_stream.d ├── compare_json.d ├── convert_json.d ├── custom.d ├── custom_handler.d ├── map_test.d ├── register.d ├── simple.d ├── stream.d └── unpacker_foreach.d ├── html └── candydoc │ ├── candy.ddoc │ ├── explorer.js │ ├── ie56hack.css │ ├── img │ ├── bg.gif │ ├── candydoc.gif │ ├── outline │ │ ├── alias.gif │ │ ├── bg.gif │ │ ├── class.gif │ │ ├── enum.gif │ │ ├── func.gif │ │ ├── module.gif │ │ ├── package.gif │ │ ├── struct.gif │ │ ├── template.gif │ │ └── var.gif │ ├── package │ │ └── bg.gif │ └── tree │ │ ├── shim.gif │ │ ├── tb.gif │ │ ├── tbr.gif │ │ ├── tbrm.gif │ │ ├── tbrp.gif │ │ ├── tr.gif │ │ ├── trm.gif │ │ └── trp.gif │ ├── modules.ddoc │ ├── style.css │ ├── tree.js │ └── util.js ├── meson.build ├── posix.mak ├── src └── msgpack │ ├── attribute.d │ ├── buffer.d │ ├── common.d │ ├── exception.d │ ├── package.d │ ├── packer.d │ ├── register.d │ ├── streaming_unpacker.d │ ├── unpacker.d │ └── value.d └── win32.mak /.github/workflows/d.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | os: [ ubuntu-latest, macOS-latest, windows-latest ] 10 | dc: [ dmd-latest, ldc-latest ] 11 | runs-on: ${{ matrix.os }} 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v3 15 | - name: Install compiler 16 | uses: dlang-community/setup-dlang@v1 17 | with: 18 | compiler: ${{ matrix.dc }} 19 | - name: Test 20 | run: | 21 | dub test 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dub.selections.json 2 | .dub 3 | tests/*/tests 4 | __test__*__ 5 | *.obj 6 | *.[oa] 7 | *.so 8 | *.lib 9 | *.dll 10 | *.sublime-project 11 | *.sublime-workspace 12 | msgpack-d-test-unittest 13 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | [![CI](https://github.com/msgpack/msgpack-d/actions/workflows/d.yml/badge.svg)](https://github.com/msgpack/msgpack-d/actions/workflows/d.yml) 2 | 3 | # MessagePack for D 4 | 5 | MessagePack is a binary-based JSON-like serialization library. 6 | 7 | MessagePack for D is a pure D implementation of MessagePack. 8 | 9 | # Features 10 | 11 | * Small size and High performance 12 | * Zero copy serialization / deserialization 13 | * Streaming deserializer for non-contiguous IO situation 14 | * Supports D features (Ranges, Tuples, real type) 15 | 16 | Note: The `real` type is only supported in D. 17 | Don't use the `real` type when communicating with other programming languages. 18 | Note that `Unpacker` will raise an exception if a loss of precision occurs. 19 | 20 | ## Current Limitations 21 | 22 | * No circular references support 23 | * If you want to use the LDC compiler, you need at least version 0.15.2 beta2 24 | 25 | # Install 26 | 27 | Use dub to add it as a dependency: 28 | 29 | ```sh 30 | % dub install msgpack-d 31 | ``` 32 | 33 | # Usage 34 | 35 | Example code can be found in the `example` directory. 36 | 37 | The documentation can be found [here](http://msgpack.github.io/msgpack-d/) 38 | 39 | ## pack / unpack 40 | 41 | msgpack-d is very simple to use. Use `pack` for serialization, and `unpack` for deserialization: 42 | 43 | ```D 44 | import std.file; 45 | import msgpack; 46 | 47 | struct S { int x; float y; string z; } 48 | 49 | void main() 50 | { 51 | S input = S(10, 25.5, "message"); 52 | 53 | // serialize data 54 | ubyte[] inData = pack(input); 55 | 56 | // write data to a file 57 | write("file.dat", inData); 58 | 59 | // read data from a file 60 | ubyte[] outData = cast(ubyte[])read("file.dat"); 61 | 62 | // unserialize the data 63 | S target = outData.unpack!S(); 64 | 65 | // verify data is the same 66 | assert(target.x == input.x); 67 | assert(target.y == input.y); 68 | assert(target.z == input.z); 69 | } 70 | ``` 71 | 72 | ### Feature: Skip serialization/deserialization of a specific field. 73 | 74 | Use the `@nonPacked` attribute: 75 | 76 | ```d 77 | struct User 78 | { 79 | string name; 80 | @nonPacked int level; // pack / unpack will ignore the 'level' field 81 | } 82 | ``` 83 | 84 | ### Feature: Use your own serialization/deserialization routines for custom class and struct types. 85 | 86 | msgpack-d provides the functions `registerPackHandler` / `registerUnpackHandler` to allow you 87 | to use custom routines during the serialization or deserialization of user-defined class and struct types. 88 | This feature is especially useful when serializing a derived class object when that object is statically 89 | typed as a base class object. 90 | 91 | For example: 92 | 93 | ```d 94 | class Document { } 95 | class XmlDocument : Document 96 | { 97 | this() { } 98 | this(string name) { this.name = name; } 99 | string name; 100 | } 101 | 102 | void xmlPackHandler(ref Packer p, ref XmlDocument xml) 103 | { 104 | p.pack(xml.name); 105 | } 106 | 107 | void xmlUnpackHandler(ref Unpacker u, ref XmlDocument xml) 108 | { 109 | u.unpack(xml.name); 110 | } 111 | 112 | void main() 113 | { 114 | /// Register the 'xmlPackHandler' and 'xmlUnpackHandler' routines for 115 | /// XmlDocument object instances. 116 | registerPackHandler!(XmlDocument, xmlPackHandler); 117 | registerUnpackHandler!(XmlDocument, xmlUnpackHandler); 118 | 119 | /// Now we can serialize/deserialize XmlDocument object instances via a 120 | /// base class reference. 121 | Document doc = new XmlDocument("test.xml"); 122 | auto data = pack(doc); 123 | XmlDocument xml = unpack!XmlDocument(data); 124 | assert(xml.name == "test.xml"); // xml.name is "test.xml" 125 | } 126 | ``` 127 | 128 | In addition, here is also a method using `@serializedAs` attribute: 129 | 130 | ```d 131 | import std.datetime: Clock, SysTime; 132 | static struct SysTimePackProxy 133 | { 134 | static void serialize(ref Packer p, ref in SysTime tim) 135 | { 136 | p.pack(tim.toISOExtString()); 137 | } 138 | 139 | static void deserialize(ref Unpacker u, ref SysTime tim) 140 | { 141 | string tmp; 142 | u.unpack(tmp); 143 | tim = SysTime.fromISOExtString(tmp); 144 | } 145 | } 146 | static struct LogData 147 | { 148 | string msg; 149 | string file; 150 | ulong line; 151 | @serializedAs!SysTimePackProxy SysTime timestamp; 152 | 153 | this(string message, string file = __FILE__, ulong line = __LINE__) 154 | { 155 | this.msg = message; 156 | this.file = file; 157 | this.line = line; 158 | this.timestamp = Clock.currTime(); 159 | } 160 | } 161 | 162 | void main() 163 | { 164 | /// Now we can serialize/deserialize LogData 165 | LogData[] logs; 166 | logs ~= LogData("MessagePack is nice!"); 167 | auto data = pack(logs); 168 | LogData[] datas = unpack!(LogData[])(data); 169 | assert(datas[0].timestamp.toString() == datas[0].timestamp.toString()); 170 | } 171 | ``` 172 | 173 | ## The PackerImpl / Unpacker / StreamingUnpacker types 174 | 175 | These types are used by the `pack` and `unpack` functions. 176 | 177 | See the documentation of [PackerImpl](http://msgpack.github.io/msgpack-d/#PackerImpl), [Unpacker](http://msgpack.github.io/msgpack-d/#Unpacker) and [StreamingUnpacker](http://msgpack.github.io/msgpack-d/#StreamingUnpacker) for more details. 178 | 179 | # Links 180 | 181 | * [The MessagePack Project](http://msgpack.org/) 182 | 183 | The official MessagePack protocol website. 184 | 185 | * [msgpack-d's issue tracker](https://github.com/msgpack/msgpack-d/issues) 186 | 187 | Use this issue tracker to review and file bugs in msgpack-d. 188 | 189 | * [MessagePack's Github](http://github.com/msgpack/) 190 | 191 | Other language bindings and implementations of the msgpack protocol can be found here. 192 | 193 | # Copyright 194 | 195 | Copyright (c) 2010- Masahiro Nakagawa 196 | 197 | # License 198 | 199 | Distributed under the [Boost Software License, Version 1.0](http://www.boost.org/users/license.html). 200 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "msgpack-d", 3 | "description": "MessagePack for D.", 4 | "authors": ["Masahiro Nakagawa"], 5 | "homepage": "https://github.com/msgpack/msgpack-d", 6 | "license": "Boost Software License, Version 1.0", 7 | "copyright": "Copyright (c) 2010- Masahiro Nakagawa", 8 | 9 | "configurations": [ 10 | { 11 | "name": "default", 12 | "targetType": "library" 13 | }, 14 | { 15 | "name": "unittest", 16 | "dflags": ["-dip25", "-dip1000"] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /example/array_test.d: -------------------------------------------------------------------------------- 1 | import std.conv; 2 | import std.stdio; 3 | import std.datetime; 4 | import msgpack; 5 | 6 | struct A { 7 | int x; 8 | } 9 | 10 | struct Foo 11 | { 12 | A[] a; 13 | } 14 | 15 | void main() 16 | { 17 | Foo foo; 18 | foreach (a; 'a' .. 'z') 19 | foreach (b; 'a' .. 'z') 20 | foreach (c; 'a' .. 'z') 21 | foo.a ~= A(); 22 | 23 | auto sw = StopWatch(AutoStart.yes); 24 | ubyte[] data = msgpack.pack(foo); 25 | writeln(sw.peek.usecs); 26 | 27 | auto sw2 = StopWatch(AutoStart.yes); 28 | Foo foo1 = msgpack.unpack(data).as!(typeof(foo)); 29 | writeln(sw2.peek.usecs); 30 | 31 | assert(foo == foo1); 32 | 33 | Foo foo2; 34 | auto sw3 = StopWatch(AutoStart.yes); 35 | msgpack.unpack(data, foo2); 36 | writeln(sw3.peek.usecs); 37 | 38 | assert(foo == foo2); 39 | } 40 | -------------------------------------------------------------------------------- /example/attribute.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | * Attribute usage 5 | */ 6 | 7 | import msgpack; 8 | 9 | struct Hoge 10 | { 11 | string f1; 12 | @nonPacked int f2; 13 | } 14 | 15 | void main() 16 | { 17 | Hoge hoge = Hoge("hoge", 10); 18 | Hoge fuga; 19 | 20 | unpack(pack(hoge), fuga); 21 | assert(hoge.f1 == fuga.f1); 22 | assert(hoge.f2 != fuga.f2); 23 | assert(fuga.f2 == int.init); 24 | } 25 | -------------------------------------------------------------------------------- /example/compare_direct_stream.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | * Compares direct conversion with stream. 5 | */ 6 | 7 | import std.datetime; 8 | import std.stdio; 9 | import std.typecons; 10 | 11 | import msgpack; 12 | 13 | 14 | void main() 15 | { 16 | // tuple 17 | auto test1 = tuple(new int[](100), "MessagePack!", [1:2.0, 3:4.0, 5:6.0, 7:8.0]); 18 | auto data1 = pack(test1); 19 | 20 | // stream 21 | void s1() 22 | { 23 | auto result = unpack(data1).as!(typeof(test1)); 24 | } 25 | 26 | // direct conversion 27 | void d1() 28 | { 29 | typeof(test1) result; 30 | unpack(data1, result); 31 | } 32 | 33 | // array 34 | auto test2 = new int[](1000); 35 | auto data2 = pack(test2); 36 | 37 | // stream 38 | void s2() 39 | { 40 | auto result = unpack(data2).as!(typeof(test2)); 41 | } 42 | 43 | // direct conversion 44 | void d2() 45 | { 46 | typeof(test2) result; 47 | unpack(data2, result); 48 | } 49 | 50 | auto times = benchmark!(s1, d1, s2, d2)(1000); 51 | writeln("Stream(Tuple):", times[0].msecs); 52 | writeln("Direct(Tuple):", times[1].msecs); 53 | writeln("Stream(Array):", times[2].msecs); 54 | writeln("Direct(Array):", times[3].msecs); 55 | } 56 | -------------------------------------------------------------------------------- /example/compare_json.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | * Compares std.json 5 | */ 6 | 7 | import std.datetime; 8 | import std.json; 9 | import std.stdio; 10 | 11 | import msgpack; 12 | 13 | 14 | void main() 15 | { 16 | JSONValue jsonObj = parseJSON(`[12, "foo", true, 0.23, {"1":1}, [1, 2]]`); 17 | 18 | void f1() 19 | { 20 | parseJSON(toJSON(jsonObj)); 21 | } 22 | 23 | Value mpObj = unpack(pack(12, "foo", true, 0.23, ["1":1], [1, 2])); 24 | 25 | void f2() 26 | { 27 | unpack(pack(mpObj)); 28 | } 29 | 30 | auto times = benchmark!(f1, f2)(10000); 31 | writeln("JSON: ", times[0].msecs); 32 | writeln("Msgpack: ", times[1].msecs); 33 | } 34 | -------------------------------------------------------------------------------- /example/convert_json.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | * Converting to/from JSON usage 5 | */ 6 | 7 | import std.stdio; 8 | 9 | import msgpack; 10 | import std.json; 11 | 12 | 13 | void main() 14 | { 15 | struct Simple 16 | { 17 | int a = 5; 18 | string b = "hello"; 19 | double c = 3.14; 20 | char d = '!'; 21 | @nonPacked string e = "world"; 22 | bool f = true; 23 | } 24 | auto simple = Simple(); 25 | Value val = simple.pack().unpack(); 26 | writeln(val.toJSONValue()); 27 | val = simple.pack!true().unpack(); 28 | writeln(val.toJSONValue()); 29 | 30 | string jsonString = `[30, 30.5, true, "hello", "40"]`; 31 | val = parseJSON(jsonString).fromJSONValue(); 32 | assert(val.pack() !is null); 33 | assert(val.type == Value.Type.array); 34 | foreach (v; val.via.array) 35 | { 36 | writeln(v.type); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/custom.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | * User-defined class sample 5 | */ 6 | 7 | import std.stdio, std.math; 8 | 9 | import msgpack; 10 | 11 | 12 | enum Attr : ubyte 13 | { 14 | A, B 15 | } 16 | 17 | 18 | struct User 19 | { 20 | string name; 21 | uint age; 22 | Attr attr; 23 | } 24 | 25 | 26 | void main() 27 | { 28 | User user = User("Foo", 20, Attr.B), other; 29 | 30 | unpack(pack(user), other); 31 | 32 | writeln("name: ", other.name, "(", other.age, ", ", other.attr, ")"); 33 | 34 | // Complex data-structure 35 | 36 | auto btree = new BTree!(int, string); 37 | int[] keys = [3, 6, 8, 10, 1, 5, 20]; 38 | string[] values = ["Foo", "Baz", "Bar", "Hoge", "Fuga", "Piyo", "ham"]; 39 | 40 | foreach (i, key; keys) 41 | btree.insert(key, values[i]); 42 | btree.print(); 43 | 44 | auto result = new BTree!(int, string); 45 | 46 | unpack(pack(btree), result); 47 | 48 | result.print(); 49 | } 50 | 51 | 52 | /** 53 | * Simple B-Tree. 54 | */ 55 | class BTree(Key, Data) 56 | { 57 | private: 58 | immutable uint MAX_CHILD; // sub-tree size(m-tree) 59 | immutable uint HALF_CHILD; // min number((m + 1) / 2) 60 | 61 | enum NodeType 62 | { 63 | Internal, 64 | Leaf 65 | } 66 | 67 | static class Node 68 | { 69 | NodeType type; 70 | 71 | union 72 | { 73 | struct // internal 74 | { 75 | uint childs; // child number 76 | Key[] low; // min element of each sub-tree 77 | Node[] child; // sub-tree size(m) 78 | } 79 | 80 | struct // leaf 81 | { 82 | Key key; 83 | Data data; 84 | } 85 | } 86 | 87 | this(in uint num) { low.length = child.length = num; } 88 | 89 | void toMsgpack(Packer)(ref Packer packer) const 90 | { 91 | if (type == NodeType.Internal) 92 | packer.packArray(childs, low.length, low, child); 93 | else 94 | packer.packArray(key, data); 95 | } 96 | 97 | void fromMsgpack(ref Unpacker unpacker) 98 | { 99 | if (unpacker.beginArray() == 4) { 100 | uint max; 101 | 102 | unpacker.unpack(childs, max, low); 103 | child.length = unpacker.beginArray(); 104 | foreach (i; 0..child.length) 105 | unpacker.unpack(child[i], max); 106 | low.length = child.length = max; 107 | } else { 108 | type = NodeType.Leaf; 109 | unpacker.unpack(key, data); 110 | } 111 | } 112 | } 113 | 114 | Node root; 115 | 116 | 117 | public: 118 | /** 119 | * Params: 120 | * num = node size. 121 | */ 122 | this(in uint num = 5) 123 | in 124 | { 125 | assert(num >= 2, "The prerequisite of m-tree(size must be larger than 2)"); 126 | } 127 | body 128 | { 129 | MAX_CHILD = num; 130 | HALF_CHILD = cast(uint)ceil(cast(real)MAX_CHILD / 2); 131 | } 132 | 133 | /** 134 | * Params: 135 | * key = key that represents a data to insert. 136 | * data = data to insert. 137 | * 138 | * Returns: 139 | * node that inserted data. 140 | */ 141 | Node insert(in Key key, in Data data) 142 | { 143 | /* 144 | * Params: 145 | * n = node to insert element 146 | * nNode = new node(null if not create). 147 | * lowest = min element in $(D_PARAM nNode) 148 | * 149 | * Returns: 150 | * node that element was inserted. 151 | */ 152 | Node _insert(ref Node n, out Node nNode, out Key lowest) 153 | { 154 | Node node = n; // for updating data 155 | 156 | if (node.type == NodeType.Leaf) { 157 | if (node.key == key) { 158 | return null; 159 | } else { 160 | Node elem = allocNode(); 161 | elem.type = NodeType.Leaf; 162 | elem.key = key; 163 | elem.data = data; 164 | 165 | if (elem.key < node.key) { 166 | n = elem; 167 | nNode = node; 168 | lowest = node.key; 169 | } else { 170 | nNode = elem; 171 | lowest = elem.key; 172 | } 173 | 174 | return elem; 175 | } 176 | } else { 177 | int i, j, pos; // pos = position to insert. 178 | Key xLowest; // lowest for recursion. 179 | Node xNode, ret; // nNode for recursion. 180 | 181 | pos = locateSubTree(node, key); 182 | ret = _insert(node.child[pos], xNode, xLowest); 183 | 184 | // Doesn't create and patition. 185 | if (xNode is null) 186 | return ret; 187 | 188 | if (node.childs < MAX_CHILD) { 189 | for (i = node.childs - 1; i > pos; i--) { 190 | node.child[i+1] = node.child[i]; 191 | node.low[i+1] = node.low[i]; 192 | } 193 | node.child[pos+1] = xNode; 194 | node.low[pos+1] = xLowest; 195 | node.childs++; 196 | return ret; 197 | } else { 198 | Node elem = allocNode(); 199 | elem.type = NodeType.Internal; 200 | 201 | // insert to node side or elem side? 202 | if (pos < HALF_CHILD - 1) { 203 | for (i = HALF_CHILD - 1, j = 0; i < MAX_CHILD; i++, j++) { 204 | elem.child[j] = node.child[i]; 205 | elem.low[j] = node.low[i]; 206 | } 207 | 208 | for (i = HALF_CHILD - 2; i > pos; i--) { 209 | node.child[i+1] = node.child[i]; 210 | node.low[i+1] = node.low[i]; 211 | } 212 | node.child[pos+1] = xNode; 213 | node.low[pos+1] = xLowest; 214 | } else { 215 | for (i = MAX_CHILD - 1, j = MAX_CHILD - HALF_CHILD; i >= HALF_CHILD; i--) { 216 | if (i == pos) { 217 | elem.child[j] = xNode; 218 | elem.low[j--] = xLowest; 219 | } 220 | elem.child[j] = node.child[i]; 221 | elem.low[j--] = node.low[i]; 222 | } 223 | 224 | if (pos < HALF_CHILD) { 225 | elem.child[0] = xNode; 226 | elem.low[0] = xLowest; 227 | } 228 | } 229 | 230 | node.childs = HALF_CHILD; 231 | elem.childs = MAX_CHILD+1 - HALF_CHILD; 232 | 233 | nNode = elem; 234 | lowest = elem.low[0]; 235 | 236 | return ret; 237 | } 238 | } 239 | } 240 | 241 | if (root is null) { 242 | root = allocNode(); 243 | root.type = NodeType.Leaf; 244 | root.key = key; 245 | root.data = data; 246 | return root; 247 | } else { 248 | Key lowest; 249 | Node ret, newNode; 250 | 251 | ret = _insert(root, newNode, lowest); 252 | 253 | // new node and growl height if patitioned. 254 | if (newNode !is null) { 255 | Node elem = allocNode(); 256 | elem.type = NodeType.Internal; 257 | elem.childs = 2; 258 | elem.child[0] = root; 259 | elem.child[1] = newNode; 260 | elem.low[1] = lowest; 261 | root = elem; 262 | } 263 | 264 | return ret; 265 | } 266 | } 267 | 268 | /** 269 | * Params: 270 | * key = key to delete. 271 | * 272 | * Returns: 273 | * true if deleted. 274 | */ 275 | bool remove(in Key key) 276 | { 277 | enum State 278 | { 279 | Nothing, 280 | Removed, 281 | Reconfig 282 | } 283 | 284 | /* 285 | * Params: 286 | * n = node to remove. 287 | * result = node change because of an remove. 288 | * 289 | * Returns: 290 | * true if removed. 291 | */ 292 | bool _remove(ref Node n, out State result) 293 | { 294 | if (n.type == NodeType.Leaf) { 295 | if (n.key == key) { 296 | result = State.Removed; 297 | delete n; 298 | return true; 299 | } else { 300 | return false; 301 | } 302 | } else { 303 | int pos, sub; // sub-tree position to remove, for restructure. 304 | bool ret, merged; // delete?, merge sub-tree? 305 | State state; // sub-tree state. 306 | 307 | pos = locateSubTree(n, key); 308 | ret = _remove(n.child[pos], state); 309 | 310 | if (state == State.Nothing) 311 | return ret; 312 | 313 | if (state == State.Reconfig) { 314 | sub = pos == 0 ? 0 : pos - 1; 315 | merged = revisionNodes(n, sub); 316 | 317 | if (merged) 318 | pos = sub+1; 319 | } 320 | 321 | if (state == State.Removed || merged) { 322 | // sub-tree compaction 323 | for (int i = pos; i < n.childs - 1; i++) { 324 | n.child[i] = n.child[i+1]; 325 | n.low[i] = n.low[i+1]; 326 | } 327 | 328 | if (--n.childs < HALF_CHILD) 329 | result = State.Reconfig; 330 | } 331 | 332 | return ret; 333 | } 334 | } 335 | 336 | if (root is null) { 337 | return false; 338 | } else { 339 | State result; 340 | bool ret = _remove(root, result); 341 | 342 | if (result == State.Removed) { 343 | root = null; 344 | } else if (result == State.Reconfig && root.childs == 1) { 345 | Node n = root; 346 | root = root.child[0]; 347 | delete n; 348 | } 349 | 350 | return ret; 351 | } 352 | } 353 | 354 | /** 355 | * Params: 356 | * key = key to search. 357 | * 358 | * Returns: 359 | * finded node. 360 | */ 361 | Node search(in Key key) 362 | { 363 | if (root is null) { 364 | return null; 365 | } else { 366 | int i; 367 | Node n = root; 368 | 369 | // searches internal node until find leaf. 370 | while (n.type == NodeType.Internal) { 371 | i = locateSubTree(n, key); 372 | n = n.child[i]; 373 | } 374 | 375 | if (key == n.key) 376 | return n; 377 | else 378 | return null; 379 | } 380 | } 381 | 382 | void print() 383 | { 384 | void _print(ref Node n) 385 | { 386 | if (n.type == NodeType.Leaf) { 387 | writefln("[%x] Leaf : %s Data : %s", &n, n.key, n.data); 388 | } else { 389 | writef("[%x] Childs %d [%x], ", &n, n.childs, &n.child[0]); 390 | 391 | foreach (i; 0..MAX_CHILD) 392 | writef("%d[%x] ", n.low[i], &n.child[i]); 393 | writeln(); 394 | 395 | foreach (i; 0..n.childs) 396 | _print(n.child[i]); 397 | } 398 | } 399 | 400 | if (root is null) 401 | writefln("Element is nothing"); 402 | else 403 | _print(root); 404 | } 405 | 406 | // for MessagePack 407 | 408 | void toMsgpack(Packer)(ref Packer packer) const 409 | { 410 | packer.pack(root); 411 | } 412 | 413 | void fromMsgpack(ref Unpacker unpacker) 414 | { 415 | unpacker.unpack(root, MAX_CHILD); 416 | } 417 | 418 | 419 | private: 420 | /* 421 | * Returns: 422 | * new node. 423 | */ 424 | Node allocNode() 425 | { 426 | return new Node(MAX_CHILD); 427 | } 428 | 429 | /* 430 | * searches $(D_PARAM key) element in sub-tree of $(D_PARAM node). 431 | * 432 | * Params: 433 | * node = node to search. 434 | * key = key to search in $(D_PARAM node). 435 | * 436 | * Returns: 437 | * finded position. 438 | */ 439 | int locateSubTree(ref Node node, in Key key) const 440 | { 441 | for (int i = node.childs - 1; i > 0; i--) 442 | if (key >= node.low[i]) 443 | return i; 444 | return 0; 445 | } 446 | 447 | /* 448 | * Params: 449 | * n = revision node. 450 | * x = position to sub-tree of revision node. 451 | * 452 | * Returns: 453 | * true if merged. 454 | */ 455 | bool revisionNodes(ref Node n, in uint x) 456 | { 457 | int i; 458 | Node a, b; // each sub-tree. 459 | uint an, bn; // child number of each sub-tree. 460 | 461 | a = n.child[x]; 462 | b = n.child[x+1]; 463 | an = a.childs; 464 | bn = b.childs; 465 | b.low[0] = n.low[x+1]; 466 | 467 | if (an + bn <= MAX_CHILD) { // merge 468 | for (i = 0; i < bn; i++) { 469 | a.child[an+i] = b.child[i]; 470 | a.low[an+i] = b.low[i]; 471 | } 472 | 473 | a.childs += bn; 474 | delete b; 475 | 476 | return true; 477 | } else { // partition 478 | uint pivot = (an + bn) / 2; // pivot to patition. 479 | uint move; // element number to copy. 480 | 481 | if (an > pivot) { 482 | move = an - pivot; 483 | 484 | for (i = bn - 1; i >= 0; i--) { 485 | b.child[move+i] = b.child[i]; 486 | b.low[move+i] = b.low[i]; 487 | } 488 | // copy element a to b. 489 | for (i = 0; i < move; i++) { 490 | b.child[i] = a.child[pivot+i]; 491 | b.low[i] = a.low[pivot+i]; 492 | } 493 | } else { 494 | move = pivot - an; 495 | 496 | // copy element b to a. 497 | for (i = 0; i < move; i++) { 498 | a.child[an+i] = b.child[i]; 499 | a.low[an+i] = b.low[i]; 500 | } 501 | for (i = 0; i < bn - move; i++) { 502 | b.child[i] = b.child[move+i]; 503 | b.low[i] = b.low[move+i]; 504 | } 505 | } 506 | 507 | a.childs = pivot; 508 | b.childs = an + bn - pivot; 509 | n.low[x+1] = b.low[0]; 510 | 511 | return false; 512 | } 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /example/custom_handler.d: -------------------------------------------------------------------------------- 1 | import msgpack; 2 | import std.array; 3 | import std.stdio; 4 | import std.variant; 5 | 6 | class A { } 7 | class C : A 8 | { 9 | int num; 10 | this(int n) { num = n; } 11 | } 12 | 13 | void cPackHandler(ref Packer p, ref C c) 14 | { 15 | writeln("Pack C: ", c.num); 16 | p.pack(c.num); 17 | } 18 | 19 | void cUnpackHandler(ref Unpacker u, ref C c) 20 | { 21 | writeln("Unpack C: ", c.num); 22 | u.unpack(c.num); 23 | } 24 | 25 | void vPackHandler(ref Packer p, ref Variant v) 26 | { 27 | writeln("pack Variant: ", v); 28 | p.pack(v.get!bool); 29 | } 30 | 31 | void vUnpackHandler(ref Unpacker u, ref Variant v) 32 | { 33 | writeln("unpack Variant: ", v); 34 | bool b; 35 | u.unpack(b); 36 | v = b; 37 | } 38 | 39 | void main() 40 | { 41 | registerPackHandler!(C, cPackHandler); 42 | registerUnpackHandler!(C, cUnpackHandler); 43 | registerPackHandler!(Variant, vPackHandler); 44 | registerUnpackHandler!(Variant, vUnpackHandler); 45 | 46 | { 47 | Packer p; 48 | A c = new C(1000); 49 | p.pack(c); 50 | 51 | A c2 = new C(5); 52 | unpack(p.stream.data, c2); 53 | assert(1000 == (cast(C)c2).num); 54 | } 55 | { 56 | Packer p; 57 | 58 | Variant v = true; 59 | p.pack(v); 60 | 61 | Variant v2 = 10; 62 | unpack(p.stream.data, v2); 63 | assert(v2 == true); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /example/map_test.d: -------------------------------------------------------------------------------- 1 | import std.conv; 2 | import std.stdio; 3 | import std.datetime; 4 | import msgpack; 5 | 6 | struct A { 7 | int x; 8 | } 9 | 10 | struct Foo 11 | { 12 | A[string] a; 13 | } 14 | 15 | void main() 16 | { 17 | Foo foo; 18 | foreach (a; 'a' .. 'z') 19 | foreach (b; 'a' .. 'z') 20 | foreach (c; 'a' .. 'z') 21 | foo.a[to!string(a) ~ to!string(b) ~ to!string(c)] = A(); 22 | 23 | auto sw = StopWatch(AutoStart.yes); 24 | ubyte[] data = msgpack.pack(foo); 25 | writeln(sw.peek.usecs); 26 | 27 | auto sw2 = StopWatch(AutoStart.yes); 28 | Foo foo1 = msgpack.unpack(data).as!(typeof(foo)); 29 | writeln(sw2.peek.usecs); 30 | 31 | assert(foo == foo1); 32 | 33 | Foo foo2; 34 | auto sw3 = StopWatch(AutoStart.yes); 35 | msgpack.unpack(data, foo2); 36 | writeln(sw3.peek.usecs); 37 | 38 | assert(foo == foo2); 39 | } 40 | -------------------------------------------------------------------------------- /example/register.d: -------------------------------------------------------------------------------- 1 | import msgpack; 2 | import std.stdio; 3 | 4 | class A 5 | { 6 | string str = "foo"; 7 | } 8 | 9 | class C : A 10 | { 11 | int num; 12 | this(int n) { num = n; } 13 | } 14 | 15 | void main() 16 | { 17 | registerClass!(C); 18 | 19 | { 20 | Packer p; 21 | A c = new C(1000); 22 | p.pack(c); 23 | 24 | A c2 = new C(5); 25 | unpack(p.stream.data, c2); 26 | assert(1000 == (cast(C)c2).num); 27 | } 28 | { 29 | Packer p; 30 | C c = new C(1000); 31 | p.pack(c); 32 | 33 | C c2 = new C(5); 34 | unpack(p.stream.data, c2); 35 | assert(1000 == (cast(C)c2).num); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/simple.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | * Serializer and Stream Deserializer usage 5 | */ 6 | 7 | import std.array; 8 | import std.stdio; 9 | 10 | import msgpack; 11 | 12 | 13 | void main() 14 | { 15 | auto packer = packer(appender!(ubyte[])()); 16 | 17 | packer.packArray(null, true, "Hi!", -1, [1, 2], '!'); 18 | 19 | auto unpacker = StreamingUnpacker(packer.stream.data); 20 | 21 | if (unpacker.execute()) { 22 | foreach (obj; unpacker.purge()) 23 | writeln(obj.type); 24 | } else { 25 | writeln("Serialized object is too large!"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/stream.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | * Stream deserialization usage 5 | */ 6 | 7 | import std.array; 8 | import std.concurrency; 9 | import std.exception; 10 | import std.stdio; 11 | 12 | import msgpack; 13 | 14 | 15 | void deserializer() 16 | { 17 | auto unpacker = StreamingUnpacker(cast(ubyte[])null); 18 | bool endLoop; 19 | 20 | while (true) { 21 | receive((immutable(ubyte)[] data) { unpacker.feed(data); }, 22 | (bool end) { endLoop = end; }); 23 | 24 | if (endLoop) 25 | break; 26 | 27 | while (unpacker.execute()) { 28 | auto unpacked = unpacker.purge(); 29 | writeln("Type: ", unpacked.type); 30 | writeln("Value: ", unpacked.as!(string)); 31 | } 32 | 33 | if (unpacker.size >= 100) 34 | throw new Exception("Too large!"); 35 | } 36 | } 37 | 38 | 39 | void main() 40 | { 41 | string message = "Hell"; 42 | foreach (i; 0..93) // Throws Exception if 94 43 | message ~= 'o'; 44 | 45 | auto packed = pack(message); 46 | auto data = packed.assumeUnique(); 47 | auto tid = spawn(&deserializer); 48 | 49 | while (!data.empty) { 50 | auto limit = data.length >= 10 ? 10 : data.length; 51 | 52 | tid.send(data[0..limit]); 53 | data = data[limit..$]; 54 | } 55 | 56 | tid.send(true); 57 | } 58 | -------------------------------------------------------------------------------- /example/unpacker_foreach.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | * Stream Deserializer with foreach. 5 | */ 6 | 7 | import std.stdio; 8 | 9 | import msgpack; 10 | 11 | 12 | void main() 13 | { 14 | // create 3 MessagePack objects([1, 0.1L], true, "foobarbaz") 15 | auto test1 = pack(1, 0.1L) ~ pack(true); 16 | auto test2 = pack("foobarbaz"); 17 | 18 | // split data to deserialize test 19 | test1 ~= test2[0..2]; 20 | test2 = test2[2..$]; 21 | 22 | auto unpacker = StreamingUnpacker(test1); 23 | 24 | foreach (unpacked; unpacker) { 25 | if (unpacked.type == Value.Type.array) { 26 | foreach (obj; unpacked) { 27 | switch (obj.type) { 28 | case Value.Type.unsigned: writeln(obj.as!(uint)); break; 29 | case Value.Type.floating: writeln(obj.as!(real)); break; 30 | default: 31 | throw new Exception("Unknown type"); 32 | } 33 | } 34 | } else { 35 | writeln(unpacked.as!(bool)); 36 | } 37 | } 38 | 39 | unpacker.feed(test2); 40 | 41 | foreach (unpacked; unpacker) 42 | writeln(unpacked.as!(string)); 43 | } 44 | -------------------------------------------------------------------------------- /html/candydoc/candy.ddoc: -------------------------------------------------------------------------------- 1 | D = $(B $0) 2 | 3 | DDOC = 4 | 5 | 6 | 7 | $(TITLE) 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | 17 | 18 | 23 |

$(TITLE)

$(BODY)
19 | Page was generated with 20 | 21 | on $(DATETIME) 22 |
24 |
25 | $(ADD_MODULES) 26 | 27 | 28 | 29 | DDOC_DECL = 30 | 31 | $(DT $0) 32 | 33 | 34 | 35 | DDOC_PSYMBOL = 36 | $0 37 | 38 | 39 | 40 | DDOC_MEMBERS = 41 | 42 | $(DL $0) 43 | 44 | 45 | 46 | DDOC_PARAM_ID = 47 | $0 48 | 49 | 50 | DDOC_PARAM =$0 51 | ADD_MODULES = 52 | MODULE =explorer.packageExplorer.addModule("$0"); 53 | -------------------------------------------------------------------------------- /html/candydoc/explorer.js: -------------------------------------------------------------------------------- 1 | /* This file is a part of CanDyDOC fileset. 2 | File is written by Victor Nakoryakov and placed into the public domain. 3 | 4 | This file is javascript with classes that represents explorer window. 5 | And things related to navigation. */ 6 | 7 | var explorer = new Explorer(); 8 | 9 | /////////////////////////////////////////////////////////////////////////////// 10 | // Current symbol marker class constructor 11 | /////////////////////////////////////////////////////////////////////////////// 12 | function Marker() 13 | { 14 | this.top = document.createElement("div"); 15 | this.middle = document.createElement("div"); 16 | this.bottom = document.createElement("div"); 17 | this.container = document.createElement("div"); 18 | 19 | this.setTo = function(term) 20 | { 21 | // find definition related to `term` 22 | var def = term.nextSibling; 23 | while (def && def.nodeName != "DD") 24 | def = def.nextSibling; 25 | 26 | var defHeight = 0; 27 | var childrenHeight = 0; // children of current declaration 28 | if (def) 29 | { 30 | defHeight = def.offsetHeight; 31 | var child = def.firstChild; 32 | 33 | // traverse until DL tag, until children definition 34 | while (child && child.nodeName != "DL") 35 | child = child.nextSibling; 36 | 37 | if (child) 38 | childrenHeight = child.offsetHeight; 39 | } 40 | 41 | this.top.style.height = term.offsetHeight; 42 | this.middle.style.height = defHeight - childrenHeight; 43 | this.bottom.style.height = childrenHeight; 44 | 45 | if (childrenHeight == 0) 46 | this.bottom.style.display = "none"; 47 | else 48 | this.bottom.style.display = ""; 49 | 50 | this.container.style.left = getLeft(term) - 8; 51 | this.container.style.top = getTop(term); 52 | this.container.style.display = ""; 53 | } 54 | 55 | /////////////////////////////////////////////////////////////////////////// 56 | this.container.style.position = "absolute"; 57 | this.container.style.display = "none"; 58 | 59 | this.top.className = "markertop"; 60 | this.middle.className = "markermiddle"; 61 | this.bottom.className = "markerbottom"; 62 | 63 | this.container.appendChild(this.top); 64 | this.container.appendChild(this.middle); 65 | this.container.appendChild(this.bottom); 66 | 67 | //document.body.appendChild( this.container ); 68 | 69 | // Workaround bug in IE 5/6. We can not append anything to document body until 70 | // full page load. 71 | window.marker = this; 72 | if (window.addEventListener) 73 | window.addEventListener("load", new Function("document.body.appendChild( window.marker.container );"), false); 74 | else if (window.attachEvent) 75 | window.attachEvent("onload", new Function("document.body.appendChild( window.marker.container );")); 76 | } 77 | 78 | /////////////////////////////////////////////////////////////////////////////// 79 | // Outline class constructor 80 | /////////////////////////////////////////////////////////////////////////////// 81 | function Outline() 82 | { 83 | this.tree = new TreeView(); 84 | this.mountPoint = null; 85 | this.writeEnabled = false; 86 | this.marker = new Marker(); 87 | this.classRegExp = new RegExp; 88 | this.structRegExp = new RegExp; 89 | this.enumRegExp = new RegExp; 90 | this.templateRegExp = new RegExp; 91 | this.aliasRegExp = new RegExp; 92 | this.funcRegExp = new RegExp; 93 | 94 | this.incSymbolLevel = function() 95 | { 96 | if (this.mountPoint == null) 97 | this.mountPoint = this.tree.children[ 0 ]; 98 | else 99 | this.mountPoint = this.mountPoint.lastChild(); 100 | } 101 | 102 | this.decSymbolLevel = function() 103 | { 104 | // place icons near items according to extracted below type 105 | for (var i = 0; i < this.mountPoint.children.length; ++i) 106 | { 107 | child = this.mountPoint.children[i]; 108 | var term = child.termRef; 109 | 110 | // find first span node 111 | var n = term.firstChild; 112 | while (n && n.nodeName != "SPAN") 113 | n = n.nextSibling; 114 | 115 | if (!n) // shouldn't happen 116 | continue; 117 | 118 | var iconSrc; 119 | if (n.firstChild.nodeName == "#text") 120 | { 121 | var text = n.firstChild.data; // text before declaration 122 | 123 | if ( this.classRegExp.test(text) ) 124 | iconSrc = "candydoc/img/outline/class.gif"; 125 | else if ( this.structRegExp.test(text) ) 126 | iconSrc = "candydoc/img/outline/struct.gif"; 127 | else if ( this.enumRegExp.test(text) ) 128 | iconSrc = "candydoc/img/outline/enum.gif"; 129 | else if ( this.templateRegExp.test(text) ) 130 | iconSrc = "candydoc/img/outline/template.gif"; 131 | else if ( this.aliasRegExp.test(text) ) 132 | iconSrc = "candydoc/img/outline/alias.gif"; 133 | else // function or variable? check whether '(' ')' exists on the right 134 | { 135 | var np = n.firstChild; 136 | while (np && np.nodeName != "SCRIPT") // find our script "onDecl" 137 | np = np.nextSibling; 138 | 139 | if (np && np.nextSibling && np.nextSibling.nodeName == "#text" && 140 | this.funcRegExp.test(np.nextSibling.data)) 141 | { 142 | iconSrc = "candydoc/img/outline/func.gif"; 143 | } 144 | else 145 | iconSrc = "candydoc/img/outline/var.gif"; 146 | } 147 | } 148 | else // enum member ? 149 | iconSrc = "candydoc/img/outline/var.gif"; 150 | 151 | child.icon.src = iconSrc; 152 | child.icon.width = 16; 153 | child.icon.height = 16; 154 | } 155 | 156 | this.mountPoint = this.mountPoint.parentNode; 157 | } 158 | 159 | this.addDecl = function(decl) 160 | { 161 | function getLastLeaf(elem) 162 | { 163 | if (elem.childNodes.length > 0) 164 | return getLastLeaf(elem.lastChild); 165 | else 166 | return elem; 167 | } 168 | 169 | function getCurrentTerm() 170 | { 171 | var ret = getLastLeaf( document.getElementById("content") ); 172 | while (ret && ret.nodeName != "DT") 173 | ret = ret.parentNode; 174 | 175 | return ret; 176 | } 177 | 178 | if (this.writeEnabled) 179 | { 180 | var node = this.mountPoint.createChild(decl); 181 | node.termRef = getCurrentTerm(); 182 | node.setOnclick( new Function("explorer.outline.mark(this.termRef);") ); 183 | } 184 | } 185 | 186 | this.mark = function(term) 187 | { 188 | this.marker.setTo(term); 189 | window.scrollTo(0, getTop(term) - getWindowHeight() / 6); 190 | } 191 | 192 | 193 | this.classRegExp.compile("(.*\b)?class(\b.*)?"); 194 | this.structRegExp.compile("(.*\b)?struct(\b.*)?"); 195 | this.enumRegExp.compile("(.*\b)?enum(\b.*)?"); 196 | this.templateRegExp.compile("(.*\b)?template(\b.*)?"); 197 | this.aliasRegExp.compile("(.*\b)?alias(\b.*)?"); 198 | this.funcRegExp.compile(/.*\(.*/); 199 | } 200 | 201 | 202 | 203 | 204 | /////////////////////////////////////////////////////////////////////////////// 205 | // Package explorer class constructor 206 | /////////////////////////////////////////////////////////////////////////////// 207 | function PackageExplorer() 208 | { 209 | this.tree = new TreeView(true); 210 | 211 | this.addModule = function(mod) 212 | { 213 | var moduleIco = "candydoc/img/outline/module.gif"; 214 | var packageIco = "candydoc/img/outline/package.gif"; 215 | 216 | var path = mod.split("\."); 217 | var node = this.tree.branch(path[0]); 218 | if ( !node ) 219 | node = this.tree.createBranch(path[0], (path.length == 1) ? moduleIco : packageIco); 220 | 221 | for (var i = 1; i < path.length; ++i) 222 | { 223 | var prev = node; 224 | node = node.child(path[i]); 225 | if (!node) 226 | node = prev.createChild(path[i], (path.length == i + 1) ? moduleIco : packageIco); 227 | 228 | if (path.length == i + 1) 229 | node.setRef(path[i] + ".html"); 230 | } 231 | } 232 | } 233 | 234 | 235 | 236 | /////////////////////////////////////////////////////////////////////////////// 237 | // Explorer class constructor 238 | /////////////////////////////////////////////////////////////////////////////// 239 | function Explorer() 240 | { 241 | this.outline = new Outline(); 242 | this.packageExplorer = new PackageExplorer(); 243 | this.tabs = new Array(); 244 | this.tabCount = 0; 245 | 246 | this.initialize = function(moduleName) 247 | { 248 | this.tabArea = document.getElementById("tabarea"); 249 | this.clientArea = document.getElementById("explorerclient"); 250 | 251 | // prevent text selection 252 | this.tabArea.onmousedown = new Function("return false;"); 253 | this.tabArea.onclick = new Function("return true;"); 254 | this.tabArea.onselectstart = new Function("return false;"); 255 | this.clientArea.onmousedown = new Function("return false;"); 256 | this.clientArea.onclick = new Function("return true;"); 257 | this.clientArea.onselectstart = new Function("return false;"); 258 | 259 | this.outline.tree.createBranch( moduleName, "candydoc/img/outline/module.gif" ); 260 | 261 | // create tabs 262 | this.createTab("Outline", this.outline.tree.domEntry); 263 | this.createTab("Package", this.packageExplorer.tree.domEntry); 264 | } 265 | 266 | this.createTab = function(name, domEntry) 267 | { 268 | var tab = new Object(); 269 | this.tabs[name] = tab; 270 | this.tabCount++; 271 | 272 | tab.domEntry = domEntry; 273 | tab.labelSpan = document.createElement("span"); 274 | 275 | if (this.tabCount > 1) 276 | { 277 | tab.labelSpan.className = "inactivetab"; 278 | tab.domEntry.style.display = "none"; 279 | } 280 | else 281 | { 282 | tab.labelSpan.className = "activetab"; 283 | tab.domEntry.style.display = ""; 284 | } 285 | 286 | tab.labelSpan.appendChild( document.createTextNode(name) ); 287 | tab.labelSpan.owner = this; 288 | tab.labelSpan.onclick = new Function("this.owner.setSelection('" + name + "');"); 289 | 290 | this.tabArea.appendChild( tab.labelSpan ); 291 | this.clientArea.appendChild( domEntry ); 292 | } 293 | 294 | this.setSelection = function(tabName) 295 | { 296 | for (name in this.tabs) 297 | { 298 | this.tabs[name].labelSpan.className = "inactivetab"; 299 | this.tabs[name].domEntry.style.display = "none"; 300 | } 301 | 302 | this.tabs[tabName].labelSpan.className = "activetab"; 303 | this.tabs[tabName].domEntry.style.display = ""; 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /html/candydoc/ie56hack.css: -------------------------------------------------------------------------------- 1 | /* This file is a part of CanDyDOC fileset. 2 | File is written by Victor Nakoryakov and placed into the public domain. 3 | 4 | This file is CSS to work around IE6 and earlier bugs. It's included just 5 | in these browsers. */ 6 | 7 | 8 | /* Some magic to emulate unsupported "position: fixed" style. */ 9 | #tabarea 10 | { 11 | _position: absolute; 12 | _top: expression(eval(document.body.scrollTop+8)); 13 | } 14 | 15 | /* ditto */ 16 | #explorerclient 17 | { 18 | _position: absolute; 19 | _top: expression(eval(document.body.scrollTop+24)); 20 | _height: expression(eval(document.body.clientHeight-48)); 21 | } 22 | -------------------------------------------------------------------------------- /html/candydoc/img/bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/bg.gif -------------------------------------------------------------------------------- /html/candydoc/img/candydoc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/candydoc.gif -------------------------------------------------------------------------------- /html/candydoc/img/outline/alias.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/outline/alias.gif -------------------------------------------------------------------------------- /html/candydoc/img/outline/bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/outline/bg.gif -------------------------------------------------------------------------------- /html/candydoc/img/outline/class.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/outline/class.gif -------------------------------------------------------------------------------- /html/candydoc/img/outline/enum.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/outline/enum.gif -------------------------------------------------------------------------------- /html/candydoc/img/outline/func.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/outline/func.gif -------------------------------------------------------------------------------- /html/candydoc/img/outline/module.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/outline/module.gif -------------------------------------------------------------------------------- /html/candydoc/img/outline/package.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/outline/package.gif -------------------------------------------------------------------------------- /html/candydoc/img/outline/struct.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/outline/struct.gif -------------------------------------------------------------------------------- /html/candydoc/img/outline/template.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/outline/template.gif -------------------------------------------------------------------------------- /html/candydoc/img/outline/var.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/outline/var.gif -------------------------------------------------------------------------------- /html/candydoc/img/package/bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/package/bg.gif -------------------------------------------------------------------------------- /html/candydoc/img/tree/shim.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/tree/shim.gif -------------------------------------------------------------------------------- /html/candydoc/img/tree/tb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/tree/tb.gif -------------------------------------------------------------------------------- /html/candydoc/img/tree/tbr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/tree/tbr.gif -------------------------------------------------------------------------------- /html/candydoc/img/tree/tbrm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/tree/tbrm.gif -------------------------------------------------------------------------------- /html/candydoc/img/tree/tbrp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/tree/tbrp.gif -------------------------------------------------------------------------------- /html/candydoc/img/tree/tr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/tree/tr.gif -------------------------------------------------------------------------------- /html/candydoc/img/tree/trm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/tree/trm.gif -------------------------------------------------------------------------------- /html/candydoc/img/tree/trp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack/msgpack-d/26ef07e16023483ad93e3f86374b19d0e541c924/html/candydoc/img/tree/trp.gif -------------------------------------------------------------------------------- /html/candydoc/modules.ddoc: -------------------------------------------------------------------------------- 1 | MODULES = 2 | $(MODULE msgpack.buffer) 3 | $(MODULE msgpack.common) 4 | $(MODULE msgpack.msgpack) 5 | $(MODULE msgpack.object) 6 | $(MODULE msgpack.packer) 7 | $(MODULE msgpack.unpacker) 8 | $(MODULE msgpack.util) 9 | -------------------------------------------------------------------------------- /html/candydoc/style.css: -------------------------------------------------------------------------------- 1 | /* This file is a part of CanDyDOC fileset. 2 | File is written by Victor Nakoryakov and placed into the public domain. 3 | 4 | This file is main CSS file of CanDyDOC. You may adjust some part of 5 | parameters to control how result documentation would looks like. See 6 | further documentation for details. */ 7 | 8 | 9 | 10 | /* This controls how background would looks like and 11 | sets some document-scope defaults. */ 12 | body 13 | { 14 | /* These parameters control default font. */ 15 | font-family: Verdana, Arial, Helvetica, sans-serif; 16 | font-size: 10pt; 17 | color: #666666; 18 | 19 | /* These control look of background. Note that you have to use 20 | fixed background to keep documentation good-looking in 21 | IE6 and earlier. Otherwise whole *explorer* will jerk while 22 | scrolling. If you do not want to use background at all use 23 | some invalid url, e.g. url(foo). */ 24 | background-color: #e6fcea; 25 | background: url(img/bg.gif) fixed; 26 | 27 | /* Don't touch. Necessary for IE6 and earlier. */ 28 | height: 100%; 29 | } 30 | 31 | 32 | 33 | /* Style applied to all tables. Actualy there are two: one table is 34 | that contains contant and footer with CanDyDOC logo, and others 35 | are that contains functions' parameters description. */ 36 | table 37 | { 38 | font-family: Verdana, Arial, Helvetica, sans-serif; 39 | font-size: 10pt; 40 | color: #666666; 41 | text-align: justify; 42 | } 43 | 44 | 45 | /* Style used for all hyperlinks. */ 46 | a:link { color: #009900; text-decoration: none } 47 | a:visited { color: #009999; text-decoration: none } 48 | a:hover { color: #0033cc; text-decoration: none } 49 | a:active { color: #0033cc; text-decoration: none } 50 | 51 | /* 52 | table.matrix 53 | { 54 | border-left: double 3px #666666; 55 | border-right: double 3px #666666; 56 | margin-left: 3em; 57 | } 58 | */ 59 | 60 | /* Style appled to declarations. E.g. 'void foo(int a, float b);' */ 61 | span.decl { font-size: 10pt; font-weight: bold; color: #000000; text-align: left } 62 | /* Style appled to current declaration's symbol. E.g. 'foo' in 'void foo(int a, float b);' */ 63 | span.currsymbol { font-size: 12pt; color: #009900 } 64 | /* Style appled to function's parameters. E.g. 'a' and 'b' in 'void foo(int a, float b);' */ 65 | span.funcparam { font-style: italic; font-weight: normal; color: #331200 } 66 | 67 | /* Style for div that actualy contains documenation. */ 68 | #content 69 | { 70 | padding-right: 8px; 71 | position: absolute; 72 | left: 245px; 73 | top: 8px; 74 | text-align: justify; 75 | } 76 | 77 | /* Style for table that is inside div considered above. Contains documentaton 78 | itself and footer with CanDyDOC logo. */ 79 | table.content 80 | { 81 | margin-bottom: 8px; 82 | border-spacing: 0px; 83 | border-collapse: collapse; 84 | background-color: #ffffff; 85 | } 86 | 87 | /* Style for cell of above considered table that contains documentation itself. */ 88 | #docbody 89 | { 90 | padding: 8px 20px 8px 20px; 91 | border: solid 1px #009900; 92 | } 93 | 94 | /* Style for cell that contains CanDyDOC logo. */ 95 | #docfooter 96 | { 97 | height: 16px; 98 | background-color: #ddeedd; 99 | padding: 0px 8px 0px 8px; 100 | border: solid 1px #009900; 101 | } 102 | 103 | /* Style applied to currently active tab of explorer window. */ 104 | span.activetab 105 | { 106 | background-color: #0033cc; 107 | border-top: solid 2px #009900; 108 | color: #ffffff; 109 | font-weight: bold; 110 | padding-left: 4px; 111 | padding-right: 4px; 112 | padding-top: 1px; 113 | margin-right: 1px; 114 | } 115 | 116 | /* Style applied to currently inactive tab of explorer window. */ 117 | span.inactivetab 118 | { 119 | background-color: #000066; 120 | color: #cccccc; 121 | font-weight: normal; 122 | padding-left: 4px; 123 | padding-right: 4px; 124 | padding-top: 0px; 125 | margin-right: 1px; 126 | } 127 | 128 | /* Style applied to div that contains tabs of explorer. Note that if 129 | you want to change it's position you have to change position of 130 | #explorerclient, #content and corresponding values in ie56hack.css */ 131 | #tabarea 132 | { 133 | position: fixed; 134 | top: 8px; 135 | width: 205px; 136 | height: 16px; 137 | cursor: default; 138 | } 139 | 140 | 141 | /* Style applied to div that contains tree in explorer. Note that if 142 | you want to change it's position you have to change position of 143 | #tabarea, #content and corresponding values in ie56hack.css */ 144 | #explorerclient 145 | { 146 | position: fixed; 147 | top: 24px; 148 | bottom: 8px; 149 | width: 205px; 150 | overflow: auto; 151 | background-color: #fcfffc; 152 | border: solid 2px #0033cc; 153 | padding: 4px; 154 | cursor: default; 155 | color: Black; 156 | } 157 | 158 | /* Following 3 styles control appearance of marker that appears 159 | if you click some entity in outline window. */ 160 | div.markertop { border-left: solid 2px #0033cc;} 161 | div.markermiddle{ border-left: dotted 2px #0033cc;} 162 | div.markerbottom{ border-left: dotted 2px #66cc66;} 163 | 164 | /* Style applied to preformated text used to show examples. */ 165 | pre.d_code 166 | { 167 | border: dotted 1px #9c9; 168 | background-color: #eeffee; 169 | } -------------------------------------------------------------------------------- /html/candydoc/tree.js: -------------------------------------------------------------------------------- 1 | /* This file is a part of CanDyDOC fileset. 2 | File is written by Victor Nakoryakov and placed into the public domain. 3 | 4 | This file is javascript with classes that represents native style tree control. */ 5 | 6 | var pmNone = 0; 7 | var pmPlus = 1; 8 | var pmMinus = 2; 9 | 10 | var hlNone = 0; 11 | var hlGrey = 1; 12 | var hlSelected = 2; 13 | 14 | function TreeView(hrefMode) 15 | { 16 | this.domEntry = document.createElement("div"); 17 | this.children = new Array(); 18 | this.selection = null; 19 | this.hrefMode = hrefMode; 20 | 21 | this.createBranch = function(text, iconSrc) 22 | { 23 | var root = new TreeNode(text, iconSrc, this.hrefMode); 24 | root.owner = this; 25 | this.children[ this.children.length ] = root; 26 | this.domEntry.appendChild( root.domEntry ); 27 | return root; 28 | } 29 | 30 | this.branch = function(text) 31 | { 32 | var ret = null; 33 | for (var i = 0; i < this.children.length; ++i) 34 | if (this.children[i].textElement.data == text) 35 | { 36 | ret = this.children[i]; 37 | break; 38 | } 39 | 40 | return ret; 41 | } 42 | 43 | this.domEntry.style.fontSize = "10px"; 44 | this.domEntry.style.cursor = "default"; 45 | this.domEntry.style.whiteSpace = "nowrap"; 46 | } 47 | 48 | var idCounter = 0; 49 | function TreeNode(text, iconSrc, hrefMode) 50 | { 51 | this.id = idCounter++; 52 | this.parentNode = null; 53 | this.children = new Array(); 54 | this.domEntry = document.createElement("div"); 55 | this.icon = document.createElement("img"); 56 | this.textElement = document.createTextNode(text); 57 | this.textSpan = document.createElement("span"); 58 | this.lineDiv = document.createElement("div"); 59 | this.hierarchyImgs = new Array(); 60 | this.onclick = null; 61 | 62 | function createIcon() 63 | { 64 | var img = document.createElement("img"); 65 | img.style.verticalAlign = "middle"; 66 | img.style.position = "relative"; 67 | img.style.top = "-1px"; 68 | img.width = 16; 69 | img.height = 16; 70 | return img; 71 | } 72 | 73 | function createHierarchyImage() 74 | { 75 | var img = createIcon(); 76 | img.pointsTop = false; 77 | img.pointsBottom = false; 78 | img.pointsRight = false; 79 | img.pmState = pmNone; 80 | return img; 81 | } 82 | 83 | function genHierarchyImageSrc(hierarchyImg) 84 | { 85 | var name = ""; 86 | if (hierarchyImg.pointsTop) 87 | name += "t"; 88 | 89 | if (hierarchyImg.pointsBottom) 90 | name += "b"; 91 | 92 | if (hierarchyImg.pointsRight) 93 | name += "r"; 94 | 95 | if (hierarchyImg.pmState == pmPlus) 96 | name += "p"; 97 | else if (hierarchyImg.pmState == pmMinus) 98 | name += "m"; 99 | 100 | if (name == "") 101 | name = "shim"; 102 | 103 | return "candydoc/img/tree/" + name + ".gif"; 104 | } 105 | 106 | function setSrc(icon, src) 107 | { 108 | icon.src = src; 109 | // After src change width and height are reseted in IE. 110 | // Bug workaround: 111 | icon.width = 16; 112 | icon.height = 16; 113 | } 114 | 115 | this.createChild = function(text, iconSrc) 116 | { 117 | var child = new TreeNode(text, iconSrc, this.owner.hrefMode); 118 | this.children[ this.children.length ] = child; 119 | this.domEntry.appendChild( child.domEntry ); 120 | child.parentNode = this; 121 | child.owner = this.owner; 122 | 123 | // insert hierarchy images according to deepness level 124 | // of created child. 125 | 126 | if (this.children.length > 1) 127 | { 128 | // there were already added child before. So copy `level-1` 129 | // hierarchy images from it. 130 | 131 | var prevAddedChild = this.children[ this.children.length - 2 ]; 132 | 133 | for (var i = 0; i < prevAddedChild.hierarchyImgs.length - 1; ++i) 134 | { 135 | var prevAddedChildImg = prevAddedChild.hierarchyImgs[i]; 136 | var img = createHierarchyImage(); 137 | setSrc(img, prevAddedChildImg.src); 138 | img.pointsTop = prevAddedChildImg.pointsTop; 139 | img.pointsBottom = prevAddedChildImg.pointsBottom; 140 | img.pointsRight = prevAddedChildImg.pointsRight; 141 | img.pmState = prevAddedChildImg.pmState; 142 | 143 | child.hierarchyImgs[ child.hierarchyImgs.length ] = img; 144 | child.lineDiv.insertBefore(img, child.icon); 145 | } 146 | 147 | // change last hierarchy image of prevAddedChild from |_ to |- 148 | var lastHierarchyImg = prevAddedChild.hierarchyImgs[ prevAddedChild.hierarchyImgs.length - 1 ]; 149 | lastHierarchyImg.pointsBottom = true; 150 | setSrc(lastHierarchyImg, genHierarchyImageSrc(lastHierarchyImg)); 151 | 152 | // change hierarchy images of prevAddedChild's children on it's last 153 | // level to | 154 | prevAddedChild.addHierarchyTBLine(prevAddedChild.hierarchyImgs.length - 1); 155 | } 156 | else 157 | { 158 | // this is a first child. So copy `level-2` 159 | // hierarchy images from parent, i.e. this. 160 | 161 | for (var i = 0; i < this.hierarchyImgs.length - 1; ++i) 162 | { 163 | var parentImg = this.hierarchyImgs[i]; 164 | var img = createHierarchyImage(); 165 | setSrc(img, parentImg.src); 166 | img.pointsTop = parentImg.pointsTop; 167 | img.pointsBottom = parentImg.pointsBottom; 168 | img.pointsRight = parentImg.pointsRight; 169 | img.pmState = parentImg.pmState; 170 | 171 | child.hierarchyImgs[ child.hierarchyImgs.length ] = img; 172 | child.lineDiv.insertBefore(img, child.icon); 173 | } 174 | 175 | if (this.hierarchyImgs.length > 0) // we are not root 176 | { 177 | // change last hierarchy image of parent (i.e. this): add minus to it 178 | var lastHierarchyImg = this.hierarchyImgs[ this.hierarchyImgs.length - 1]; 179 | lastHierarchyImg.pmState = pmMinus; 180 | setSrc(lastHierarchyImg, genHierarchyImageSrc(lastHierarchyImg)); 181 | lastHierarchyImg.owner = this; 182 | lastHierarchyImg.onclick = new Function("e", "this.owner.processPMClick(e);"); 183 | 184 | // make decision on image on `level-1`. It depends on parent's (ie this) 185 | // image on same level. 186 | var parentL1HierarchyImg = lastHierarchyImg; 187 | var l1HierarchyImg = createHierarchyImage(); 188 | if (parentL1HierarchyImg.pointsBottom) 189 | { 190 | l1HierarchyImg.pointsTop = true; 191 | l1HierarchyImg.pointsBottom = true; 192 | } 193 | setSrc(l1HierarchyImg, genHierarchyImageSrc(l1HierarchyImg)); 194 | child.hierarchyImgs[ child.hierarchyImgs.length ] = l1HierarchyImg; 195 | child.lineDiv.insertBefore(l1HierarchyImg, child.icon); 196 | } 197 | } 198 | 199 | // in any case on last level our child will have icon |_ 200 | var img = createHierarchyImage(); 201 | img.pointsTop = true; 202 | img.pointsRight = true; 203 | setSrc(img, genHierarchyImageSrc(img)); 204 | 205 | child.hierarchyImgs[ child.hierarchyImgs.length ] = img; 206 | child.lineDiv.insertBefore(img, child.icon); 207 | 208 | return child; 209 | } 210 | 211 | this.lastChild = function() 212 | { 213 | return this.children[ this.children.length - 1 ]; 214 | } 215 | 216 | this.child = function(text) 217 | { 218 | var ret = null; 219 | for (var i = 0; i < this.children.length; ++i) 220 | if (this.children[i].textElement.data == text) 221 | { 222 | ret = this.children[i]; 223 | break; 224 | } 225 | 226 | return ret; 227 | } 228 | 229 | this.addHierarchyTBLine = function(level) 230 | { 231 | for (var i = 0; i < this.children.length; ++i) 232 | { 233 | var img = this.children[i].hierarchyImgs[level]; 234 | img.pointsTop = true; 235 | img.pointsBottom = true; 236 | setSrc(img, genHierarchyImageSrc(img)); 237 | this.children[i].addHierarchyTBLine(level); 238 | } 239 | } 240 | 241 | this.expand = function() 242 | { 243 | var img = this.hierarchyImgs[ this.hierarchyImgs.length - 1 ]; 244 | 245 | if (img.pmState == pmPlus) 246 | { 247 | img.pmState = pmMinus; 248 | setSrc(img, genHierarchyImageSrc(img)); 249 | 250 | for (var i = 0; i < this.children.length; ++i) 251 | this.children[i].domEntry.style.display = ""; 252 | } 253 | } 254 | 255 | this.collapse = function() 256 | { 257 | var img = this.hierarchyImgs[ this.hierarchyImgs.length - 1 ]; 258 | 259 | if (img.pmState == pmMinus) 260 | { 261 | img.pmState = pmPlus; 262 | setSrc(img, genHierarchyImageSrc(img)); 263 | 264 | for (var i = 0; i < this.children.length; ++i) 265 | this.children[i].domEntry.style.display = "none"; 266 | } 267 | } 268 | 269 | this.toggle = function() 270 | { 271 | var img = this.hierarchyImgs[ this.hierarchyImgs.length - 1 ]; 272 | if (img.pmState == pmMinus) 273 | this.collapse(); 274 | else 275 | this.expand(); 276 | } 277 | 278 | this.select = function() 279 | { 280 | if (this.owner.selection != this) 281 | { 282 | if (this.owner.selection) 283 | this.owner.selection.setHighlight(hlNone); 284 | 285 | this.owner.selection = this; 286 | this.setHighlight(hlSelected); 287 | } 288 | } 289 | 290 | this.setHighlight = function(mode) 291 | { 292 | if (mode == hlNone) 293 | { 294 | this.textSpan.style.backgroundColor = ""; 295 | this.textSpan.style.color = ""; 296 | this.textSpan.style.border = ""; 297 | } 298 | else if (mode == hlGrey) 299 | { 300 | this.textSpan.style.backgroundColor = "#aaaaaa"; 301 | this.textSpan.style.color = ""; 302 | this.textSpan.style.border = ""; 303 | } 304 | else if (mode == hlSelected) 305 | { 306 | this.textSpan.style.backgroundColor = "3399cc"; 307 | this.textSpan.style.color = "white"; 308 | this.textSpan.style.border = "dotted 1px red"; 309 | } 310 | } 311 | 312 | this.setOnclick = function(proc) 313 | { 314 | this.onclick = proc; 315 | } 316 | 317 | this.setRef = function(url) 318 | { 319 | if (this.anchor) 320 | this.anchor.href = url; 321 | } 322 | 323 | this.processPMClick = function(e) 324 | { 325 | this.toggle(); 326 | 327 | // prevent this line selection, stop bubbling 328 | if (e) 329 | e.stopPropagation(); // Mozilla way 330 | if (window.event) 331 | window.event.cancelBubble = true; // IE way 332 | } 333 | 334 | this.processOnclick = function() 335 | { 336 | this.select(); 337 | if (this.onclick instanceof Function) 338 | this.onclick(); 339 | } 340 | 341 | /////////////////////////////////////////////////////////////////////////// 342 | if (iconSrc) 343 | this.icon.src = iconSrc; 344 | else 345 | { 346 | this.icon.width = 0; 347 | this.icon.height = 0; 348 | } 349 | 350 | this.icon.style.verticalAlign = "middle"; 351 | this.icon.style.position = "relative"; 352 | this.icon.style.top = "-1px"; 353 | this.icon.style.paddingRight = "2px"; 354 | 355 | if (!hrefMode) 356 | { 357 | this.textSpan.appendChild( this.textElement ); 358 | } 359 | else 360 | { 361 | this.anchor = document.createElement("a"); 362 | this.anchor.appendChild( this.textElement ); 363 | this.textSpan.appendChild( this.anchor ); 364 | } 365 | 366 | this.lineDiv.appendChild( this.icon ); 367 | this.lineDiv.appendChild( this.textSpan ); 368 | this.domEntry.appendChild( this.lineDiv ); 369 | 370 | this.lineDiv.owner = this; 371 | 372 | if (!hrefMode) 373 | this.lineDiv.onclick = new Function("this.owner.processOnclick();"); 374 | } 375 | -------------------------------------------------------------------------------- /html/candydoc/util.js: -------------------------------------------------------------------------------- 1 | /* This file is a part of CanDyDOC fileset. 2 | File is written by Victor Nakoryakov and placed into the public domain. 3 | 4 | This file is javascript with cross-browser utility functions. */ 5 | 6 | function getLeft(elem) 7 | { 8 | var ret = 0; 9 | while (elem.offsetParent) 10 | { 11 | ret += elem.offsetLeft; 12 | elem = elem.offsetParent; 13 | } 14 | 15 | return ret; 16 | } 17 | 18 | function getTop(elem) 19 | { 20 | var ret = 0; 21 | while (elem.offsetParent) 22 | { 23 | ret += elem.offsetTop; 24 | elem = elem.offsetParent; 25 | } 26 | 27 | return ret; 28 | } 29 | 30 | function getWindowHeight() 31 | { 32 | var ret = 0; 33 | if (typeof(window.innerHeight) == "number") 34 | ret = window.innerHeight; 35 | else if (document.documentElement && document.documentElement.clientHeight) 36 | ret = document.documentElement.clientHeight; 37 | else if (document.body && document.body.clientHeight) 38 | ret = document.body.clientHeight; 39 | 40 | return ret; 41 | } 42 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('msgpack-d', 'd', 2 | meson_version: '>=0.47', 3 | license: 'BSL-1.0', 4 | version: '1.0.4' 5 | ) 6 | 7 | project_soversion = '1' 8 | 9 | pkgc = import('pkgconfig') 10 | 11 | # 12 | # Sources 13 | # 14 | msgpack_src = [ 15 | 'src/msgpack/attribute.d', 16 | 'src/msgpack/buffer.d', 17 | 'src/msgpack/common.d', 18 | 'src/msgpack/exception.d', 19 | 'src/msgpack/package.d', 20 | 'src/msgpack/packer.d', 21 | 'src/msgpack/register.d', 22 | 'src/msgpack/streaming_unpacker.d', 23 | 'src/msgpack/unpacker.d', 24 | 'src/msgpack/value.d', 25 | ] 26 | 27 | src_dir = include_directories('src/') 28 | 29 | # 30 | # Targets 31 | # 32 | msgpack_lib = library('msgpack-d', 33 | [msgpack_src], 34 | include_directories: [src_dir], 35 | install: true, 36 | version: meson.project_version(), 37 | soversion: project_soversion, 38 | ) 39 | 40 | pkgc.generate(name: 'msgpack-d', 41 | libraries: [msgpack_lib], 42 | subdirs: 'd/msgpack', 43 | version: meson.project_version(), 44 | description: 'Library for lexing and parsing D source code.' 45 | ) 46 | 47 | # for use by others which embed this as subproject 48 | msgpack_dep = declare_dependency( 49 | link_with: [msgpack_lib], 50 | include_directories: [src_dir] 51 | ) 52 | 53 | # 54 | # Tests 55 | # 56 | if meson.get_compiler('d').get_id() == 'llvm' 57 | extra_args = ['-main', '-link-defaultlib-shared'] 58 | else 59 | extra_args = ['-main'] 60 | endif 61 | 62 | msgpack_test_exe = executable('test_msgpack', 63 | [msgpack_src], 64 | include_directories: [src_dir], 65 | d_unittest: true, 66 | link_args: extra_args, 67 | ) 68 | test('test_msgpack', msgpack_test_exe) 69 | 70 | # 71 | # Install 72 | # 73 | install_subdir('src/msgpack/', install_dir: 'include/d/msgpack/') 74 | -------------------------------------------------------------------------------- /posix.mak: -------------------------------------------------------------------------------- 1 | # build mode: 32bit or 64bit 2 | MODEL ?= $(shell getconf LONG_BIT) 3 | 4 | ifeq (,$(DMD)) 5 | DMD := dmd 6 | endif 7 | 8 | LIB = libmsgpack-d.a 9 | DFLAGS = -Isrc -m$(MODEL) -d -w -dip25 -dip1000 10 | 11 | ifeq (true, $(EnableReal)) 12 | DFLAGS += -version=EnableReal 13 | endif 14 | 15 | ifeq ($(BUILD),debug) 16 | DFLAGS += -g -debug 17 | else 18 | DFLAGS += -O -release -nofloat -inline -noboundscheck 19 | endif 20 | 21 | NAMES = attribute common package register unpacker buffer exception packer streaming_unpacker value 22 | FILES = $(addsuffix .d, $(NAMES)) 23 | SRCS = $(addprefix src/msgpack/, $(FILES)) 24 | 25 | # DDoc 26 | DOCS = $(addsuffix .html, $(NAMES)) 27 | DOCDIR = html 28 | CANDYDOC = $(addprefix html/candydoc/, candy.ddoc modules.ddoc) 29 | DDOCFLAGS = -Dd$(DOCDIR) -c -o- -Isrc $(CANDYDOC) 30 | 31 | target: doc $(LIB) 32 | 33 | $(LIB): 34 | $(DMD) $(DFLAGS) -lib -of$(LIB) $(SRCS) 35 | 36 | doc: 37 | $(DMD) $(DDOCFLAGS) $(SRCS) 38 | 39 | clean: 40 | rm $(addprefix $(DOCDIR)/, $(DOCS)) $(LIB) 41 | 42 | MAIN_FILE = "empty_msgpack_unittest.d" 43 | 44 | unittest: 45 | echo 'import msgpack; void main(){}' > $(MAIN_FILE) 46 | $(DMD) $(DFLAGS) -unittest -of$(LIB) $(SRCS) -run $(MAIN_FILE) 47 | rm $(MAIN_FILE) 48 | 49 | run_examples: 50 | echo example/* | xargs -n 1 $(DMD) src/msgpack/*.d $(DFLAGS) -Isrc -run 51 | -------------------------------------------------------------------------------- /src/msgpack/attribute.d: -------------------------------------------------------------------------------- 1 | module msgpack.attribute; 2 | 3 | import std.typetuple; // will use std.meta 4 | import std.traits; 5 | 6 | 7 | /** 8 | * Attribute for specifying non pack/unpack field. 9 | * This is an alternative approach of MessagePackable mixin. 10 | * 11 | * Example: 12 | * ----- 13 | * struct S 14 | * { 15 | * int num; 16 | * // Packer/Unpacker ignores this field; 17 | * @nonPacked string str; 18 | * } 19 | * ----- 20 | */ 21 | struct nonPacked {} 22 | 23 | 24 | package template isPackedField(alias field) 25 | { 26 | enum isPackedField = (staticIndexOf!(nonPacked, __traits(getAttributes, field)) == -1) && (!isSomeFunction!(typeof(field))); 27 | } 28 | 29 | 30 | /** 31 | * Attribute for specifying serialize/deserialize proxy for pack/unpack field. 32 | * This is an alternative approach of registerPackHandler/registerUnpackHandler. 33 | * 34 | * Example: 35 | * ----- 36 | * struct Proxy 37 | * { 38 | * import std.conv; 39 | * static void serialize(ref Packer p, ref int val) { p.pack(to!string(val)); } 40 | * static void deserialize(ref Unpacker u, ref int val) { string tmp; u.unpack(tmp); val = to!int(tmp); } 41 | * } 42 | * struct S 43 | * { 44 | * // The Packer/Unpacker proxy handler is applied this field. 45 | * @serializedAs!Proxy int num; 46 | * string str; 47 | * } 48 | * ----- 49 | */ 50 | struct serializedAs(T){} 51 | 52 | package enum bool isSerializedAs(A) = is(A : serializedAs!T, T); 53 | package alias getSerializedAs(T : serializedAs!Proxy, Proxy) = Proxy; 54 | package alias ProxyList(alias value) = staticMap!(getSerializedAs, Filter!(isSerializedAs, __traits(getAttributes, value))); 55 | package template isSerializedAs(alias value) 56 | { 57 | static if ( __traits(compiles, __traits(getAttributes, value)) ) { 58 | enum bool isSerializedAs = ProxyList!value.length > 0; 59 | } else { 60 | enum bool isSerializedAs = false; 61 | } 62 | } 63 | package template getSerializedAs(alias value) 64 | { 65 | private alias _list = ProxyList!value; 66 | static assert(_list.length <= 1, `Only single serialization proxy is allowed`); 67 | alias getSerializedAs = _list[0]; 68 | } 69 | package template hasSerializedAs(alias value) 70 | { 71 | private enum _listLength = ProxyList!value.length; 72 | static assert(_listLength <= 1, `Only single serialization proxy is allowed`); 73 | enum bool hasSerializedAs = _listLength == 1; 74 | } 75 | 76 | unittest 77 | { 78 | import msgpack.packer, msgpack.unpacker; 79 | struct Proxy 80 | { 81 | static void serialize(ref Packer p, ref int value) {} 82 | static void deserialize(ref Unpacker u, ref int value) {} 83 | } 84 | struct A 85 | { 86 | @serializedAs!Proxy int a; 87 | @(42) int b; 88 | @(42) @serializedAs!Proxy int c; 89 | } 90 | static assert(is(getSerializedAs!(A.a) == Proxy)); 91 | static assert(isSerializedAs!(__traits(getAttributes, A.a)[0])); 92 | static assert(hasSerializedAs!(A.a)); 93 | static assert(!hasSerializedAs!(A.b)); 94 | static assert(hasSerializedAs!(A.c)); 95 | } 96 | -------------------------------------------------------------------------------- /src/msgpack/buffer.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | module msgpack.buffer; 4 | 5 | //import std.traits; 6 | import std.range; 7 | 8 | 9 | version(Posix) 10 | { 11 | import core.sys.posix.sys.uio : iovec; 12 | } 13 | else 14 | { 15 | /** 16 | * from core.sys.posix.sys.uio.iovec for compatibility with posix. 17 | */ 18 | struct iovec 19 | { 20 | void* iov_base; 21 | size_t iov_len; 22 | } 23 | } 24 | 25 | 26 | /** 27 | * $(D RefBuffer) is a reference stored buffer for more efficient serialization 28 | * 29 | * Example: 30 | * ----- 31 | * auto packer = packer(RefBuffer(16)); // threshold is 16 32 | * 33 | * // packs data 34 | * 35 | * writev(fd, cast(void*)packer.buffer.vector.ptr, packer.buffer.vector.length); 36 | * ----- 37 | */ 38 | struct RefBuffer 39 | { 40 | private: 41 | static struct Chunk 42 | { 43 | ubyte[] data; // storing serialized value 44 | size_t used; // used size of data 45 | } 46 | 47 | immutable size_t Threshold; 48 | immutable size_t ChunkSize; 49 | 50 | // for putCopy 51 | Chunk[] chunks_; // memory chunk for buffer 52 | size_t index_; // index for cunrrent chunk 53 | 54 | // for putRef 55 | iovec[] vecList_; // reference to large data or copied data. 56 | 57 | 58 | public: 59 | /** 60 | * Constructs a buffer. 61 | * 62 | * Params: 63 | * threshold = the threshold of writing value or stores reference. 64 | * chunkSize = the default size of chunk for allocation. 65 | */ 66 | @safe 67 | this(in size_t threshold, in size_t chunkSize = 8192) 68 | { 69 | Threshold = threshold; 70 | ChunkSize = chunkSize; 71 | 72 | chunks_.length = 1; 73 | chunks_[index_].data.length = chunkSize; 74 | } 75 | 76 | 77 | /** 78 | * Returns the buffer contents that excluding references. 79 | * 80 | * Returns: 81 | * the non-contiguous copied contents. 82 | */ 83 | @property @safe 84 | nothrow ubyte[] data() 85 | { 86 | ubyte[] result; 87 | 88 | foreach (ref chunk; chunks_) 89 | result ~= chunk.data[0..chunk.used]; 90 | 91 | return result; 92 | } 93 | 94 | 95 | /** 96 | * Forwards to all buffer contents. 97 | * 98 | * Returns: 99 | * the array of iovec struct that stores references. 100 | */ 101 | @property @safe 102 | nothrow ref iovec[] vector() return 103 | { 104 | return vecList_; 105 | } 106 | 107 | 108 | /** 109 | * Writes the argument to buffer and stores the reference of writed content 110 | * if the argument size is smaller than threshold, 111 | * otherwise stores the reference of argument directly. 112 | * 113 | * Params: 114 | * value = the content to write. 115 | */ 116 | @safe 117 | void put(in ubyte value) 118 | { 119 | ubyte[1] values = [value]; 120 | putCopy(values); 121 | } 122 | 123 | 124 | /// ditto 125 | @safe 126 | void put(in ubyte[] value) 127 | { 128 | if (value.length < Threshold) 129 | putCopy(value); 130 | else 131 | putRef(value); 132 | } 133 | 134 | 135 | private: 136 | /* 137 | * Stores the reference of $(D_PARAM value). 138 | * 139 | * Params: 140 | * value = the content to write. 141 | */ 142 | @trusted 143 | void putRef(in ubyte[] value) 144 | { 145 | vecList_.length += 1; 146 | vecList_[$ - 1] = iovec(cast(void*)value.ptr, value.length); 147 | } 148 | 149 | 150 | /* 151 | * Writes $(D_PARAM value) to buffer and appends to its reference. 152 | * 153 | * Params: 154 | * value = the contents to write. 155 | */ 156 | @trusted 157 | void putCopy(const scope ubyte[] value) 158 | { 159 | /* 160 | * Helper for expanding new space. 161 | */ 162 | void expand(in size_t size) 163 | { 164 | const newSize = size < ChunkSize ? ChunkSize : size; 165 | 166 | index_++; 167 | chunks_.length = 1; 168 | chunks_[index_].data.length = newSize; 169 | } 170 | 171 | const size = value.length; 172 | 173 | // lacks current chunk? 174 | if (chunks_[index_].data.length - chunks_[index_].used < size) 175 | expand(size); 176 | 177 | const base = chunks_[index_].used; // start index 178 | auto data = chunks_[index_].data[base..base + size]; // chunk to write 179 | 180 | data[] = value[]; 181 | chunks_[index_].used += size; 182 | 183 | // Optimization for avoiding iovec allocation. 184 | if (vecList_.length && data.ptr == (vecList_[$ - 1].iov_base + 185 | vecList_[$ - 1].iov_len)) 186 | vecList_[$ - 1].iov_len += size; 187 | else 188 | putRef(data); 189 | } 190 | } 191 | 192 | 193 | unittest 194 | { 195 | static assert(isOutputRange!(RefBuffer, ubyte) && 196 | isOutputRange!(RefBuffer, ubyte[])); 197 | 198 | auto buffer = RefBuffer(2, 4); 199 | 200 | ubyte[] tests = [1, 2]; 201 | foreach (v; tests) 202 | buffer.put(v); 203 | buffer.put(tests); 204 | 205 | assert(buffer.data == tests, "putCopy failed"); 206 | 207 | iovec[] vector = buffer.vector; 208 | ubyte[] result; 209 | 210 | assert(vector.length == 2, "Optimization failed"); 211 | 212 | foreach (v; vector) 213 | result ~= (cast(ubyte*)v.iov_base)[0..v.iov_len]; 214 | 215 | assert(result == tests ~ tests); 216 | } 217 | -------------------------------------------------------------------------------- /src/msgpack/common.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | module msgpack.common; 4 | 5 | import msgpack.attribute; 6 | 7 | import std.typetuple; // will use std.meta 8 | import std.traits; 9 | 10 | 11 | // for Converting Endian using ntohs and ntohl; 12 | version(Windows) 13 | { 14 | package import core.sys.windows.winsock2; 15 | } 16 | else 17 | { 18 | package import core.sys.posix.arpa.inet; 19 | } 20 | 21 | 22 | version(EnableReal) 23 | { 24 | package enum EnableReal = true; 25 | } 26 | else 27 | { 28 | package enum EnableReal = false; 29 | } 30 | 31 | 32 | static if (real.sizeof == double.sizeof) { 33 | // for 80bit real inter-operation on non-x86 CPU 34 | version = NonX86; 35 | 36 | package import std.numeric; 37 | } 38 | 39 | 40 | @trusted: 41 | public: 42 | 43 | 44 | /** 45 | * $(D ExtValue) is a $(D MessagePack) Extended value representation. 46 | * The application is responsible for correctly interpreting $(D data) according 47 | * to the type described by $(D type). 48 | */ 49 | struct ExtValue 50 | { 51 | byte type; /// An integer 0-127 with application-defined meaning 52 | ubyte[] data; /// The raw bytes 53 | } 54 | 55 | 56 | /** 57 | * MessagePack type-information format 58 | * 59 | * See_Also: 60 | * $(LINK2 http://redmine.msgpack.org/projects/msgpack/wiki/FormatSpec, MessagePack Specificaton) 61 | */ 62 | enum Format : ubyte 63 | { 64 | // unsinged integer 65 | UINT8 = 0xcc, // ubyte 66 | UINT16 = 0xcd, // ushort 67 | UINT32 = 0xce, // uint 68 | UINT64 = 0xcf, // ulong 69 | 70 | // signed integer 71 | INT8 = 0xd0, // byte 72 | INT16 = 0xd1, // short 73 | INT32 = 0xd2, // int 74 | INT64 = 0xd3, // long 75 | 76 | // floating point 77 | FLOAT = 0xca, // float 78 | DOUBLE = 0xcb, // double 79 | 80 | // raw byte 81 | RAW = 0xa0, 82 | RAW16 = 0xda, 83 | RAW32 = 0xdb, 84 | 85 | // bin type 86 | BIN8 = 0xc4, 87 | BIN16 = 0xc5, 88 | BIN32 = 0xc6, 89 | 90 | // ext type 91 | EXT = 0xd4, // fixext 1/2/4/8/16 92 | EXT8 = 0xc7, 93 | EXT16 = 0xc8, 94 | EXT32 = 0xc9, 95 | 96 | // str type 97 | STR8 = 0xd9, 98 | //STR16 = 0xda, 99 | //STR32 = 0xdb, 100 | 101 | // array 102 | ARRAY = 0x90, 103 | ARRAY16 = 0xdc, 104 | ARRAY32 = 0xdd, 105 | 106 | // map 107 | MAP = 0x80, 108 | MAP16 = 0xde, 109 | MAP32 = 0xdf, 110 | 111 | // other 112 | NIL = 0xc0, // null 113 | TRUE = 0xc3, 114 | FALSE = 0xc2, 115 | 116 | // real (This format is D only!) 117 | REAL = 0xd4 118 | } 119 | 120 | 121 | package: 122 | 123 | 124 | /** 125 | * For float type serialization / deserialization 126 | */ 127 | union _f 128 | { 129 | float f; 130 | uint i; 131 | } 132 | 133 | 134 | /** 135 | * For double type serialization / deserialization 136 | */ 137 | union _d 138 | { 139 | double f; 140 | ulong i; 141 | } 142 | 143 | 144 | /** 145 | * For real type serialization / deserialization 146 | * 147 | * 80-bit real is padded to 12 bytes(Linux) and 16 bytes(Mac). 148 | * http://lists.puremagic.com/pipermail/digitalmars-d/2010-June/077394.html 149 | */ 150 | union _r 151 | { 152 | real f; 153 | 154 | struct 155 | { 156 | ulong fraction; 157 | ushort exponent; // includes sign 158 | } 159 | } 160 | 161 | enum RealSize = 10; // Real size is 80bit 162 | 163 | 164 | /** 165 | * Detects whether $(D_PARAM T) is a built-in byte type. 166 | */ 167 | template isByte(T) 168 | { 169 | enum isByte = staticIndexOf!(Unqual!T, byte, ubyte) >= 0; 170 | } 171 | 172 | 173 | unittest 174 | { 175 | static assert(isByte!(byte)); 176 | static assert(isByte!(const(byte))); 177 | static assert(isByte!(ubyte)); 178 | static assert(isByte!(immutable(ubyte))); 179 | static assert(!isByte!(short)); 180 | static assert(!isByte!(char)); 181 | static assert(!isByte!(string)); 182 | } 183 | 184 | 185 | /** 186 | * Gets asterisk string from pointer type 187 | */ 188 | template AsteriskOf(T) 189 | { 190 | static if (is(T P == U*, U)) 191 | enum AsteriskOf = "*" ~ AsteriskOf!U; 192 | else 193 | enum AsteriskOf = ""; 194 | } 195 | 196 | 197 | /** 198 | * Get the number of member to serialize. 199 | */ 200 | template SerializingMemberNumbers(Classes...) 201 | { 202 | static if (Classes.length == 0) 203 | enum SerializingMemberNumbers = 0; 204 | else 205 | enum SerializingMemberNumbers = Filter!(isPackedField, Classes[0].tupleof).length + SerializingMemberNumbers!(Classes[1..$]); 206 | } 207 | 208 | 209 | /** 210 | * Get derived classes with serialization-order 211 | */ 212 | template SerializingClasses(T) 213 | { 214 | // There is no information in Object type. Currently disable Object serialization. 215 | static if (is(T == Object)) 216 | static assert(false, "Object type serialization doesn't support yet. Please define toMsgpack/fromMsgpack and use cast"); 217 | else 218 | alias TypeTuple!(Reverse!(Erase!(Object, BaseClassesTuple!(T))), T) SerializingClasses; 219 | } 220 | 221 | 222 | /** 223 | * Get a field name of class or struct. 224 | */ 225 | template getFieldName(Type, size_t i) 226 | { 227 | import std.conv : text; 228 | 229 | static assert((is(Unqual!Type == class) || is(Unqual!Type == struct)), "Type must be class or struct: type = " ~ Type.stringof); 230 | static assert(i < Type.tupleof.length, text(Type.stringof, " has ", Type.tupleof.length, " attributes: given index = ", i)); 231 | 232 | enum getFieldName = __traits(identifier, Type.tupleof[i]); 233 | } 234 | 235 | 236 | version (LittleEndian) 237 | { 238 | /* 239 | * Converts $(value) to different Endian. 240 | * 241 | * Params: 242 | * value = the LittleEndian value to convert. 243 | * 244 | * Returns: 245 | * the converted value. 246 | */ 247 | @trusted 248 | ushort convertEndianTo(size_t Bit, T)(in T value) if (Bit == 16) 249 | { 250 | return ntohs(cast(ushort)value); 251 | } 252 | 253 | 254 | // ditto 255 | @trusted 256 | uint convertEndianTo(size_t Bit, T)(in T value) if (Bit == 32) 257 | { 258 | return ntohl(cast(uint)value); 259 | } 260 | 261 | 262 | // ditto 263 | @trusted 264 | ulong convertEndianTo(size_t Bit, T)(in T value) if (Bit == 64) 265 | { 266 | // dmd has convert function? 267 | return ((((cast(ulong)value) << 56) & 0xff00000000000000UL) | 268 | (((cast(ulong)value) << 40) & 0x00ff000000000000UL) | 269 | (((cast(ulong)value) << 24) & 0x0000ff0000000000UL) | 270 | (((cast(ulong)value) << 8) & 0x000000ff00000000UL) | 271 | (((cast(ulong)value) >> 8) & 0x00000000ff000000UL) | 272 | (((cast(ulong)value) >> 24) & 0x0000000000ff0000UL) | 273 | (((cast(ulong)value) >> 40) & 0x000000000000ff00UL) | 274 | (((cast(ulong)value) >> 56) & 0x00000000000000ffUL)); 275 | } 276 | 277 | 278 | unittest 279 | { 280 | assert(convertEndianTo!16(0x0123) == 0x2301); 281 | assert(convertEndianTo!32(0x01234567) == 0x67452301); 282 | assert(convertEndianTo!64(0x0123456789abcdef) == 0xefcdab8967452301); 283 | } 284 | 285 | 286 | /* 287 | * Comapatible for BigEndian environment. 288 | */ 289 | ubyte take8from(size_t bit = 8, T)(T value) 290 | { 291 | static if (bit == 8 || bit == 16 || bit == 32 || bit == 64) 292 | return (cast(ubyte*)&value)[0]; 293 | else 294 | static assert(false, bit.stringof ~ " is not support bit width."); 295 | } 296 | 297 | 298 | unittest 299 | { 300 | foreach (Integer; TypeTuple!(ubyte, ushort, uint, ulong)) { 301 | assert(take8from!8 (cast(Integer)0x01) == 0x01); 302 | assert(take8from!16(cast(Integer)0x0123) == 0x23); 303 | assert(take8from!32(cast(Integer)0x01234567) == 0x67); 304 | assert(take8from!64(cast(Integer)0x0123456789abcdef) == 0xef); 305 | } 306 | } 307 | } 308 | else 309 | { 310 | /* 311 | * Comapatible for LittleEndian environment. 312 | */ 313 | @safe 314 | ushort convertEndianTo(size_t Bit, T)(in T value) if (Bit == 16) 315 | { 316 | return cast(ushort)value; 317 | } 318 | 319 | 320 | // ditto 321 | @safe 322 | uint convertEndianTo(size_t Bit, T)(in T value) if (Bit == 32) 323 | { 324 | return cast(uint)value; 325 | } 326 | 327 | 328 | // ditto 329 | @safe 330 | ulong convertEndianTo(size_t Bit, T)(in T value) if (Bit == 64) 331 | { 332 | return cast(ulong)value; 333 | } 334 | 335 | 336 | unittest 337 | { 338 | assert(convertEndianTo!16(0x0123) == 0x0123); 339 | assert(convertEndianTo!32(0x01234567) == 0x01234567); 340 | assert(convertEndianTo!64(0x0123456789) == 0x0123456789); 341 | } 342 | 343 | 344 | /* 345 | * Takes 8bit from $(D_PARAM value) 346 | * 347 | * Params: 348 | * value = the content to take. 349 | * 350 | * Returns: 351 | * the 8bit value corresponding $(D_PARAM bit) width. 352 | */ 353 | ubyte take8from(size_t bit = 8, T)(T value) 354 | { 355 | static if (bit == 8) 356 | return (cast(ubyte*)&value)[0]; 357 | else static if (bit == 16) 358 | return (cast(ubyte*)&value)[1]; 359 | else static if (bit == 32) 360 | return (cast(ubyte*)&value)[3]; 361 | else static if (bit == 64) 362 | return (cast(ubyte*)&value)[7]; 363 | else 364 | static assert(false, bit.stringof ~ " is not support bit width."); 365 | } 366 | 367 | 368 | unittest 369 | { 370 | foreach (Integer; TypeTuple!(ubyte, ushort, uint, ulong)) { 371 | assert(take8from!8 (cast(Integer)0x01) == 0x01); 372 | assert(take8from!16(cast(Integer)0x0123) == 0x23); 373 | assert(take8from!32(cast(Integer)0x01234567) == 0x67); 374 | assert(take8from!64(cast(Integer)0x0123456789abcdef) == 0xef); 375 | } 376 | } 377 | } 378 | 379 | 380 | /* 381 | * Loads $(D_PARAM T) type value from $(D_PARAM buffer). 382 | * 383 | * Params: 384 | * buffer = the serialized contents. 385 | * 386 | * Returns: 387 | * the Endian-converted value. 388 | */ 389 | T load16To(T)(ubyte[] buffer) 390 | { 391 | return cast(T)(convertEndianTo!16(*cast(ushort*)buffer.ptr)); 392 | } 393 | 394 | 395 | // ditto 396 | T load32To(T)(ubyte[] buffer) 397 | { 398 | return cast(T)(convertEndianTo!32(*cast(uint*)buffer.ptr)); 399 | } 400 | 401 | 402 | // ditto 403 | T load64To(T)(ubyte[] buffer) 404 | { 405 | return cast(T)(convertEndianTo!64(*cast(ulong*)buffer.ptr)); 406 | } 407 | 408 | 409 | version (D_Ddoc) 410 | { 411 | /** 412 | * Internal buffer and related operations for Unpacker 413 | * 414 | * Following Unpackers mixin this template. So, Unpacker can use following methods. 415 | * 416 | * ----- 417 | * //buffer image: 418 | * +-------------------------------------------+ 419 | * | [object] | [obj | unparsed... | unused... | 420 | * +-------------------------------------------+ 421 | * ^ offset 422 | * ^ current 423 | * ^ used 424 | * ^ buffer.length 425 | * ----- 426 | * 427 | * This mixin template is a private. 428 | */ 429 | mixin template InternalBuffer() 430 | { 431 | private: 432 | ubyte[] buffer_; // internal buffer 433 | size_t used_; // index that buffer cosumed 434 | size_t offset_; // index that buffer parsed 435 | size_t parsed_; // total size of parsed message 436 | bool hasRaw_; // indicates whether Raw object has been deserialized 437 | 438 | 439 | public: 440 | /** 441 | * Forwards to internal buffer. 442 | * 443 | * Returns: 444 | * the reference of internal buffer. 445 | */ 446 | @property @safe 447 | nothrow ubyte[] buffer(); 448 | 449 | 450 | /** 451 | * Fills internal buffer with $(D_PARAM target). 452 | * 453 | * Params: 454 | * target = new serialized buffer to deserialize. 455 | */ 456 | @safe void feed(in ubyte[] target); 457 | 458 | 459 | /** 460 | * Consumes buffer. This method is helper for buffer property. 461 | * You must use this method if you write bytes to buffer directly. 462 | * 463 | * Params: 464 | * size = the number of consuming. 465 | */ 466 | @safe 467 | nothrow void bufferConsumed(in size_t size); 468 | 469 | 470 | /** 471 | * Removes unparsed buffer. 472 | */ 473 | @safe 474 | nothrow void removeUnparsed(); 475 | 476 | 477 | /** 478 | * Returns: 479 | * the total size including unparsed buffer size. 480 | */ 481 | @property @safe 482 | nothrow size_t size() const; 483 | 484 | 485 | /** 486 | * Returns: 487 | * the parsed size of buffer. 488 | */ 489 | @property @safe 490 | nothrow size_t parsedSize() const; 491 | 492 | 493 | /** 494 | * Returns: 495 | * the unparsed size of buffer. 496 | */ 497 | @property @safe 498 | nothrow size_t unparsedSize() const; 499 | 500 | 501 | private: 502 | @safe 503 | void initializeBuffer(in ubyte[] target, in size_t bufferSize = 8192); 504 | } 505 | } 506 | else 507 | { 508 | mixin template InternalBuffer() 509 | { 510 | private: 511 | ubyte[] buffer_; // internal buffer 512 | size_t used_; // index that buffer cosumed 513 | size_t offset_; // index that buffer parsed 514 | size_t parsed_; // total size of parsed message 515 | bool hasRaw_; // indicates whether Raw object has been deserialized 516 | 517 | 518 | public: 519 | @property @safe 520 | nothrow ubyte[] buffer() 521 | { 522 | return buffer_; 523 | } 524 | 525 | 526 | @safe 527 | void feed(in ubyte[] target) 528 | in 529 | { 530 | assert(target.length); 531 | } 532 | do 533 | { 534 | /* 535 | * Expands internal buffer. 536 | * 537 | * Params: 538 | * size = new buffer size to append. 539 | */ 540 | void expandBuffer(in size_t size) 541 | { 542 | // rewinds buffer(completed deserialization) 543 | if (used_ == offset_ && !hasRaw_) { 544 | used_ = offset_ = 0; 545 | 546 | if (buffer_.length < size) 547 | buffer_.length = size; 548 | 549 | return; 550 | } 551 | 552 | // deserializing state is mid-flow(buffer has non-parsed data yet) 553 | auto unparsed = buffer_[offset_..used_]; 554 | auto restSize = buffer_.length - used_ + offset_; 555 | auto newSize = size > restSize ? unparsedSize + size : buffer_.length; 556 | 557 | if (hasRaw_) { 558 | hasRaw_ = false; 559 | buffer_ = new ubyte[](newSize); 560 | } else { 561 | buffer_.length = newSize; 562 | 563 | // avoids overlapping copy 564 | auto area = buffer_[0..unparsedSize]; 565 | unparsed = area.overlap(unparsed) ? unparsed.dup : unparsed; 566 | } 567 | 568 | buffer_[0..unparsedSize] = unparsed[]; 569 | used_ = unparsedSize; 570 | offset_ = 0; 571 | } 572 | 573 | const size = target.length; 574 | 575 | // lacks current buffer? 576 | if (buffer_.length - used_ < size) 577 | expandBuffer(size); 578 | 579 | buffer_[used_..used_ + size] = target[]; 580 | used_ += size; 581 | } 582 | 583 | 584 | @safe 585 | nothrow void bufferConsumed(in size_t size) 586 | { 587 | if (used_ + size > buffer_.length) 588 | used_ = buffer_.length; 589 | else 590 | used_ += size; 591 | } 592 | 593 | 594 | @safe 595 | nothrow void removeUnparsed() 596 | { 597 | used_ = offset_; 598 | } 599 | 600 | 601 | @property @safe 602 | nothrow size_t size() const 603 | { 604 | return parsed_ - offset_ + used_; 605 | } 606 | 607 | 608 | @property @safe 609 | nothrow size_t parsedSize() const 610 | { 611 | return parsed_; 612 | } 613 | 614 | 615 | @property @safe 616 | nothrow size_t unparsedSize() const 617 | { 618 | return used_ - offset_; 619 | } 620 | 621 | 622 | private: 623 | @safe 624 | nothrow void initializeBuffer(in ubyte[] target, in size_t bufferSize = 8192) 625 | { 626 | const size = target.length; 627 | 628 | buffer_ = new ubyte[](size > bufferSize ? size : bufferSize); 629 | used_ = size; 630 | buffer_[0..size] = target[]; 631 | } 632 | } 633 | } 634 | -------------------------------------------------------------------------------- /src/msgpack/exception.d: -------------------------------------------------------------------------------- 1 | module msgpack.exception; 2 | 3 | @trusted: 4 | 5 | /** 6 | * $(D MessagePackException) is a root Exception for MessagePack related operation. 7 | */ 8 | class MessagePackException : Exception 9 | { 10 | pure this(string message) 11 | { 12 | super(message); 13 | } 14 | } 15 | 16 | 17 | /** 18 | * $(D UnpackException) is thrown on deserialization failure 19 | */ 20 | class UnpackException : MessagePackException 21 | { 22 | this(string message) 23 | { 24 | super(message); 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/msgpack/package.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | * MessagePack serializer and deserializer implementation. 5 | * 6 | * MessagePack is a binary-based serialization specification. 7 | * 8 | * Example: 9 | * ----- 10 | * auto data = tuple("MessagePack!", [1, 2], true); 11 | * 12 | * auto serialized = pack(data); 13 | * 14 | * // ... 15 | * 16 | * typeof(data) deserialized; 17 | * 18 | * unpack(serialized, deserialized); 19 | * 20 | * assert(data == deserialized); 21 | * ----- 22 | * 23 | * See_Also: 24 | * $(LINK2 http://msgpack.org/, The MessagePack Project)$(BR) 25 | * $(LINK2 https://github.com/msgpack/msgpack/blob/master/spec.md, MessagePack data format) 26 | * 27 | * Copyright: Copyright Masahiro Nakagawa 2010-. 28 | * License: Boost License 1.0. 29 | * Authors: Masahiro Nakagawa 30 | */ 31 | module msgpack; 32 | 33 | public: 34 | 35 | import msgpack.common; 36 | import msgpack.attribute; 37 | import msgpack.buffer; 38 | import msgpack.exception; 39 | import msgpack.packer; 40 | import msgpack.unpacker; 41 | import msgpack.streaming_unpacker; 42 | import msgpack.register; 43 | import msgpack.value; 44 | 45 | version(Windows) { 46 | pragma(lib, "WS2_32"); 47 | } 48 | 49 | @trusted: 50 | 51 | 52 | /** 53 | * Serializes $(D_PARAM args). 54 | * 55 | * Assumes single object if the length of $(D_PARAM args) == 1, 56 | * otherwise array object. 57 | * 58 | * Params: 59 | * args = the contents to serialize. 60 | * 61 | * Returns: 62 | * a serialized data. 63 | */ 64 | ubyte[] pack(bool withFieldName = false, Args...)(in Args args) 65 | { 66 | auto packer = Packer(withFieldName); 67 | 68 | static if (Args.length == 1) 69 | packer.pack(args[0]); 70 | else 71 | packer.packArray(args); 72 | 73 | return packer.stream.data; 74 | } 75 | 76 | 77 | /** 78 | * Deserializes $(D_PARAM buffer) using stream deserializer. 79 | * 80 | * Params: 81 | * buffer = the buffer to deserialize. 82 | * 83 | * Returns: 84 | * a $(D Unpacked) contains deserialized object. 85 | * 86 | * Throws: 87 | * UnpackException if deserialization doesn't succeed. 88 | */ 89 | Unpacked unpack(in ubyte[] buffer) 90 | { 91 | auto unpacker = StreamingUnpacker(buffer); 92 | 93 | if (!unpacker.execute()) 94 | throw new UnpackException("Deserialization failure"); 95 | 96 | return unpacker.unpacked; 97 | } 98 | 99 | 100 | /** 101 | * Deserializes $(D_PARAM buffer) using direct-conversion deserializer. 102 | * 103 | * Assumes single object if the length of $(D_PARAM args) == 1, 104 | * otherwise array object. 105 | * 106 | * Params: 107 | * buffer = the buffer to deserialize. 108 | * args = the references of values to assign. 109 | */ 110 | void unpack(bool withFieldName = false, Args...)(in ubyte[] buffer, ref Args args) 111 | { 112 | auto unpacker = Unpacker(buffer, buffer.length, withFieldName); 113 | 114 | static if (Args.length == 1) 115 | unpacker.unpack(args[0]); 116 | else 117 | unpacker.unpackArray(args); 118 | } 119 | 120 | 121 | /** 122 | * Return value version 123 | */ 124 | Type unpack(Type, bool withFieldName = false)(in ubyte[] buffer) 125 | { 126 | auto unpacker = Unpacker(buffer, buffer.length, withFieldName); 127 | 128 | Type result; 129 | unpacker.unpack(result); 130 | return result; 131 | } 132 | 133 | 134 | unittest 135 | { 136 | auto serialized = pack(false); 137 | 138 | assert(serialized[0] == Format.FALSE); 139 | 140 | auto deserialized = unpack(pack(1, true, "Foo")); 141 | 142 | assert(deserialized.type == Value.Type.array); 143 | assert(deserialized.via.array[0].type == Value.Type.unsigned); 144 | assert(deserialized.via.array[1].type == Value.Type.boolean); 145 | assert(deserialized.via.array[2].type == Value.Type.raw); 146 | } 147 | 148 | 149 | unittest 150 | { 151 | import std.typecons; 152 | 153 | { // stream 154 | auto result = unpack(pack(false)); 155 | 156 | assert(result.via.boolean == false); 157 | } 158 | { // direct conversion 159 | Tuple!(uint, string) result; 160 | Tuple!(uint, string) test = tuple(1, "Hi!"); 161 | 162 | unpack(pack(test), result); 163 | assert(result == test); 164 | 165 | test.field[0] = 2; 166 | test.field[1] = "Hey!"; 167 | unpack(pack(test.field[0], test.field[1]), result.field[0], result.field[1]); 168 | assert(result == test); 169 | } 170 | { // return value direct conversion 171 | Tuple!(uint, string) test = tuple(1, "Hi!"); 172 | 173 | auto data = pack(test); 174 | assert(data.unpack!(Tuple!(uint, string)) == test); 175 | } 176 | { // serialize object as a Map 177 | static class C 178 | { 179 | int num; 180 | 181 | this(int num) { this.num = num; } 182 | } 183 | 184 | auto test = new C(10); 185 | auto result = new C(100); 186 | 187 | unpack!(true)(pack!(true)(test), result); 188 | assert(result.num == 10, "Unpacking with field names failed"); 189 | } 190 | } 191 | 192 | 193 | unittest 194 | { 195 | import std.typetuple; 196 | 197 | // unittest for https://github.com/msgpack/msgpack-d/issues/8 198 | foreach (Type; TypeTuple!(byte, short, int, long)) { 199 | foreach (i; [-33, -20, -1, 0, 1, 20, 33]) { 200 | Type a = cast(Type)i; 201 | Type b; 202 | unpack(pack(a), b); 203 | assert(a == b); 204 | } 205 | } 206 | } 207 | 208 | 209 | unittest 210 | { 211 | import std.typetuple; 212 | 213 | // char types 214 | foreach (Type; TypeTuple!(char, wchar, dchar)) { 215 | foreach (i; [Type.init, Type.min, Type.max, cast(Type)'j']) { 216 | Type a = i; 217 | Type b; 218 | unpack(pack(a), b); 219 | assert(a == b); 220 | } 221 | } 222 | } 223 | 224 | unittest 225 | { 226 | // ext type 227 | auto result = unpack(pack(ExtValue(7, [1,2,3,4]))); 228 | assert(result == ExtValue(7, [1,2,3,4])); 229 | } 230 | 231 | unittest { 232 | import std.exception: assertThrown; 233 | 234 | struct Version { 235 | int major= -1; 236 | int minor = -1; 237 | } 238 | 239 | struct SubscriptionTopic { 240 | string[] topicComponents; 241 | } 242 | 243 | struct SubscriptionSender 244 | { 245 | string hostName; 246 | string biosName; 247 | } 248 | 249 | struct PubSubMessage { 250 | 251 | enum Type { 252 | publication, 253 | subscribe, 254 | unsubscribe, 255 | } 256 | 257 | Version version_; 258 | Type type; 259 | SubscriptionSender sender; 260 | SubscriptionTopic topic; 261 | string value; 262 | } 263 | 264 | ubyte[] bytes = [149, 146, 255, 255, 0, 146, 164, 104, 265 | 111, 115, 116, 164, 98, 105, 111, 115, 266 | 145, 221, 171, 105, 110, 116, 101, 114, 267 | 101, 115, 116, 105, 110, 103, 165, 116, 268 | 111, 112, 105, 99, 167, 112, 97, 121, 269 | 108, 111, 97, 100, 158, 142, 210, 31, 270 | 127, 81, 149, 125, 183, 108, 86, 17, 271 | 100, 35, 168]; 272 | 273 | // should not throw OutOfMemoryError 274 | assertThrown!MessagePackException(unpack!PubSubMessage(bytes)); 275 | } 276 | 277 | 278 | /** 279 | * Handy helper for creating MessagePackable object. 280 | * 281 | * toMsgpack / fromMsgpack are special methods for serialization / deserialization. 282 | * This template provides those methods to struct/class. 283 | * 284 | * Example: 285 | * ----- 286 | * struct S 287 | * { 288 | * int num; string str; 289 | * 290 | * // http://d.puremagic.com/issues/show_bug.cgi?id = 1099 291 | * mixin MessagePackable; // all members 292 | * // mixin MessagePackable!("num"); // num only 293 | * } 294 | * ----- 295 | * 296 | * Defines those methods manually if you treat complex data-structure. 297 | */ 298 | mixin template MessagePackable(Members...) 299 | { 300 | static if (Members.length == 0) { 301 | /** 302 | * Serializes members using $(D_PARAM packer). 303 | * 304 | * Params: 305 | * packer = the serializer to pack. 306 | */ 307 | void toMsgpack(Packer)(ref Packer packer, bool withFieldName = false) const 308 | { 309 | if (withFieldName) { 310 | packer.beginMap(this.tupleof.length); 311 | foreach (i, member; this.tupleof) { 312 | packer.pack(getFieldName!(typeof(this), i)); 313 | packer.pack(member); 314 | } 315 | } else { 316 | packer.beginArray(this.tupleof.length); 317 | foreach (member; this.tupleof) 318 | packer.pack(member); 319 | } 320 | } 321 | 322 | 323 | /** 324 | * Deserializes $(D MessagePack) object to members using Value. 325 | * 326 | * Params: 327 | * value = the MessagePack value to unpack. 328 | * 329 | * Throws: 330 | * MessagePackException if $(D_PARAM value) is not an Array type. 331 | */ 332 | void fromMsgpack(Value value) 333 | { 334 | // enables if std.contracts.enforce is moved to object_.d 335 | // enforceEx!MessagePackException(value.type == Value.Type.array, "Value must be Array type"); 336 | if (value.type != Value.Type.array) 337 | throw new MessagePackException("Value must be an Array type"); 338 | if (value.via.array.length != this.tupleof.length) 339 | throw new MessagePackException("The size of deserialized value is mismatched"); 340 | 341 | foreach (i, member; this.tupleof) 342 | this.tupleof[i] = value.via.array[i].as!(typeof(member)); 343 | } 344 | 345 | 346 | /** 347 | * Deserializes $(D MessagePack) object to members using direct-conversion deserializer. 348 | * 349 | * Params: 350 | * value = the reference to direct-conversion deserializer. 351 | * 352 | * Throws: 353 | * MessagePackException if the size of deserialized value is mismatched. 354 | */ 355 | void fromMsgpack(ref Unpacker unpacker) 356 | { 357 | auto length = unpacker.beginArray(); 358 | if (length != this.tupleof.length) 359 | throw new MessagePackException("The size of deserialized value is mismatched"); 360 | 361 | foreach (i, member; this.tupleof) 362 | unpacker.unpack(this.tupleof[i]); 363 | } 364 | } else { 365 | /** 366 | * Member selecting version of toMsgpack. 367 | */ 368 | void toMsgpack(Packer)(ref Packer packer, bool withFieldName = false) const 369 | { 370 | if (withFieldName) { 371 | packer.beginMap(Members.length); 372 | foreach (member; Members) { 373 | packer.pack(member); 374 | packer.pack(mixin(member)); 375 | } 376 | } else { 377 | packer.beginArray(Members.length); 378 | foreach (member; Members) 379 | packer.pack(mixin(member)); 380 | } 381 | } 382 | 383 | 384 | /** 385 | * Member selecting version of fromMsgpack for Value. 386 | */ 387 | void fromMsgpack(Value value) 388 | { 389 | if (value.type != Value.Type.array) 390 | throw new MessagePackException("Value must be an Array type"); 391 | if (value.via.array.length != Members.length) 392 | throw new MessagePackException("The size of deserialized value is mismatched"); 393 | 394 | foreach (i, member; Members) 395 | mixin(member ~ "= value.via.array[i].as!(typeof(" ~ member ~ "));"); 396 | } 397 | 398 | 399 | /** 400 | * Member selecting version of fromMsgpack for direct-converion deserializer. 401 | */ 402 | void fromMsgpack(ref Unpacker unpacker) 403 | { 404 | auto length = unpacker.beginArray(); 405 | if (length != Members.length) 406 | throw new MessagePackException("The size of deserialized value is mismatched"); 407 | 408 | foreach (member; Members) 409 | unpacker.unpack(mixin(member)); 410 | } 411 | } 412 | } 413 | 414 | 415 | unittest 416 | { 417 | { // all members 418 | /* 419 | * Comment out because "src/msgpack.d(4048): Error: struct msgpack.__unittest16.S no size yet for forward reference" occurs 420 | */ 421 | static struct S 422 | { 423 | uint num; string str; 424 | mixin MessagePackable; 425 | } 426 | 427 | mixin DefinePacker; 428 | 429 | S orig = S(10, "Hi!"); orig.toMsgpack(packer); 430 | 431 | { // stream 432 | auto unpacker = StreamingUnpacker(packer.stream.data); unpacker.execute(); 433 | 434 | S result; result.fromMsgpack(unpacker.unpacked); 435 | 436 | assert(result.num == 10); 437 | assert(result.str == "Hi!"); 438 | } 439 | { // direct conversion 440 | auto unpacker = Unpacker(packer.stream.data); 441 | 442 | S result; unpacker.unpack(result); 443 | 444 | assert(result.num == 10); 445 | assert(result.str == "Hi!"); 446 | } 447 | } 448 | { // member select 449 | static class C 450 | { 451 | uint num; string str; 452 | 453 | this() {} 454 | this(uint n, string s) { num = n; str = s; } 455 | 456 | mixin MessagePackable!("num"); 457 | } 458 | 459 | mixin DefinePacker; 460 | 461 | C orig = new C(10, "Hi!"); orig.toMsgpack(packer); 462 | 463 | { // stream 464 | auto unpacker = StreamingUnpacker(packer.stream.data); unpacker.execute(); 465 | 466 | C result = new C; result.fromMsgpack(unpacker.unpacked); 467 | 468 | assert(result.num == 10); 469 | } 470 | { // direct conversion 471 | auto unpacker = Unpacker(packer.stream.data); 472 | 473 | C result; unpacker.unpack(result); 474 | 475 | assert(result.num == 10); 476 | } 477 | } 478 | } 479 | 480 | 481 | unittest 482 | { 483 | import std.datetime: Clock, SysTime; 484 | import msgpack.packer, msgpack.unpacker; 485 | 486 | static struct SysTimePackProxy 487 | { 488 | static void serialize(ref Packer p, ref in SysTime tim) 489 | { 490 | p.pack(tim.toISOExtString()); 491 | } 492 | 493 | static void deserialize(ref Unpacker u, ref SysTime tim) 494 | { 495 | string tmp; 496 | u.unpack(tmp); 497 | tim = SysTime.fromISOExtString(tmp); 498 | } 499 | } 500 | static struct LogData 501 | { 502 | string msg; 503 | string file; 504 | ulong line; 505 | @serializedAs!SysTimePackProxy SysTime timestamp; 506 | 507 | this(string message, string file = __FILE__, ulong line = __LINE__) 508 | { 509 | this.msg = message; 510 | this.file = file; 511 | this.line = line; 512 | this.timestamp = Clock.currTime(); 513 | } 514 | } 515 | 516 | /// Now we can serialize/deserialize LogData 517 | LogData[] logs; 518 | logs ~= LogData("MessagePack is nice!"); 519 | auto data = pack(logs); 520 | LogData[] datas = unpack!(LogData[])(data); 521 | assert(datas[0].timestamp.toString() == datas[0].timestamp.toString()); 522 | } 523 | -------------------------------------------------------------------------------- /src/msgpack/register.d: -------------------------------------------------------------------------------- 1 | module msgpack.register; 2 | 3 | import msgpack.packer; 4 | import msgpack.unpacker; 5 | 6 | import std.array; 7 | 8 | 9 | /** 10 | * Register a serialization handler for $(D_PARAM T) type 11 | * 12 | * Example: 13 | * ----- 14 | * registerPackHandler!(Foo, fooPackHandler); 15 | * ----- 16 | */ 17 | void registerPackHandler(T, alias Handler, Stream = Appender!(ubyte[]))() 18 | { 19 | PackerImpl!(Stream).registerHandler!(T, Handler); 20 | } 21 | 22 | 23 | /** 24 | * Register a deserialization handler for $(D_PARAM T) type 25 | * 26 | * Example: 27 | * ----- 28 | * registerUnackHandler!(Foo, fooUnackHandler); 29 | * ----- 30 | */ 31 | void registerUnpackHandler(T, alias Handler)() 32 | { 33 | Unpacker.registerHandler!(T, Handler); 34 | } 35 | 36 | 37 | /** 38 | * Register derived class for (de)serialization 39 | * 40 | * Example: 41 | * ----- 42 | * registerClass!(DerivedClass); 43 | * ----- 44 | */ 45 | void registerClass(T, Stream = Appender!(ubyte[]))() 46 | { 47 | PackerImpl!(Stream).register!(T); 48 | Unpacker.register!(T); 49 | } 50 | -------------------------------------------------------------------------------- /src/msgpack/streaming_unpacker.d: -------------------------------------------------------------------------------- 1 | module msgpack.streaming_unpacker; 2 | 3 | import msgpack.common; 4 | import msgpack.attribute; 5 | import msgpack.exception; 6 | import msgpack.value; 7 | 8 | import std.array; 9 | import std.exception; 10 | import std.range; 11 | import std.stdio; 12 | import std.traits; 13 | import std.typecons; 14 | import std.typetuple; 15 | import std.container; 16 | 17 | 18 | /** 19 | * $(D Unpacked) is a $(D Range) wrapper for stream deserialization result 20 | */ 21 | struct Unpacked 22 | { 23 | import std.conv : text; 24 | 25 | Value value; /// deserialized value 26 | 27 | alias value this; 28 | 29 | 30 | /** 31 | * Constructs a $(D Unpacked) with argument. 32 | * 33 | * Params: 34 | * value = a deserialized value. 35 | */ 36 | @safe 37 | this(ref Value value) 38 | { 39 | this.value = value; 40 | } 41 | 42 | 43 | /** 44 | * InputRange primitive operation that checks iteration state. 45 | * 46 | * Returns: 47 | * true if there are no more elements to be iterated. 48 | */ 49 | @property @trusted 50 | nothrow bool empty() const // std.array.empty isn't nothrow function 51 | { 52 | return (value.type == Value.Type.array) && !value.via.array.length; 53 | } 54 | 55 | 56 | /** 57 | * Range primitive operation that returns the length of the range. 58 | * 59 | * Returns: 60 | * the number of values. 61 | */ 62 | @property @trusted 63 | size_t length() 64 | { 65 | debug enforce(value.type == Value.Type.array, "lenght is called with non array object. type = " ~ text(value.type)); 66 | return value.via.array.length; 67 | } 68 | 69 | 70 | /** 71 | * InputRange primitive operation that returns the currently iterated element. 72 | * 73 | * Returns: 74 | * the deserialized $(D Value). 75 | */ 76 | @property @trusted 77 | ref Value front() 78 | { 79 | debug enforce(value.type == Value.Type.array, "front is called with non array object. type = " ~ text(value.type)); 80 | return value.via.array.front; 81 | } 82 | 83 | 84 | /** 85 | * InputRange primitive operation that advances the range to its next element. 86 | */ 87 | @trusted 88 | void popFront() 89 | { 90 | debug enforce(value.type == Value.Type.array, "popFront is called with non array object. type = " ~ text(value.type)); 91 | value.via.array.popFront(); 92 | } 93 | 94 | /** 95 | * RandomAccessRange primitive operation. 96 | * 97 | * Returns: 98 | * the deserialized $(D Value) at $(D_PARAM n) position. 99 | */ 100 | @trusted 101 | ref Value opIndex(size_t n) 102 | { 103 | debug enforce(value.type == Value.Type.array, "opIndex is called with non array object. type = " ~ text(value.type)); 104 | return value.via.array[n]; 105 | } 106 | 107 | /** 108 | * Returns a slice of the range. 109 | * 110 | * Paramas: 111 | * from = the start point of slicing. 112 | * to = the end point of slicing. 113 | * 114 | * Returns: 115 | * the slice of Values. 116 | */ 117 | @trusted 118 | Value[] opSlice(size_t from, size_t to) 119 | { 120 | debug enforce(value.type == Value.Type.array, "opSlice is called with non array object. type = " ~ text(value.type)); 121 | return value.via.array[from..to]; 122 | } 123 | 124 | /** 125 | * Range primitive operation that returns the snapshot. 126 | * 127 | * Returns: 128 | * the snapshot of this Value. 129 | */ 130 | @property @safe 131 | Unpacked save() 132 | { 133 | return Unpacked(value); 134 | } 135 | } 136 | 137 | 138 | unittest 139 | { 140 | static assert(isForwardRange!Unpacked); 141 | static assert(hasLength!Unpacked); 142 | } 143 | 144 | 145 | /** 146 | * This $(D StreamingUnpacker) is a $(D MessagePack) streaming deserializer 147 | * 148 | * This implementation enables you to load multiple objects from a stream(like network). 149 | * 150 | * Example: 151 | * ----- 152 | * ... 153 | * auto unpacker = StreamingUnpacker(serializedData); 154 | * ... 155 | * 156 | * // appends new data to buffer if pre execute() call didn't finish deserialization. 157 | * unpacker.feed(newSerializedData); 158 | * 159 | * while (unpacker.execute()) { 160 | * foreach (obj; unpacker.purge()) { 161 | * // do stuff (obj is a Value) 162 | * } 163 | * } 164 | * 165 | * if (unpacker.size) 166 | * throw new Exception("Message is too large"); 167 | * ----- 168 | */ 169 | struct StreamingUnpacker 170 | { 171 | private: 172 | /* 173 | * Context state of deserialization 174 | */ 175 | enum State 176 | { 177 | HEADER = 0x00, 178 | 179 | BIN8 = 0x04, 180 | BIN16, 181 | BIN32, 182 | 183 | // Floating point, Unsigned, Signed interger (== header & 0x03) 184 | FLOAT = 0x0a, 185 | DOUBLE, 186 | UINT8, 187 | UINT16, 188 | UINT32, 189 | UINT64, 190 | INT8, 191 | INT16, 192 | INT32, 193 | INT64, 194 | 195 | // Container (== header & 0x01) 196 | STR8 = 0x19, 197 | RAW16 = 0x1a, 198 | RAW32, 199 | ARRAY16, 200 | ARRAY36, 201 | MAP16, 202 | MAP32, 203 | RAW, 204 | 205 | // EXT family 206 | EXT8, 207 | EXT16, 208 | EXT32, 209 | EXT_DATA, 210 | 211 | // D-specific type 212 | REAL 213 | } 214 | 215 | 216 | /* 217 | * Element type of container 218 | */ 219 | enum ContainerElement 220 | { 221 | ARRAY_ITEM, 222 | MAP_KEY, 223 | MAP_VALUE 224 | } 225 | 226 | 227 | /* 228 | * Internal stack context 229 | */ 230 | static struct Context 231 | { 232 | static struct Container 233 | { 234 | ContainerElement type; // value container type 235 | Value value; // current value 236 | Value key; // for map value 237 | size_t count; // container length 238 | } 239 | 240 | State state; // current state of deserialization 241 | size_t trail; // current deserializing size 242 | size_t top; // current index of stack 243 | Container[] stack; // storing values 244 | } 245 | 246 | Context context_; // stack environment for streaming deserialization 247 | 248 | mixin InternalBuffer; 249 | 250 | 251 | public: 252 | /** 253 | * Constructs a $(D StreamingUnpacker). 254 | * 255 | * Params: 256 | * target = byte buffer to deserialize 257 | * bufferSize = size limit of buffer size 258 | */ 259 | @safe 260 | this(in ubyte[] target, in size_t bufferSize = 8192) 261 | { 262 | initializeBuffer(target, bufferSize); 263 | initializeContext(); 264 | } 265 | 266 | 267 | /** 268 | * Forwards to deserialized object. 269 | * 270 | * Returns: 271 | * the $(D Unpacked) object contains deserialized value. 272 | */ 273 | @property @safe 274 | Unpacked unpacked() 275 | { 276 | return Unpacked(context_.stack[0].value); 277 | } 278 | 279 | 280 | /** 281 | * Clears some states for next deserialization. 282 | */ 283 | @safe 284 | nothrow void clear() 285 | { 286 | initializeContext(); 287 | 288 | parsed_ = 0; 289 | } 290 | 291 | 292 | /** 293 | * Convenient method for unpacking and clearing states. 294 | * 295 | * Example: 296 | * ----- 297 | * foreach (obj; unpacker.purge()) { 298 | * // do stuff 299 | * } 300 | * ----- 301 | * is equivalent to 302 | * ----- 303 | * foreach (obj; unpacker.unpacked) { 304 | * // do stuff 305 | * } 306 | * unpacker.clear(); 307 | * ----- 308 | * 309 | * Returns: 310 | * the $(D Unpacked) object contains deserialized value. 311 | */ 312 | @safe 313 | Unpacked purge() 314 | { 315 | auto result = Unpacked(context_.stack[0].value); 316 | 317 | clear(); 318 | 319 | return result; 320 | } 321 | 322 | 323 | /** 324 | * Executes deserialization. 325 | * 326 | * Returns: 327 | * true if deserialization has been completed, otherwise false. 328 | * 329 | * Throws: 330 | * $(D UnpackException) when parse error occurs. 331 | */ 332 | bool execute() 333 | { 334 | /* 335 | * Current implementation is very dirty(goto! goto!! goto!!!). 336 | * This Complexity for performance(avoid function call). 337 | */ 338 | 339 | bool ret; 340 | size_t cur = offset_; 341 | Value obj; 342 | 343 | // restores before state 344 | auto state = context_.state; 345 | auto trail = context_.trail; 346 | auto top = context_.top; 347 | auto stack = &context_.stack; 348 | 349 | /* 350 | * Helper for container deserialization 351 | */ 352 | void startContainer(string Type)(ContainerElement type, size_t length) 353 | { 354 | mixin("callback" ~ Type ~ "((*stack)[top].value, length);"); 355 | 356 | (*stack)[top].type = type; 357 | (*stack)[top].count = length; 358 | (*stack).length = ++top + 1; 359 | } 360 | 361 | // non-deserialized data is nothing 362 | if (used_ - offset_ == 0) 363 | goto Labort; 364 | 365 | do { 366 | Lstart: 367 | if (state == State.HEADER) { 368 | const header = buffer_[cur]; 369 | 370 | if (0x00 <= header && header <= 0x7f) { // positive 371 | callbackUInt(obj, header); 372 | goto Lpush; 373 | } else if (0xe0 <= header && header <= 0xff) { // negative 374 | callbackInt(obj, cast(byte)header); 375 | goto Lpush; 376 | } else if (0xa0 <= header && header <= 0xbf) { // fix raw 377 | trail = header & 0x1f; 378 | state = State.RAW; 379 | cur++; 380 | continue; 381 | } else if (0xd4 <= header && header <= 0xd8) { // fix ext 382 | trail = 2 ^^ (header - 0xd4) + 1; 383 | state = State.EXT_DATA; 384 | cur++; 385 | continue; 386 | } else if (0x90 <= header && header <= 0x9f) { // fix array 387 | size_t length = header & 0x0f; 388 | if (length == 0) { 389 | callbackArray(obj, 0); 390 | goto Lpush; 391 | } else { 392 | startContainer!"Array"(ContainerElement.ARRAY_ITEM, length); 393 | cur++; 394 | continue; 395 | } 396 | } else if (0x80 <= header && header <= 0x8f) { // fix map 397 | size_t length = header & 0x0f; 398 | if (length == 0) { 399 | callbackMap(obj, 0); 400 | goto Lpush; 401 | } else { 402 | startContainer!"Map"(ContainerElement.MAP_KEY, length); 403 | cur++; 404 | continue; 405 | } 406 | } else { 407 | switch (header) { 408 | case Format.UINT8, Format.UINT16, Format.UINT32, Format.UINT64, 409 | Format.INT8, Format.INT16, Format.INT32, Format.INT64, 410 | Format.FLOAT, Format.DOUBLE: 411 | trail = 1 << (header & 0x03); // computes object size 412 | state = cast(State)(header & 0x1f); 413 | break; 414 | case Format.REAL: 415 | trail = RealSize; 416 | state = State.REAL; 417 | break; 418 | case Format.ARRAY16, Format.ARRAY32, 419 | Format.MAP16, Format.MAP32: 420 | trail = 2 << (header & 0x01); // computes container size 421 | state = cast(State)(header & 0x1f); 422 | break; 423 | // raw will become str format in new spec 424 | case Format.STR8: 425 | case Format.RAW16: // will be STR16 426 | case Format.RAW32: // will be STR32 427 | trail = 1 << ((header & 0x03) - 1); // computes container size 428 | state = cast(State)(header & 0x1f); 429 | break; 430 | case Format.BIN8, Format.BIN16, Format.BIN32: 431 | trail = 1 << (header & 0x03); // computes container size 432 | state = cast(State)(header & 0x1f); 433 | break; 434 | case Format.EXT8: 435 | trail = 1; 436 | state = State.EXT8; 437 | break; 438 | case Format.EXT16: 439 | trail = 2; 440 | state = State.EXT16; 441 | break; 442 | case Format.EXT32: 443 | trail = 4; 444 | state = State.EXT32; 445 | break; 446 | case Format.NIL: 447 | callbackNil(obj); 448 | goto Lpush; 449 | case Format.TRUE: 450 | callbackBool(obj, true); 451 | goto Lpush; 452 | case Format.FALSE: 453 | callbackBool(obj, false); 454 | goto Lpush; 455 | default: 456 | throw new UnpackException("Unknown type"); 457 | } 458 | 459 | cur++; 460 | goto Lstart; 461 | } 462 | } else { 463 | // data lack for deserialization 464 | if (used_ - cur < trail) 465 | goto Labort; 466 | 467 | const base = cur; cur += trail - 1; // fix current position 468 | 469 | final switch (state) { 470 | case State.FLOAT: 471 | _f temp; 472 | 473 | temp.i = load32To!uint(buffer_[base..base + trail]); 474 | callbackFloat(obj, temp.f); 475 | goto Lpush; 476 | case State.DOUBLE: 477 | _d temp; 478 | 479 | temp.i = load64To!ulong(buffer_[base..base + trail]); 480 | callbackFloat(obj, temp.f); 481 | goto Lpush; 482 | case State.REAL: 483 | const expb = base + ulong.sizeof; 484 | 485 | version (NonX86) 486 | { 487 | CustomFloat!80 temp; 488 | 489 | const frac = load64To!ulong (buffer_[base..expb]); 490 | const exp = load16To!ushort(buffer_[expb..expb + ushort.sizeof]); 491 | 492 | temp.significand = frac; 493 | temp.exponent = exp & 0x7fff; 494 | temp.sign = exp & 0x8000 ? true : false; 495 | 496 | // NOTE: temp.get!real is inf on non-x86 when deserialized value is larger than double.max. 497 | callbackFloat(obj, temp.get!real); 498 | } 499 | else 500 | { 501 | _r temp; 502 | 503 | temp.fraction = load64To!(typeof(temp.fraction))(buffer_[base..expb]); 504 | temp.exponent = load16To!(typeof(temp.exponent))(buffer_[expb..expb + temp.exponent.sizeof]); 505 | 506 | callbackFloat(obj, temp.f); 507 | } 508 | 509 | goto Lpush; 510 | case State.UINT8: 511 | callbackUInt(obj, buffer_[base]); 512 | goto Lpush; 513 | case State.UINT16: 514 | callbackUInt(obj, load16To!ushort(buffer_[base..base + trail])); 515 | goto Lpush; 516 | case State.UINT32: 517 | callbackUInt(obj, load32To!uint(buffer_[base..base + trail])); 518 | goto Lpush; 519 | case State.UINT64: 520 | callbackUInt(obj, load64To!ulong(buffer_[base..base + trail])); 521 | goto Lpush; 522 | case State.INT8: 523 | callbackInt(obj, cast(byte)buffer_[base]); 524 | goto Lpush; 525 | case State.INT16: 526 | callbackInt(obj, load16To!short(buffer_[base..base + trail])); 527 | goto Lpush; 528 | case State.INT32: 529 | callbackInt(obj, load32To!int(buffer_[base..base + trail])); 530 | goto Lpush; 531 | case State.INT64: 532 | callbackInt(obj, load64To!long(buffer_[base..base + trail])); 533 | goto Lpush; 534 | case State.RAW: Lraw: 535 | hasRaw_ = true; 536 | callbackRaw(obj, buffer_[base..base + trail]); 537 | goto Lpush; 538 | 539 | case State.EXT_DATA: Lext: 540 | hasRaw_ = true; 541 | obj.via.ext.type = buffer_[base]; 542 | callbackExt(obj, buffer_[base+1..base+trail]); 543 | goto Lpush; 544 | case State.EXT8: 545 | trail = buffer_[base] + 1; 546 | if (trail == 0) 547 | goto Lext; 548 | state = State.EXT_DATA; 549 | cur++; 550 | goto Lstart; 551 | case State.EXT16: 552 | trail = load16To!size_t(buffer_[base..base+trail]) + 1; 553 | if (trail == 0) 554 | goto Lext; 555 | state = State.EXT_DATA; 556 | cur++; 557 | goto Lstart; 558 | case State.EXT32: 559 | trail = load32To!size_t(buffer_[base..base+trail]) + 1; 560 | if (trail == 0) 561 | goto Lext; 562 | state = State.EXT_DATA; 563 | cur++; 564 | goto Lstart; 565 | 566 | case State.STR8, State.BIN8: 567 | trail = buffer_[base]; 568 | if (trail == 0) 569 | goto Lraw; 570 | state = State.RAW; 571 | cur++; 572 | goto Lstart; 573 | case State.RAW16, State.BIN16: 574 | trail = load16To!size_t(buffer_[base..base + trail]); 575 | if (trail == 0) 576 | goto Lraw; 577 | state = State.RAW; 578 | cur++; 579 | goto Lstart; 580 | case State.RAW32, State.BIN32: 581 | trail = load32To!size_t(buffer_[base..base + trail]); 582 | if (trail == 0) 583 | goto Lraw; 584 | state = State.RAW; 585 | cur++; 586 | goto Lstart; 587 | case State.ARRAY16: 588 | size_t length = load16To!size_t(buffer_[base..base + trail]); 589 | if (length == 0) { 590 | callbackArray(obj, 0); 591 | goto Lpush; 592 | } else { 593 | startContainer!"Array"(ContainerElement.ARRAY_ITEM, length); 594 | state = State.HEADER; 595 | cur++; 596 | continue; 597 | } 598 | case State.ARRAY36: 599 | size_t length = load32To!size_t(buffer_[base..base + trail]); 600 | if (length == 0) { 601 | callbackArray(obj, 0); 602 | goto Lpush; 603 | } else { 604 | startContainer!"Array"(ContainerElement.ARRAY_ITEM, length); 605 | state = State.HEADER; 606 | cur++; 607 | continue; 608 | } 609 | case State.MAP16: 610 | size_t length = load16To!size_t(buffer_[base..base + trail]); 611 | if (length == 0) { 612 | callbackMap(obj, 0); 613 | goto Lpush; 614 | } else { 615 | startContainer!"Map"(ContainerElement.MAP_KEY, length); 616 | state = State.HEADER; 617 | cur++; 618 | continue; 619 | } 620 | case State.MAP32: 621 | size_t length = load32To!size_t(buffer_[base..base + trail]); 622 | if (length == 0) { 623 | callbackMap(obj, 0); 624 | goto Lpush; 625 | } else { 626 | startContainer!"Map"(ContainerElement.MAP_KEY, length); 627 | state = State.HEADER; 628 | cur++; 629 | continue; 630 | } 631 | case State.HEADER: 632 | break; 633 | } 634 | } 635 | 636 | Lpush: 637 | if (top == 0) 638 | goto Lfinish; 639 | 640 | auto container = &(*stack)[top - 1]; 641 | 642 | final switch (container.type) { 643 | case ContainerElement.ARRAY_ITEM: 644 | container.value.via.array ~= obj; 645 | if (--container.count == 0) { 646 | obj = container.value; 647 | top--; 648 | goto Lpush; 649 | } 650 | break; 651 | case ContainerElement.MAP_KEY: 652 | container.key = obj; 653 | container.type = ContainerElement.MAP_VALUE; 654 | break; 655 | case ContainerElement.MAP_VALUE: 656 | container.value.via.map[container.key] = obj; 657 | if (--container.count == 0) { 658 | obj = container.value; 659 | top--; 660 | goto Lpush; 661 | } 662 | container.type = ContainerElement.MAP_KEY; 663 | } 664 | 665 | state = State.HEADER; 666 | cur++; 667 | } while (cur < used_); 668 | 669 | goto Labort; 670 | 671 | Lfinish: 672 | (*stack)[0].value = obj; 673 | ret = true; 674 | cur++; 675 | goto Lend; 676 | 677 | Labort: 678 | ret = false; 679 | 680 | Lend: 681 | context_.state = state; 682 | context_.trail = trail; 683 | context_.top = top; 684 | parsed_ += cur - offset_; 685 | offset_ = cur; 686 | 687 | return ret; 688 | } 689 | 690 | 691 | /** 692 | * supports foreach. One loop provides $(D Unpacked) object contains execute() result. 693 | * This is convenient in case that $(D MessagePack) values are continuous. 694 | */ 695 | int opApply(scope int delegate(ref Unpacked) dg) 696 | { 697 | int result; 698 | 699 | while (execute()) { 700 | auto unpackedResult = Unpacked(context_.stack[0].value); 701 | result = dg(unpackedResult); 702 | if (result) 703 | break; 704 | 705 | clear(); 706 | } 707 | 708 | return result; 709 | } 710 | 711 | 712 | private: 713 | /* 714 | * initializes internal stack environment. 715 | */ 716 | @safe 717 | nothrow void initializeContext() 718 | { 719 | context_.state = State.HEADER; 720 | context_.trail = 0; 721 | context_.top = 0; 722 | context_.stack.length = 1; 723 | } 724 | } 725 | 726 | 727 | unittest 728 | { 729 | import msgpack.packer; 730 | 731 | { 732 | // serialize 733 | mixin DefinePacker; 734 | 735 | packer.packArray(null, true, 1, -2, "Hi!", [1], [1:1], double.max, ExtValue(7, [1,2,3,4])); 736 | 737 | // deserialize 738 | auto unpacker = StreamingUnpacker(packer.stream.data); unpacker.execute(); 739 | auto unpacked = unpacker.purge(); 740 | 741 | // Range test 742 | foreach (unused; 0..2) { 743 | uint i; 744 | 745 | foreach (obj; unpacked) 746 | i++; 747 | 748 | assert(i == unpacked.via.array.length); 749 | } 750 | 751 | auto result = unpacked.via.array; 752 | 753 | assert(result[0].type == Value.Type.nil); 754 | assert(result[1].via.boolean == true); 755 | assert(result[2].via.uinteger == 1); 756 | assert(result[3].via.integer == -2); 757 | assert(result[4].via.raw == [72, 105, 33]); 758 | assert(result[5].as!(int[]) == [1]); 759 | assert(result[6].as!(int[int]) == [1:1]); 760 | assert(result[7].as!(double) == double.max); 761 | assert(result[8].as!(ExtValue) == ExtValue(7, [1,2,3,4])); 762 | } 763 | 764 | // Test many combinations of EXT 765 | { 766 | mixin DefinePacker; 767 | 768 | alias Lengths = TypeTuple!(0, 1, 2, 3, 4, 5, 8, 15, 16, 31, 769 | 255, 256, 2^^16, 2^^32); 770 | 771 | // Initialize a bunch of ExtValues and pack them 772 | ExtValue[Lengths.length] values; 773 | foreach (I, L; Lengths) 774 | values[I] = ExtValue(7, new ubyte[](L)); 775 | packer.pack(values); 776 | 777 | auto unpacker = StreamingUnpacker(packer.stream.data); unpacker.execute(); 778 | auto unpacked = unpacker.purge(); 779 | 780 | // Compare unpacked values to originals 781 | size_t i = 0; 782 | foreach (deserialized; unpacked) 783 | assert(deserialized == values[i++]); 784 | } 785 | } 786 | 787 | 788 | private: 789 | @trusted: 790 | 791 | 792 | /** 793 | * Sets value type and value. 794 | * 795 | * Params: 796 | * value = the value to set 797 | * number = the content to set 798 | */ 799 | void callbackUInt(ref Value value, ulong number) 800 | { 801 | value.type = Value.Type.unsigned; 802 | value.via.uinteger = number; 803 | } 804 | 805 | 806 | /// ditto 807 | void callbackInt(ref Value value, long number) 808 | { 809 | value.type = Value.Type.signed; 810 | value.via.integer = number; 811 | } 812 | 813 | 814 | /// ditto 815 | void callbackFloat(ref Value value, real number) 816 | { 817 | value.type = Value.Type.floating; 818 | value.via.floating = number; 819 | } 820 | 821 | 822 | /// ditto 823 | void callbackRaw(ref Value value, ubyte[] raw) 824 | { 825 | value.type = Value.Type.raw; 826 | value.via.raw = raw; 827 | } 828 | 829 | /// ditto 830 | void callbackExt(ref Value value, ubyte[] raw) 831 | { 832 | value.type = Value.Type.ext; 833 | value.via.ext.data = raw; 834 | } 835 | 836 | /// ditto 837 | void callbackArray(ref Value value, size_t length) 838 | { 839 | value.type = Value.Type.array; 840 | value.via.array.length = 0; 841 | value.via.array.reserve(length); 842 | } 843 | 844 | 845 | /// ditto 846 | void callbackMap(ref Value value, lazy size_t length) 847 | { 848 | value.type = Value.Type.map; 849 | value.via.map = null; // clears previous result avoiding 'Access Violation' 850 | } 851 | 852 | 853 | /// ditto 854 | void callbackNil(ref Value value) 855 | { 856 | value.type = Value.Type.nil; 857 | } 858 | 859 | 860 | /// ditto 861 | void callbackBool(ref Value value, bool boolean) 862 | { 863 | value.type = Value.Type.boolean; 864 | value.via.boolean = boolean; 865 | } 866 | 867 | 868 | unittest 869 | { 870 | Value value; 871 | 872 | // Unsigned integer 873 | callbackUInt(value, uint.max); 874 | assert(value.type == Value.Type.unsigned); 875 | assert(value.via.uinteger == uint.max); 876 | 877 | // Signed integer 878 | callbackInt(value, int.min); 879 | assert(value.type == Value.Type.signed); 880 | assert(value.via.integer == int.min); 881 | 882 | // Floating point 883 | callbackFloat(value, real.max); 884 | assert(value.type == Value.Type.floating); 885 | assert(value.via.floating == real.max); 886 | 887 | // Raw 888 | callbackRaw(value, cast(ubyte[])[1]); 889 | assert(value.type == Value.Type.raw); 890 | assert(value.via.raw == cast(ubyte[])[1]); 891 | 892 | // Array 893 | Value[] array; array.reserve(16); 894 | 895 | callbackArray(value, 16); 896 | assert(value.type == Value.Type.array); 897 | assert(value.via.array.capacity == array.capacity); 898 | 899 | // Map 900 | Value[Value] map; 901 | 902 | callbackMap(value, 16); 903 | assert(value.type == Value.Type.map); 904 | assert(value.via.map == null); 905 | 906 | // NIL 907 | callbackNil(value); 908 | assert(value.type == Value.Type.nil); 909 | 910 | // Bool 911 | callbackBool(value, true); 912 | assert(value.type == Value.Type.boolean); 913 | assert(value.via.boolean == true); 914 | } 915 | -------------------------------------------------------------------------------- /src/msgpack/value.d: -------------------------------------------------------------------------------- 1 | module msgpack.value; 2 | 3 | import msgpack.common; 4 | import msgpack.attribute; 5 | import msgpack.exception; 6 | 7 | import std.json; 8 | import std.container : Array; 9 | import std.traits; 10 | import std.typecons : Tuple, isTuple; 11 | 12 | 13 | /** 14 | * $(D Value) is a $(D MessagePack) value representation 15 | * 16 | * Example: 17 | * ----- 18 | * auto unpacker = StreamingUnpacker(pack(1, 0.1L) ~ pack(true) ~ pack("foobarbaz")); 19 | * 20 | * foreach (unpacked; unpacker) { 21 | * if (unpacked.type == Value.Type.array) { 22 | * foreach (obj; unpacked) { 23 | * switch (obj.type) { 24 | * case Value.Type.unsigned: writeln(obj.as!(uint)); break; 25 | * case Value.Type.floating: writeln(obj.as!(real)); break; 26 | * defalut: 27 | * throw new Exception("Unknown type"); 28 | * } 29 | * } 30 | * } else { 31 | * if (unpacked.type == Value.Type.boolean) 32 | * writeln(unpacked.as!(bool)); 33 | * else 34 | * writeln("Message: ", unpacked.as!(string)); 35 | * } 36 | * } 37 | * ----- 38 | */ 39 | struct Value 40 | { 41 | /** 42 | * $(D MessagePack) value type 43 | */ 44 | static enum Type 45 | { 46 | nil, /// nil(null in D) 47 | boolean, /// true, false 48 | unsigned, /// positive fixnum, uint 8, uint 16, uint 32, uint 64 49 | signed, /// negative fixnum, int 8, int 16, int 32, int 64 50 | floating, /// float, double, real 51 | array, /// fix array, array 16, array 32 52 | map, /// fix map, map 16, map 32 53 | raw, /// fix raw, raw 16, raw 32 54 | ext /// fix ext, ext8, ext16, ext32 55 | } 56 | 57 | 58 | /** 59 | * msgpack value representation 60 | */ 61 | static union Via 62 | { 63 | bool boolean; /// corresponding to Type.boolean 64 | ulong uinteger; /// corresponding to Type.unsigned 65 | long integer; /// corresponding to Type.signed 66 | real floating; /// corresponding to Type.floating 67 | Value[] array; /// corresponding to Type.array 68 | Value[Value] map; /// corresponding to Type.map 69 | ubyte[] raw; /// corresponding to Type.raw 70 | ExtValue ext; /// corresponding to Type.ext 71 | } 72 | 73 | 74 | Type type; /// represents value type 75 | Via via; /// represents real value 76 | 77 | 78 | /** 79 | * Constructs a $(D Value) with arguments. 80 | * 81 | * Params: 82 | * value = the real content. 83 | * type = the type of value. 84 | */ 85 | @safe 86 | this(Type type) 87 | { 88 | this.type = type; 89 | } 90 | 91 | @safe 92 | this(typeof(null)) 93 | { 94 | this(Type.nil); 95 | } 96 | 97 | /// ditto 98 | @trusted 99 | this(bool value, Type type = Type.boolean) 100 | { 101 | this(type); 102 | via.boolean = value; 103 | } 104 | 105 | 106 | /// ditto 107 | @trusted 108 | this(ulong value, Type type = Type.unsigned) 109 | { 110 | this(type); 111 | via.uinteger = value; 112 | } 113 | 114 | 115 | /// ditto 116 | @trusted 117 | this(long value, Type type = Type.signed) 118 | { 119 | this(type); 120 | via.integer = value; 121 | } 122 | 123 | 124 | /// ditto 125 | @trusted 126 | this(real value, Type type = Type.floating) 127 | { 128 | this(type); 129 | via.floating = value; 130 | } 131 | 132 | 133 | /// ditto 134 | @trusted 135 | this(Value[] value, Type type = Type.array) 136 | { 137 | this(type); 138 | via.array = value; 139 | } 140 | 141 | 142 | /// ditto 143 | @trusted 144 | this(Value[Value] value, Type type = Type.map) 145 | { 146 | this(type); 147 | via.map = value; 148 | } 149 | 150 | 151 | /// ditto 152 | @trusted 153 | this(ubyte[] value, Type type = Type.raw) 154 | { 155 | this(type); 156 | via.raw = value; 157 | } 158 | 159 | /// This is unsafe overload because using cast internally. 160 | @trusted 161 | this(string value, Type type = Type.raw) 162 | { 163 | this(type); 164 | via.raw = cast(ubyte[])value; 165 | } 166 | 167 | /** 168 | * Constructs a $(D Value) with arguments. 169 | * 170 | * Params: 171 | * value = the real content. 172 | * type = the type of value. 173 | */ 174 | @trusted 175 | this(ExtValue value, Type type = Type.ext) 176 | { 177 | this(type); 178 | via.ext = value; 179 | } 180 | 181 | /** 182 | * Converts value to $(D_PARAM T) type. 183 | * 184 | * Returns: 185 | * converted value. 186 | * 187 | * Throws: 188 | * MessagePackException if type is mismatched. 189 | * 190 | * NOTE: 191 | * Current implementation uses cast. 192 | */ 193 | @property @trusted 194 | T as(T)() if (is(Unqual!T == bool)) 195 | { 196 | if (type != Type.boolean) 197 | onCastError(); 198 | 199 | return via.boolean; 200 | } 201 | 202 | 203 | /// ditto 204 | @property @trusted 205 | T as(T)() if (isIntegral!T && !is(Unqual!T == enum)) 206 | { 207 | if (type == Type.unsigned) 208 | return cast(T)via.uinteger; 209 | 210 | if (type == Type.signed) 211 | return cast(T)via.integer; 212 | 213 | onCastError(); 214 | 215 | assert(false); 216 | } 217 | 218 | 219 | /// ditto 220 | @property @trusted 221 | T as(T)() if (isFloatingPoint!T && !is(Unqual!T == enum)) 222 | { 223 | if (type != Type.floating) 224 | onCastError(); 225 | 226 | return cast(T)via.floating; 227 | } 228 | 229 | 230 | /// ditto 231 | @property @trusted 232 | T as(T)() if (is(Unqual!T == enum)) 233 | { 234 | return cast(T)as!(OriginalType!T); 235 | } 236 | 237 | 238 | /// ditto 239 | @property @trusted 240 | T as(T)() if (is(Unqual!T == ExtValue)) 241 | { 242 | if (type != Type.ext) 243 | onCastError(); 244 | 245 | return cast(T)via.ext; 246 | } 247 | 248 | 249 | /// ditto 250 | @property @trusted 251 | T as(T)() if ((isArray!T || 252 | isInstanceOf!(Array, T)) && 253 | !is(Unqual!T == enum)) 254 | { 255 | alias typeof(T.init[0]) V; 256 | 257 | if (type == Type.nil) { 258 | static if (isDynamicArray!T) { 259 | return null; 260 | } else { 261 | return T.init; 262 | } 263 | } 264 | 265 | static if (isByte!V || isSomeChar!V) { 266 | if (type != Type.raw) 267 | onCastError(); 268 | 269 | static if (isDynamicArray!T) { 270 | return cast(T)via.raw; 271 | } else { 272 | if (via.raw.length != T.length) 273 | onCastError(); 274 | 275 | return cast(T)(via.raw[0 .. T.length]); 276 | } 277 | } else { 278 | if (type != Type.array) 279 | onCastError(); 280 | 281 | V[] array; 282 | 283 | foreach (elem; via.array) 284 | array ~= elem.as!(V); 285 | 286 | return array; 287 | } 288 | } 289 | 290 | 291 | /// ditto 292 | @property @trusted 293 | T as(T)() if (isAssociativeArray!T) 294 | { 295 | alias typeof(T.init.keys[0]) K; 296 | alias typeof(T.init.values[0]) V; 297 | 298 | if (type == Type.nil) 299 | return null; 300 | 301 | if (type != Type.map) 302 | onCastError(); 303 | 304 | V[K] map; 305 | 306 | foreach (key, value; via.map) 307 | map[key.as!(K)] = value.as!(V); 308 | 309 | return map; 310 | } 311 | 312 | 313 | /** 314 | * Converts to $(D_PARAM T) type. 315 | * 316 | * Calling $(D fromMsgpack) if $(D_KEYWORD class) and $(D_KEYWORD struct) implement $(D fromMsgpack) method. $(D fromMsgpack) signature is: 317 | * ----- 318 | * void fromMsgpack(Value value) 319 | * ----- 320 | * This method assigns converted values to all members of T object if $(D_KEYWORD class) and $(D_KEYWORD struct) don't implement $(D fromMsgpack). 321 | * 322 | * Params: 323 | * args = arguments to class constructor(class only). 324 | * 325 | * Returns: 326 | * converted value. 327 | */ 328 | @property @trusted 329 | T as(T, Args...)(Args args) if (is(Unqual!T == class)) 330 | { 331 | if (type == Type.nil) 332 | return null; 333 | 334 | T object = new T(args); 335 | 336 | static if (hasMember!(T, "fromMsgpack")) 337 | { 338 | static if (__traits(compiles, { object.fromMsgpack(this); })) { 339 | object.fromMsgpack(this); 340 | } else { 341 | static assert(0, "Failed to invoke 'fromMsgpack' on type '" ~ Unqual!T.stringof ~ "'"); 342 | } 343 | } else { 344 | alias SerializingClasses!(T) Classes; 345 | 346 | if (via.array.length != SerializingMemberNumbers!(Classes)) 347 | throw new MessagePackException("The number of deserialized object member is mismatched"); 348 | 349 | size_t offset; 350 | foreach (Class; Classes) { 351 | Class obj = cast(Class)object; 352 | foreach (i, member; obj.tupleof) { 353 | static if (isPackedField!(Class.tupleof[i])) 354 | obj.tupleof[i] = via.array[offset++].as!(typeof(member)); 355 | } 356 | } 357 | } 358 | 359 | return object; 360 | } 361 | 362 | 363 | /// ditto 364 | @property @trusted 365 | T as(T)() if (is(Unqual!T == struct) && !is(Unqual!T == ExtValue)) 366 | { 367 | T obj; 368 | 369 | static if (hasMember!(T, "fromMsgpack")) 370 | { 371 | static if (__traits(compiles, { obj.fromMsgpack(this); })) { 372 | obj.fromMsgpack(this); 373 | } else { 374 | static assert(0, "Failed to invoke 'fromMsgpack' on type '" ~ Unqual!T.stringof ~ "'"); 375 | } 376 | } else { 377 | static if (isTuple!T) { 378 | if (via.array.length != T.Types.length) 379 | throw new MessagePackException("The number of deserialized Tuple element is mismatched"); 380 | 381 | foreach (i, Type; T.Types) 382 | obj.field[i] = via.array[i].as!(Type); 383 | } else { // simple struct 384 | if (via.array.length != SerializingMemberNumbers!T) 385 | throw new MessagePackException("The number of deserialized struct member is mismatched"); 386 | 387 | size_t offset; 388 | foreach (i, member; obj.tupleof) { 389 | static if (isPackedField!(T.tupleof[i])) 390 | obj.tupleof[i] = via.array[offset++].as!(typeof(member)); 391 | } 392 | } 393 | } 394 | 395 | return obj; 396 | } 397 | 398 | 399 | /** 400 | * Special method called by $(D Packer). 401 | * 402 | * Params: 403 | * packer = a MessagePack serializer. 404 | */ 405 | void toMsgpack(Packer)(ref Packer packer) const 406 | { 407 | final switch (type) { 408 | case Type.nil: 409 | packer.pack(null); 410 | break; 411 | case Type.boolean: 412 | packer.pack(via.boolean); 413 | break; 414 | case Type.unsigned: 415 | packer.pack(via.uinteger); 416 | break; 417 | case Type.signed: 418 | packer.pack(via.integer); 419 | break; 420 | case Type.floating: 421 | packer.pack(via.floating); 422 | break; 423 | case Type.raw: 424 | packer.pack(via.raw); 425 | break; 426 | case Type.ext: 427 | packer.packExt(via.ext.type, via.ext.data); 428 | break; 429 | case Type.array: 430 | packer.beginArray(via.array.length); 431 | foreach (elem; via.array) 432 | elem.toMsgpack(packer); 433 | break; 434 | case Type.map: 435 | packer.beginMap(via.map.length); 436 | foreach (key, value; via.map) { 437 | key.toMsgpack(packer); 438 | value.toMsgpack(packer); 439 | } 440 | break; 441 | } 442 | } 443 | 444 | 445 | /** 446 | * Comparison for equality. @trusted for union. 447 | */ 448 | @trusted 449 | bool opEquals(Tdummy = void)(ref const Value other) const 450 | { 451 | if (type != other.type) 452 | return false; 453 | 454 | final switch (other.type) { 455 | case Type.nil: return true; 456 | case Type.boolean: return opEquals(other.via.boolean); 457 | case Type.unsigned: return opEquals(other.via.uinteger); 458 | case Type.signed: return opEquals(other.via.integer); 459 | case Type.floating: return opEquals(other.via.floating); 460 | case Type.raw: return opEquals(other.via.raw); 461 | case Type.ext: return opEquals(other.via.ext); 462 | case Type.array: return opEquals(other.via.array); 463 | case Type.map: return opEquals(other.via.map); 464 | } 465 | } 466 | 467 | 468 | /// ditto 469 | @trusted 470 | bool opEquals(T : bool)(in T other) const 471 | { 472 | if (type != Type.boolean) 473 | return false; 474 | 475 | return via.boolean == other; 476 | } 477 | 478 | 479 | /// ditto 480 | @trusted 481 | bool opEquals(T : ulong)(in T other) const 482 | { 483 | static if (__traits(isUnsigned, T)) { 484 | if (type != Type.unsigned) 485 | return false; 486 | 487 | return via.uinteger == other; 488 | } else { 489 | if (type != Type.signed) 490 | return false; 491 | 492 | return via.integer == other; 493 | } 494 | } 495 | 496 | 497 | /// ditto 498 | @trusted 499 | bool opEquals(T : real)(in T other) const 500 | { 501 | if (type != Type.floating) 502 | return false; 503 | 504 | return via.floating == other; 505 | } 506 | 507 | 508 | /// ditto 509 | @trusted 510 | bool opEquals(T : const Value[])(in T other) const 511 | { 512 | if (type != Type.array) 513 | return false; 514 | 515 | return via.array == other; 516 | } 517 | 518 | 519 | /// ditto 520 | @trusted 521 | bool opEquals(T : const Value[Value])(in T other) const 522 | { 523 | if (type != Type.map) 524 | return false; 525 | 526 | // This comparison is instead of default comparison because 'via.map == other' raises "Access Violation". 527 | foreach (key, value; via.map) { 528 | if (key in other) { 529 | if (other[key] != value) 530 | return false; 531 | } else { 532 | return false; 533 | } 534 | } 535 | 536 | return true; 537 | } 538 | 539 | 540 | /// ditto 541 | @trusted 542 | bool opEquals(T : const(ubyte)[])(in T other) const 543 | { 544 | if (type != Type.raw) 545 | return false; 546 | 547 | return via.raw == other; 548 | } 549 | 550 | 551 | /// ditto 552 | @trusted 553 | bool opEquals(T : string)(in T other) const 554 | { 555 | if (type != Type.raw) 556 | return false; 557 | 558 | return via.raw == cast(ubyte[])other; 559 | } 560 | 561 | 562 | // 563 | @trusted 564 | bool opEquals(T : ExtValue)(in T other) const 565 | { 566 | if (type != Type.ext) 567 | return false; 568 | 569 | return via.ext.type == other.type && via.ext.data == other.data; 570 | } 571 | 572 | 573 | @trusted 574 | hash_t toHash() const nothrow 575 | { 576 | static hash_t getHash(T)(T* v) @safe nothrow 577 | { 578 | return typeid(T).getHash(v); 579 | } 580 | 581 | final switch (type) { 582 | case Type.nil: return 0; 583 | case Type.boolean: return getHash(&via.boolean); 584 | case Type.unsigned: return getHash(&via.uinteger); 585 | case Type.signed: return getHash(&via.integer); 586 | case Type.floating: return getHash(&via.floating); 587 | case Type.raw: return getHash(&via.raw); 588 | case Type.ext: return getHash(&via.ext); 589 | case Type.array: 590 | hash_t ret; 591 | foreach (elem; via.array) 592 | ret ^= elem.toHash(); 593 | return ret; 594 | case Type.map: 595 | try { 596 | hash_t ret; 597 | foreach (key, value; via.map) { 598 | ret ^= key.toHash(); 599 | ret ^= value.toHash(); 600 | } 601 | return ret; 602 | } catch(Throwable) assert(0); 603 | } 604 | } 605 | } 606 | 607 | 608 | unittest 609 | { 610 | import std.array; 611 | 612 | // nil 613 | Value value = Value(null); 614 | Value other = Value(); 615 | 616 | assert(value == other); 617 | assert(value.type == Value.Type.nil); 618 | 619 | // boolean 620 | value = Value(true); 621 | other = Value(false); 622 | 623 | assert(value != other); 624 | assert(value.type == Value.Type.boolean); 625 | assert(value.as!(bool) == true); 626 | assert(other == false); 627 | 628 | try { 629 | auto b = value.as!(uint); 630 | assert(false); 631 | } catch (MessagePackException e) { } 632 | 633 | // unsigned integer 634 | value = Value(10UL); 635 | other = Value(10UL); 636 | 637 | assert(value == other); 638 | assert(value.type == Value.Type.unsigned); 639 | assert(value.as!(uint) == 10); 640 | assert(other == 10UL); 641 | 642 | // signed integer 643 | value = Value(-20L); 644 | other = Value(-10L); 645 | 646 | assert(value != other); 647 | assert(value.type == Value.Type.signed); 648 | assert(value.as!(int) == -20); 649 | assert(other == -10L); 650 | 651 | // enum 652 | enum E : int { F = -20 } 653 | 654 | E e = value.as!(E); 655 | assert(e == E.F); 656 | 657 | // floating point 658 | value = Value(0.1e-10L); 659 | other = Value(0.1e-20L); 660 | 661 | assert(value != other); 662 | assert(value.type == Value.Type.floating); 663 | assert(value.as!(real) == 0.1e-10L); 664 | assert(other == 0.1e-20L); 665 | 666 | // raw 667 | value = Value(cast(ubyte[])[72, 105, 33]); 668 | other = Value(cast(ubyte[])[72, 105, 33]); 669 | 670 | assert(value == other); 671 | assert(value.type == Value.Type.raw); 672 | assert(value.as!(string) == "Hi!"); 673 | assert(value.as!(ubyte[3]) == [72, 105, 33]); 674 | assert(other == cast(ubyte[])[72, 105, 33]); 675 | 676 | // raw with string 677 | value = Value("hello"); 678 | other = Value("hello"); 679 | 680 | assert(value == other); 681 | assert(value.type == Value.Type.raw); 682 | assert(value.as!(string) == "hello"); 683 | 684 | // enum : string 685 | enum EStr : string { elem = "hello" } 686 | 687 | assert(value.as!(EStr) == EStr.elem); 688 | 689 | // ext 690 | auto ext = ExtValue(7, [1,2,3]); 691 | value = Value(ExtValue(7, [1,2,3])); 692 | assert(value.as!ExtValue == ext); 693 | 694 | // array 695 | auto t = Value(cast(ubyte[])[72, 105, 33]); 696 | value = Value([t]); 697 | other = Value([t]); 698 | 699 | assert(value == other); 700 | assert(value.type == Value.Type.array); 701 | assert(value.as!(string[]) == ["Hi!"]); 702 | assert(other == [t]); 703 | 704 | // map 705 | value = Value([Value(1L):Value(2L)]); 706 | other = Value([Value(1L):Value(1L)]); 707 | 708 | assert(value != other); 709 | assert(value.type == Value.Type.map); 710 | assert(value.as!(int[int]) == [1:2]); 711 | assert(other == [Value(1L):Value(1L)]); 712 | 713 | value = Value(10UL); 714 | 715 | // struct 716 | static struct S 717 | { 718 | ulong num; 719 | 720 | void fromMsgpack(Value value) { num = value.via.uinteger; } 721 | } 722 | 723 | S s = value.as!(S); 724 | assert(s.num == 10); 725 | 726 | value = Value([Value(0.5f), Value(cast(ubyte[])[72, 105, 33])]); 727 | 728 | // struct 729 | static struct Simple 730 | { 731 | @nonPacked int era; 732 | double num; 733 | string msg; 734 | } 735 | 736 | Simple simple = value.as!(Simple); 737 | assert(simple.era == int.init); 738 | assert(simple.num == 0.5f); 739 | assert(simple.msg == "Hi!"); 740 | 741 | value = Value(10UL); 742 | 743 | // class 744 | static class C 745 | { 746 | ulong num; 747 | 748 | void fromMsgpack(Value value) { num = value.via.uinteger; } 749 | } 750 | 751 | C c = value.as!(C); 752 | assert(c.num == 10); 753 | 754 | static class SimpleA 755 | { 756 | bool flag = true; 757 | } 758 | 759 | static class SimpleB : SimpleA 760 | { 761 | ubyte type = 100; 762 | } 763 | 764 | static class SimpleC : SimpleB 765 | { 766 | @nonPacked string str; 767 | uint num = uint.max; 768 | } 769 | 770 | value = Value([Value(false), Value(99UL), Value(cast(ulong)(uint.max / 2u))]); 771 | 772 | SimpleC sc = value.as!(SimpleC); 773 | assert(sc.flag == false); 774 | assert(sc.type == 99); 775 | assert(sc.num == uint.max / 2); 776 | assert(sc.str.empty); 777 | 778 | // std.typecons.Tuple 779 | value = Value([Value(true), Value(1UL), Value(cast(ubyte[])"Hi!")]); 780 | 781 | auto tuple = value.as!(Tuple!(bool, uint, string)); 782 | assert(tuple.field[0] == true); 783 | assert(tuple.field[1] == 1u); 784 | assert(tuple.field[2] == "Hi!"); 785 | 786 | /* 787 | * non-MessagePackable object is stopped by static assert 788 | * static struct NonMessagePackable {} 789 | * auto nonMessagePackable = value.as!(NonMessagePackable); 790 | */ 791 | } 792 | 793 | 794 | /** 795 | * Converts $(D Value) to $(D JSONValue). 796 | * 797 | * Params: 798 | * val = $(D Value) to convert. 799 | * 800 | * Returns: 801 | * a $(D JSONValue). 802 | */ 803 | @trusted 804 | JSONValue toJSONValue(in Value val) 805 | { 806 | final switch (val.type) 807 | { 808 | case Value.Type.nil: return JSONValue(null); 809 | case Value.Type.boolean: return JSONValue(val.via.boolean); 810 | case Value.Type.unsigned: return JSONValue(val.via.uinteger); 811 | case Value.Type.signed: return JSONValue(val.via.integer); 812 | case Value.Type.floating: return JSONValue(val.via.floating); 813 | case Value.Type.raw: return JSONValue(cast(string)(val.via.raw.idup)); 814 | case Value.Type.ext: throw new MessagePackException("Unable to convert ext to json"); 815 | case Value.Type.array: { 816 | JSONValue[] vals; 817 | foreach (elem; val.via.array) 818 | vals ~= elem.toJSONValue(); 819 | return JSONValue(vals); 820 | } 821 | case Value.Type.map: { 822 | JSONValue[string] vals; 823 | foreach (key, value; val.via.map) { 824 | if (key.type != Value.Type.raw) 825 | { 826 | throw new MessagePackException("JSON-object key must be a raw type"); 827 | } 828 | vals[key.as!string] = value.toJSONValue(); 829 | } 830 | return JSONValue(vals); 831 | } 832 | } 833 | } 834 | 835 | /** 836 | * Converts $(D JSONValue) to $(D Value). 837 | * 838 | * Params: 839 | * val = $(D JSONValue) to convert. 840 | * 841 | * Returns: 842 | * a $(D Value). 843 | */ 844 | @trusted 845 | Value fromJSONValue(in JSONValue val) 846 | { 847 | final switch (val.type()) 848 | { 849 | case JSONType.null_: return Value(null); 850 | case JSONType.true_: return Value(true); 851 | case JSONType.false_: return Value(false); 852 | case JSONType.uinteger: return Value(val.uinteger); 853 | case JSONType.integer: return Value(val.integer); 854 | case JSONType.float_: return Value(val.floating); 855 | case JSONType.string: return Value(cast(ubyte[])(val.str)); 856 | case JSONType.array: { 857 | Value[] vals; 858 | foreach (elem; val.array) 859 | vals ~= elem.fromJSONValue(); 860 | return Value(vals); 861 | } 862 | case JSONType.object: { 863 | Value[Value] vals; 864 | foreach (key, value; val.object) { 865 | vals[Value(cast(ubyte[])key)] = value.fromJSONValue(); 866 | } 867 | return Value(vals); 868 | } 869 | } 870 | } 871 | 872 | unittest 873 | { 874 | import std.array : array; 875 | import std.algorithm : equal, map; 876 | import std.conv; 877 | import std.math : isClose; 878 | import std.range; 879 | import msgpack; 880 | 881 | // nil 882 | Value value = Value(null); 883 | 884 | assert(toJSONValue(value).type() == JSONType.null_); 885 | 886 | // boolean 887 | value = Value(true); 888 | auto other = Value(false); 889 | 890 | assert(toJSONValue(value).type() == JSONType.true_); 891 | assert(toJSONValue(other).type() == JSONType.false_); 892 | 893 | // unsigned integer 894 | value = Value(10UL); 895 | 896 | assert(value.toJSONValue().type == JSONType.uinteger); 897 | assert(value.toJSONValue().uinteger == value.as!uint); 898 | assert(value.toJSONValue().uinteger == 10UL); 899 | 900 | // signed integer 901 | value = Value(-20L); 902 | 903 | assert(value.toJSONValue().type == JSONType.integer); 904 | assert(value.toJSONValue().integer == value.as!int); 905 | 906 | // enum 907 | enum E : int { F = -20 } 908 | value = Value(cast(long)(E.F)); 909 | 910 | assert(value.toJSONValue().type == JSONType.integer); 911 | assert(value.toJSONValue().integer == E.F); 912 | 913 | // floating point 914 | value = Value(0.1e-10L); 915 | other = Value(0.1e-20L); 916 | 917 | assert(value.toJSONValue().type == JSONType.float_); 918 | assert(other.toJSONValue().type == JSONType.float_); 919 | 920 | assert(isClose(value.toJSONValue().floating, 0.1e-10L)); 921 | assert(isClose(other.toJSONValue().floating, 0.1e-20L)); 922 | 923 | // raw 924 | long[] arr = [72, 105, 33]; 925 | value = Value(to!(ubyte[])(arr)); 926 | 927 | assert(value.toJSONValue().type == JSONType.string); 928 | assert(equal(value.toJSONValue().str, arr)); 929 | 930 | // raw with string 931 | value = Value("hello"); 932 | assert(value.toJSONValue().type == JSONType.string); 933 | assert(value.toJSONValue().str == "hello"); 934 | 935 | // array 936 | auto t = Value(to!(ubyte[])(arr)); 937 | value = Value([t]); 938 | other = Value(array(map!(a => Value(a))(arr))); 939 | 940 | assert(value.toJSONValue().type == JSONType.array); 941 | assert(value.toJSONValue().array.length == 1); 942 | assert(value.toJSONValue().array.front().type == JSONType.string); 943 | assert(equal(value.toJSONValue().array.front().str, arr)); 944 | assert(other.toJSONValue().type == JSONType.array); 945 | assert(array(map!(a => a.integer)(other.toJSONValue().array)) == arr); 946 | 947 | // map 948 | value = Value([Value("key"):Value(2L)]); 949 | 950 | assert(value.toJSONValue().type == JSONType.object); 951 | assert("key" in value.toJSONValue().object); 952 | assert(value.toJSONValue().object["key"].type == JSONType.integer); 953 | assert(value.toJSONValue().object["key"].integer == 2L); 954 | 955 | // struct 956 | static struct Simple 957 | { 958 | @nonPacked int era; 959 | double num; 960 | string msg; 961 | } 962 | 963 | Simple simple; 964 | simple.era = 5; 965 | simple.num = 13.5; 966 | simple.msg = "helloworld"; 967 | value = simple.pack().unpack().value; 968 | 969 | assert(value.toJSONValue().type == JSONType.array); 970 | assert(value.toJSONValue().array.length == 2); 971 | assert(value.toJSONValue().array[0].type == JSONType.float_); 972 | assert(isClose(value.toJSONValue().array[0].floating, simple.num)); 973 | assert(value.toJSONValue().array[1].type == JSONType.string); 974 | assert(value.toJSONValue().array[1].str == simple.msg); 975 | 976 | // class 977 | static class SimpleA 978 | { 979 | bool flag = true; 980 | } 981 | 982 | static class SimpleB : SimpleA 983 | { 984 | ubyte type = 100; 985 | } 986 | 987 | static class SimpleC : SimpleB 988 | { 989 | @nonPacked string str; 990 | uint num = uint.max; 991 | } 992 | 993 | SimpleC sc = new SimpleC; 994 | value = sc.pack!true().unpack().value; 995 | 996 | assert(value.toJSONValue().type == JSONType.object); 997 | assert(value.toJSONValue().object.length == 3); 998 | assert("flag" in value.toJSONValue().object); 999 | assert(value.toJSONValue().object["flag"].type == (sc.flag ? JSONType.true_ : JSONType.false_)); 1000 | assert("type" in value.toJSONValue().object); 1001 | assert(value.toJSONValue().object["type"].type == JSONType.uinteger); 1002 | assert(value.toJSONValue().object["type"].uinteger == sc.type); 1003 | assert("num" in value.toJSONValue().object); 1004 | assert(value.toJSONValue().object["num"].type == JSONType.uinteger); 1005 | assert(value.toJSONValue().object["num"].uinteger == sc.num); 1006 | 1007 | other = value.toJSONValue().fromJSONValue(); 1008 | assert(value == other); 1009 | } 1010 | 1011 | 1012 | private: 1013 | 1014 | 1015 | /** 1016 | * A callback for type-mismatched error in cast conversion. 1017 | */ 1018 | @safe 1019 | pure void onCastError() 1020 | { 1021 | throw new MessagePackException("Attempt to cast with another type"); 1022 | } 1023 | -------------------------------------------------------------------------------- /win32.mak: -------------------------------------------------------------------------------- 1 | DMD = dmd 2 | LIB = msgpack.lib 3 | DFLAGS = -O -release -inline -nofloat -w -d -Isrc 4 | UDFLAGS = -w -g -debug -unittest 5 | 6 | SRCS = src\msgpack.d 7 | 8 | # DDoc 9 | DOCDIR = html 10 | CANDYDOC = html\candydoc\candy.ddoc html\candydoc\modules.ddoc 11 | DDOCFLAGS = -Dd$(DOCDIR) -c -o- -Isrc $(CANDYDOC) 12 | 13 | DOCS = $(DOCDIR)\msgpack.html 14 | 15 | target: doc $(LIB) 16 | 17 | $(LIB): 18 | $(DMD) $(DFLAGS) -lib -of$(LIB) $(SRCS) 19 | 20 | doc: 21 | $(DMD) $(DDOCFLAGS) $(SRCS) 22 | 23 | clean: 24 | rm $(DOCS) $(LIB) 25 | --------------------------------------------------------------------------------