├── .github └── workflows │ └── main.yml ├── LICENSE ├── README.md ├── binarylang.nim ├── binarylang.nimble ├── binarylang ├── operations.nim └── private │ ├── codegen │ ├── astutil.nim │ ├── conversion.nim │ ├── deserialization.nim │ └── serialization.nim │ ├── dsldecoders.nim │ ├── errors.nim │ └── types.nim ├── docs ├── changelog.html ├── changelog.rst ├── devmanual.html ├── devmanual.rst ├── index.html ├── nimdoc.out.css └── testresults.html └── tests ├── aligned.nim ├── assertions.nim ├── bitendian.nim ├── complex.nim ├── data ├── aligned.hex ├── assertions.hex ├── bitendian.hex ├── complex.hex ├── options.hex ├── plugins.hex ├── recursion.hex ├── repetition.hex ├── strings.hex ├── substreams.hex └── unaligned.hex ├── operations.nim ├── options.nim ├── plugins.nim ├── recursion.nim ├── repetition.nim ├── strings.nim ├── substreams.nim ├── tlv.nim ├── unaligned.nim ├── unnamedfields.nim └── visibility.nim /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: MAIN 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | docgen: 10 | runs-on: ubuntu-latest 11 | container: nimlang/nim 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Change Owner of Container Working Directory 15 | run: chown root:root . 16 | - name: Install dependencies 17 | run: nimble install -dy 18 | - name: Generate documentation 19 | run: | 20 | nim doc -o:docs/index.html binarylang.nim 21 | nim rst2html docs/changelog.rst 22 | nim rst2html docs/devmanual.rst 23 | mv docs/htmldocs/* docs/ 24 | rmdir docs/htmldocs 25 | - name: Run tests 26 | run: | 27 | cd tests 28 | testament pattern "*.nim" 29 | testament html 30 | mv testresults.html ../docs 31 | find . -maxdepth 1 -type f ! -name '*.nim' -delete 32 | rm -r testresults 33 | - name: Commit documentation 34 | run: | 35 | git config --global user.name 'Stefanos Mandalas' 36 | git config --global user.email 'sealmove@protonmail.com' 37 | git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} 38 | git add docs 39 | git commit -am "[skip ci] Update documentation" 40 | git push 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Stefanos Mandalas & Peter Munch-Ellingsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BinaryLang 2 | BinaryLang is an extensible Nim DSL for creating binary parsers/encoders in a 3 | symmetric fashion. 4 | 5 | It supports syntax for creating simple common parsers (ints, floats, strings), 6 | as well as ways for synthesizing more complex parsers out of simpler ones. 7 | Therefore, it is similar to a parser combinator framework. 8 | 9 | Moreover, you can mix DSL-generated parsers with custom ones and extend the DSL 10 | using nim templates. 11 | 12 | ## Documentation 13 | - [User Manual](https://sealmove.github.io/binarylang/) 14 | - [Developer Manual](https://sealmove.github.io/binarylang/devmanual.html) 15 | - [Changelog](https://sealmove.github.io/binarylang/changelog.html) 16 | - [Test Results](https://sealmove.github.io/binarylang/testresults.html) 17 | - Tutorials, write-ups, articles 18 | - [Ajusa on HTTP](https://ajusa.github.io/binarylang-fun/intro.html) 19 | - [Ajusa on Web Scraping](https://ajusa.github.io/binarylang-fun/scraping.html) -------------------------------------------------------------------------------- /binarylang.nim: -------------------------------------------------------------------------------- 1 | ## DSL invocation 2 | ## ---------------------------------------------------------------------------- 3 | ## Two macros are exported: 4 | ## - `struct` which is used to produce a *product parser* 5 | ## - `union` which is used to produce a *sum parser* 6 | ## 7 | ## Both of these macros generate a type declaration and a 8 | ## `tuple[get: proc, put: proc]`: 9 | ## - `get` returns an object with each parsed field 10 | ## - `put` writes an object to a stream 11 | ## Each statement corresponds to 1 field. The general syntax is: 12 | ## 13 | ## .. code:: 14 | ## type: name (...) 15 | ## 16 | ## - For the name you may use `_` to discard the field 17 | ## - Fields are public by default 18 | ## - You may append `{.private.}` to a field to make it private 19 | ## 20 | ## Parser options 21 | ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 22 | ## Each specified option must be in the form `option = value`: 23 | ## - `endian`: sets the default byte endianness for the whole parser 24 | ## - *default*: big endian 25 | ## - `b`: **big** endian 26 | ## - `l`: **little** endian 27 | ## - `c`: **cpu** endian 28 | ## - `bitEndian`: sets the default bit endianness for the whole parser 29 | ## - *default*: left -> right 30 | ## - `n`: left -> right (**normal**) 31 | ## - `r`: left <- right (**reverse**) 32 | ## - `reference`: configures whether the associated type will be a `ref` or not 33 | ## - *default*: no 34 | ## - `y`: yes 35 | ## - `n`: no 36 | ## - `plugins`: enable additional codegen features (value is a set) 37 | ## - `converters`: generate *from* and *to* procs for converting from/to 38 | ## `string` 39 | ## 40 | ## .. code-block:: nim 41 | ## struct(data, plugins = {converters}): 42 | ## 8: x 43 | ## 44 | ## var fileContent = readFile("data/plugins.hex") 45 | ## let data = fileContent.toData 46 | ## assert data.x == 0x41 47 | ## 48 | ## let reparsed = data.fromData 49 | ## assert reparsed == "A" 50 | ## 51 | ## - `visibility`: for parser, discr field and symbols generated by plugins 52 | ## - *default*: `public` 53 | ## - `public` 54 | ## - `private` 55 | ## 56 | ## Parser parameters 57 | ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 58 | ## Each parameter must be in the form `symbol: type`. The generated `get`/`put` 59 | ## procs will then have this additional parameter appended. 60 | ## 61 | ## The only exception is the discriminator field for **sum** parsers which is 62 | ## always named ``disc`` implicitly; and therefore, only the type must be 63 | ## provided -instead of an expression-colon-expression-. 64 | ## 65 | ## Types 66 | ## ---------------------------------------------------------------------------- 67 | ## 68 | ## Primitive types 69 | ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 70 | ## The **kind**, **endianness** and **size** are encoded in a identifier 71 | ## made up of: 72 | ## 73 | ## - 1 optional letter specifying the kind: 74 | ## - *default*: signed integer 75 | ## - `u`: unsigned integer 76 | ## - `f`: float 77 | ## - `s`: string 78 | ## - 1 optional letter specifying byte endianness: 79 | ## - *default*: big endian 80 | ## - `b`: big endian 81 | ## - `l`: little endian 82 | ## - 1 optional letter specifying bit endianness: 83 | ## - *default*: left -> right 84 | ## - `n`: left -> right (normal) 85 | ## - `r`: left <- right (reverse) 86 | ## - 1 number specifying size in **bits**: 87 | ## - for a string it refers to the size of each individual character and 88 | ## defaults to `8` 89 | ## - for an integer the allowed values are `1 .. 64` 90 | ## - for a float the allowed values are `32` and `64` 91 | ## 92 | ## You can order options however you want, but size must come last (e.g. 93 | ## `lru16` and `url16` are valid but not `16lru`). 94 | ## 95 | ## Assertion can also be used in a special manner to terminate the previous 96 | ## field if it's a **string** or a **sequence indicated as magic-terminated**. 97 | ## This is discussed in later sections. 98 | ## 99 | ## Product type 100 | ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 101 | ## A parser is of type **product** if it is created with the ``struct`` macro 102 | ## or *by hand*, as explained in a later section. To call a product parser you 103 | ## must use `*` followed by the name of the parser. If your parser requires 104 | ## arguments, you must provide them using standard call syntax. 105 | ## 106 | ## Example: 107 | ## 108 | ## .. code:: nim 109 | ## struct(inner): 110 | ## 32: a 111 | ## 32: b 112 | ## 113 | ## struct(innerWithArgs, size: int32): 114 | ## 32: a 115 | ## 32: b[size] 116 | ## 117 | ## struct(outer): 118 | ## *inner: x 119 | ## *innerWithArgs(x.a): y 120 | ## 121 | ## Sum type 122 | ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 123 | ## A parser is of type **sum** if it is created with the ``union`` macro or 124 | ## *by hand*, as explained in a later section. A sum parser has a special 125 | ## field called the *discriminator* which determines which branch will be 126 | ## activated at run-time -similarly to *object variants*-. 127 | ## 128 | ## To call a sum parser you must use `+` followed by a call-syntaxed expression. 129 | ## The callee is the name of the parser and the first argument is the value of 130 | ## the *discriminator* field. If the parser requires additional arguments, they 131 | ## also have to be provided. The first argument is treated in a special manner. 132 | ## Unlike other arguments, this one is only evaluated during parsing, whereas 133 | ## during serialization the value stored in the ``disc`` field is used. 134 | ## 135 | ## Example: 136 | ## 137 | ## .. code:: nim 138 | ## union(inner, byte): 139 | ## (0): 8: a 140 | ## (1): 16: b 141 | ## _: nil 142 | ## 143 | ## struct(outer): 144 | ## +inner(0): x 145 | ## 146 | ## Features 147 | ## ---------------------------------------------------------------------------- 148 | ## 149 | ## Alignment 150 | ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 151 | ## If any of the following is violated, BinaryLang should generate an 152 | ## exception: 153 | ## - Byte endianness can only be used with byte-multiple integers 154 | ## - Bit endianness must be uniform between **byte boundaries** 155 | ## - Spec must finish on a byte boundary 156 | ## 157 | ## .. code:: nim 158 | ## struct(parser, bitEndian = n): 159 | ## b9: a # error: cannot apply byte endianness 160 | ## r6: b # error: shares bits with previous byte 161 | ## 10: c # error: spec does not finish on a byte boundary 162 | ## 163 | ## Moreover, unaligned reads for strings are not supported: 164 | ## 165 | ## .. code:: nim 166 | ## struct(parser): 167 | ## 6: x 168 | ## s: y # invalid, generates an exception 169 | ## 170 | ## Assertion 171 | ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 172 | ## Use `= expr` for producing an exception if the parsed value doesn't match 173 | ## `expr`: 174 | ## 175 | ## .. code:: nim 176 | ## s: x = "BinaryLang is awesome" 177 | ## 8: y[5] = @[0, 1, 2, 3, 4] 178 | ## 179 | ## Repetition 180 | ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 181 | ## There are 3 ways to produce a `seq` of your type: 182 | ## 183 | ## - `for`: append `[expr]` to the name for repeating `expr` times 184 | ## - `until`: append `{expr}` to the name for repeating until `expr` is 185 | ## evaluated to `true` 186 | ## - `magic`: enclose name with `{}` and use assertion with your **next** field 187 | ## 188 | ## .. code:: nim 189 | ## 8: a[5] # reads 5 8-bit integers 190 | ## 8: b{_ == 103 or i > 9} # reads until it finds the value 103 or 191 | ## # completes 10th iteration 192 | ## 8: {c} # reads 8-bit integers until next field is matches 193 | ## 16: _ = 0xABCD 194 | ## u8: {d[5]} # reads byte sequences each of length 5 until next field 195 | ## # matches 196 | ## s: _ = "END" 197 | ## 198 | ## Also, the following symbols are defined implicitly: 199 | ## - `i`: current iteration index 200 | ## - `_`: last element read 201 | ## 202 | ## These can be leveraged even in other expressions than the expression for 203 | ## repetition itself; for instance you can use them to parameterize a parser: 204 | ## 205 | ## .. code:: nim 206 | ## struct(inner, size: int): 207 | ## 8: x[size] 208 | ## struct(outer): 209 | ## 32: amount 210 | ## 32: sizes[amount] 211 | ## *inner(sizes[i]): aux[amount] 212 | ## 213 | ## With the above trick you can get a sequence of variable-length sequences. 214 | ## 215 | ## Due to current limitations of the underlying bitstream implementation, to 216 | ## perform magic, your stream must be aligned and all the reads involved must 217 | ## also be aligned. This will be fixed in the future. 218 | ## 219 | ## Substreams 220 | ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 221 | ## Call syntax forces the creation of a substream: 222 | ## 223 | ## .. code:: nim 224 | ## struct(aux, size: int): 225 | ## 8: x[size] 226 | ## struct(parser): 227 | ## 8: x = 4 228 | ## 8: limit = 8 229 | ## *aux(x): fixed(limit) 230 | ## 231 | ## In the above example, `limit` bytes (8 in this case) will be read from the 232 | ## main `BitStream`. Then, a substream will be created out of them, which will 233 | ## then be used as the stream for parsing `fixed`. Since `fixed` will only use 234 | ## 4 of them, the remaining 4 will effectively be discarded. 235 | ## 236 | ## Note that unlike in the type, here size is counted in bytes. It is implied 237 | ## that you cannot create a substream if your bitstream is unaligned. 238 | ## 239 | ## This feature is **not implemented for repetition** because it would increase 240 | ## complexity with little benefits. The following syntax is **invalid** and 241 | ## instead you should use the technique with the auxiliary parser shown above: 242 | ## 243 | ## .. code:: nim 244 | ## struct(parser): 245 | ## u8: a[4](6) # does substream refer to each individual element or the 246 | ## # whole sequence? 247 | ## 248 | ## Strings 249 | ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 250 | ## Strings are special because they don't have a fixed size. Therefore, you 251 | ## must provide enough information regarding their termination. This can be 252 | ## achieved with one of the following: 253 | ## - Use of substream 254 | ## - Assertion 255 | ## - Magic 256 | ## 257 | ## .. code:: nim 258 | ## s: a # null/eos-terminated (because next field doesn't use assertion) 259 | ## s: b(5) # reads a string from a substream of 5 bytes until null/eos 260 | ## s: c = "ABC" # reads a string of length 3 that must match "ABC" 261 | ## s: d # reads a string until next field matches 262 | ## s: _ = "MAGIC" 263 | ## s: e[5] # reads 5 null-terminated strings 264 | ## s: {f} # reads null-terminated strings until next field matches 265 | ## 8: term = 0xff # terminator of the above sequence 266 | ## s: {g[5]} # sequence of 5-length sequences of null-terminated strings 267 | ## s: _ = "END_NESTED" 268 | ## 269 | ## Rules: 270 | ## - Strings are null/eos-terminated unless assertion is used on the same field 271 | ## **or** on the next field 272 | ## - When using repetition, each string element is null-terminated 273 | ## 274 | ## Extensions 275 | ## ---------------------------------------------------------------------------- 276 | ## 277 | ## Custom parser API 278 | ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 279 | ## Since a BinaryLang parser is just a `tuple[get: proc, put: proc]`, you can 280 | ## write parsers by hand that are compatible with the DSL. Just be sure that 281 | ## `get` and `put` have proper signatures, and there is a type with the same 282 | ## name as your parser but capitalized: 283 | ## 284 | ## .. code:: nim 285 | ## type Parser = SomeType 286 | ## proc get(s: BitStream): Parser 287 | ## proc put(s: BitStream, input: Parser) 288 | ## let parser = (get: get, put: put) 289 | ## 290 | ## If you want your custom parser to be parametric, simply append more 291 | ## parameters to your procs. These extra parameters must be identical and in 292 | ## the same order in the two procs: 293 | ## 294 | ## .. code:: nim 295 | ## type Parser = SomeType 296 | ## proc get(s: BitStream, x: int, y: float): Parser 297 | ## proc put(s: BitStream, input: Parser, x: int, y: float) 298 | ## let parser = (get: get, put: put) 299 | ## 300 | ## Operations 301 | ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 302 | ## Operations can be applied to fields with the following syntax: 303 | ## 304 | ## .. code:: 305 | ## type {op(arg)}: name 306 | ## 307 | ## Operations act on data after the parsing and before the encoding 308 | ## respectively. 309 | ## 310 | ## An operation is nothing more than a pair of templates which follow a 311 | ## specific pattern: 312 | ## - The names of the templates **must** follow the pattern: `get` 313 | ## and `put` 314 | ## - They must have at least 3 untyped parameters (you can name them as you 315 | ## wish): 316 | ## - **parameter #1**: parsing/encoding statements 317 | ## - **parameter #2**: variable *previously* parsed/encoded 318 | ## - **parameter #3**: output 319 | ## 320 | ## .. code:: nim 321 | ## template increaseGet(parse, parsed, output, num: untyped) = 322 | ## parse 323 | ## output = parsed + num 324 | ## template increasePut(encode, encoded, output, num: untyped) = 325 | ## output = encoded - num 326 | ## encode 327 | ## struct(myParser): 328 | ## 64: x 329 | ## 16 {increase(x)}: y 330 | ## 331 | ## You can apply more than one operations on one field, in which case they 332 | ## are chained in the specified order, and only the first operation really 333 | ## does any parsing/encoding to the stream. The rest just operate on the 334 | ## value produced by the operation directly before them. 335 | ## 336 | ## `parse` fills in the `parsed` variable. It is a seperate statement because 337 | ## it potentially operates on the stream (this happens **always and only for 338 | ## the first operation**). Similarly, `encode` passes on the value in 339 | ## `output` variable. *Passes* means the value is potentially written to the 340 | ## stream. 341 | ## 342 | ## .. code:: nim 343 | ## template condGet(parse, parsed, output, cond: untyped) = 344 | ## if cond: 345 | ## parse 346 | ## output = parsed 347 | ## template condPut(encode, encoded, output, cond: untyped) = 348 | ## if cond: 349 | ## output = encoded 350 | ## encode 351 | ## template increaseGet(parse, parsed, output, num: untyped) = 352 | ## parse 353 | ## output = parsed + num 354 | ## template increasePut(encode, encoded, output, num: untyped) = 355 | ## output = encoded - num 356 | ## encode 357 | ## struct(myParser): 358 | ## 8: shouldParse 359 | ## 64: x 360 | ## 16 {cond(shouldParse.bool), increase(x)}: y 361 | ## 362 | ## It is impossible for BinaryLang to infer the type of the altered value, 363 | ## that is, if your operation changes it. By default it is assumed that 364 | ## the new field value is of the same type as the *previous* one (for the 365 | ## first operation, this is the type produced according to the field type 366 | ## annotation). Therefore, if your operation alters the type, then you must 367 | ## provide the new type in square brackets: 368 | ## 369 | ## .. code:: nim 370 | ## template asciiNumGet(parse, parsed, output: untyped) = 371 | ## parse 372 | ## output = char(parsed - '0') 373 | ## template asciiNumPut(encode, encoded, output: untyped) = 374 | ## output = int8(encoded + '0') 375 | ## encode 376 | ## struct(myParser): 377 | ## 8 {asciiNum[char]}: x 378 | ## 379 | ## The actual type of the field changes to the type annotated in the last 380 | ## operation. if you annotate the type for *some* of the operations, then for 381 | ## the ones you did not, the type of the operation directly previous to it is 382 | ## assumed. 383 | ## 384 | ## Special notes 385 | ## ---------------------------------------------------------------------------- 386 | ## - Nim expressions may contain: 387 | ## - a previously defined field 388 | ## - a parser parameter 389 | ## - the `_` symbol for *subject* element (its meaning varies) 390 | ## - the `i` symbol for current index in a repetition 391 | ## - the `s` symbol for accessing the bitstream 392 | ## 393 | ## `i` and `s` might conflict with your variables or fields, so you should 394 | ## consider them reserved keywords and not use them for something else. 395 | 396 | import binarylang/private/[types, errors, dsldecoders] 397 | import binarylang/private/codegen/[serialization, deserialization, conversion] 398 | import macros, tables, strutils, sugar 399 | import bitstreams 400 | export bitstreams, MagicError 401 | 402 | macro struct*(name: untyped, rest: varargs[untyped]): untyped = 403 | ## Input: 404 | ## - `name`: Name of the parser tuple to create (must be lowercase) 405 | ## - `rest`: **Optional** parser options and parameters 406 | ## - `rest` (last): Block of the format described above 407 | ## 408 | ## Output: 409 | ## - Object type declaration with name 410 | ## `tname` ≡ `capitalizeAscii(name)` 411 | ## - Reader proc that returns an object of the type `tname` 412 | ## - Writer proc that accepts an object of type `tname` 413 | ## - A tuple named `name` with the fields `get` and `put` 414 | ## 415 | ## The procs are of the following form: 416 | ## 417 | ## .. code-block:: nim 418 | ## proc get(s: BitStream): `tname` 419 | ## proc put(s: BitStream, input: `tname`) 420 | result = newStmtList() 421 | if name.kind == nnkPrefix: 422 | raise newException(SyntaxError, 423 | "Did you use * to export a parser? This syntax is deprecated. " & 424 | "Parser visibility is controlled with the 'visibility' parser option.") 425 | name.expectKind(nnkIdent) 426 | var 427 | pname: NimNode 428 | pdef: NimNode 429 | tname: NimNode 430 | tdef: NimNode 431 | if name.strVal[0].isUpperAscii: 432 | syntaxError("Parser name must be lowercase") 433 | pname = name.copyNimTree 434 | pdef = name.copyNimTree 435 | tname = ident(name.strVal.capitalizeAscii) 436 | tdef = ident(name.strVal.capitalizeAscii) 437 | var 438 | fieldDefs = newTree(nnkRecList) 439 | fieldsSymbolTable = newSeq[string]() 440 | let 441 | bs = ident"s" 442 | input = ident"input" 443 | (params, parserOptions) = decodeHeader(rest[0 .. ^2]) 444 | paramsSymbolTable = collect(newSeq): 445 | for p in params: 446 | p[0].strVal 447 | var fields = collect(newSeq): 448 | for def in rest[^1]: 449 | decodeField(def, fieldsSymbolTable, parserOptions) 450 | for i in 0 ..< fields.len - 1: 451 | if fields[i].val.isMagic or 452 | (fields[i].typ.kind == kStr and fields[i+1].val.valueExpr != nil): 453 | if fields[i+1].val.valueExpr == nil: 454 | raise newException(Defect, 455 | "Magic was used without assertion at the next field") 456 | fields[i].magic = fields[i+1] 457 | var reader = generateReader(fields, fieldsSymbolTable, paramsSymbolTable) 458 | reader.insert(0, newAssignment( 459 | ident"result", 460 | newCall(tname))) 461 | let writer = generateWriter(fields, fieldsSymbolTable, paramsSymbolTable) 462 | for f in fields: 463 | let 464 | ident = f.symbol 465 | field = ident.strVal 466 | var impl: NimNode 467 | if f.ops.len > 0: 468 | impl = f.ops[^1].typ.copyNimTree 469 | else: 470 | impl = f.typ.getImpl 471 | if f.val.repeat != rNo: 472 | impl = quote do: seq[`impl`] 473 | if f.val.isMagic: 474 | impl = quote do: seq[`impl`] 475 | if field != "": 476 | fieldDefs.add( 477 | newIdentDefs( 478 | if parserOptions.visibility == pvPublic: postfix(f.symbol, "*") 479 | else: f.symbol, 480 | impl)) 481 | let typeBody = nnkObjectTy.newTree( 482 | newEmptyNode(), 483 | newEmptyNode(), 484 | fieldDefs) 485 | result.add( 486 | nnkTypeSection.newTree( 487 | nnkTypeDef.newTree( 488 | if parserOptions.visibility == pvPublic: postfix(tdef, "*") 489 | else: tdef, 490 | newEmptyNode(), 491 | if parserOptions.reference: nnkRefTy.newTree(typeBody) 492 | else: typeBody))) 493 | let 494 | readerName = genSym(nskProc) 495 | writerName = genSym(nskProc) 496 | var 497 | readerProcForwardDecl = quote do: 498 | proc `readerName`(`bs`: BitStream): `tname` 499 | writerProcForwardDecl = quote do: 500 | proc `writerName`(`bs`: BitStream, `input`: `tname`) 501 | readerProc = quote do: 502 | proc `readerName`(`bs`: BitStream): `tname` = 503 | `reader` 504 | writerProc = quote do: 505 | proc `writerName`(`bs`: BitStream, `input`: `tname`) = 506 | `writer` 507 | for p in params: 508 | readerProcForwardDecl[3].add p.copyNimTree 509 | writerProcForwardDecl[3].add p.copyNimTree 510 | readerProc[3].add p.copyNimTree 511 | writerProc[3].add p.copyNimTree 512 | let pdeffinal = 513 | if parserOptions.visibility == pvPublic: postfix(pdef, "*") 514 | else: pdef 515 | result.add(quote do: 516 | `readerProcForwardDecl` 517 | `writerProcForwardDecl` 518 | let `pdeffinal` = (get: `readerName`, put: `writerName`) 519 | `readerProc` 520 | `writerProc`) 521 | if ppConverters in parserOptions.plugins: 522 | let (procTo, procFrom) = generateConverters(tname, pname, params, 523 | parserOptions.visibility == pvPublic) 524 | result.add(quote do: 525 | `procTo` 526 | `procFrom`) 527 | 528 | when defined(BinaryLangEcho): 529 | echo repr result 530 | 531 | macro union*(name, disc: untyped; rest: varargs[untyped]): 532 | untyped = 533 | ## Input: 534 | ## - `name`: The name of the parser tuple to create (must be lowercase) 535 | ## - `disc`: The definition of the discriminator field (`name: type`) 536 | ## - `rest`: **Optional** parser options and parameters 537 | ## - `rest` (last): Block of the format described above 538 | ## 539 | ## Output: 540 | ## - **Variant** object type declaration with discriminator `disc` and name 541 | ## `tname` ≡ `capitalizeAscii(name)` 542 | ## - Reader proc that returns an object of the type `tname` 543 | ## - Writer proc that accepts an object of type `tname` 544 | ## - A tuple named `name` with the fields `get` and `put` 545 | ## 546 | ## The procs are of the following form: 547 | ## 548 | ## .. code-block:: nim 549 | ## proc get(s: BitStream): `tname` 550 | ## proc put(s: BitStream, input: `tname`) 551 | ## 552 | ## The body is similar to that of `struct` macro, but the fields are 553 | ## partitioned in branches. Each branch starts with one or more possible 554 | ## value of the discriminator in parenthesis, seperated by comma. 555 | ## 556 | ## For covering the rest of the cases use the `_` symbol (without 557 | ## parenthesis). 558 | ## 559 | ## If you don't want a field for some branch, use `nil` on the right side. 560 | ## 561 | ## Example: 562 | ## 563 | ## .. code-block:: nim 564 | ## union(fooBar, int): 565 | ## (0): *foo: a 566 | ## (1, 3): u32: *b 567 | ## (2): nil 568 | ## (4): 569 | ## u8: c 570 | ## *bar: d 571 | ## _: u32: e 572 | result = newStmtList() 573 | if name.kind == nnkPrefix: 574 | raise newException(SyntaxError, 575 | "Did you use * to export a parser? This syntax is deprecated. " & 576 | "Parser visibility is controlled with the 'visibility' parser option.") 577 | name.expectKind(nnkIdent) 578 | var 579 | pname: NimNode 580 | pdef: NimNode 581 | tname: NimNode 582 | tdef: NimNode 583 | if name.strVal[0].isUpperAscii: 584 | syntaxError("Parser name must be lowercase") 585 | pname = name.copyNimTree 586 | pdef = name.copyNimTree 587 | tname = ident(name.strVal.capitalizeAscii) 588 | tdef = ident(name.strVal.capitalizeAscii) 589 | let 590 | input = ident"input" 591 | bs = ident"s" 592 | (extraParams, parserOptions) = decodeHeader(rest[0 .. ^2]) 593 | discName = ident"disc" 594 | var 595 | discType: NimNode 596 | objectMeat = newTree(nnkRecCase) 597 | case disc.kind 598 | of nnkIdent: 599 | discType = disc.copyNimTree 600 | objectMeat.add( 601 | newIdentDefs( 602 | postfix( 603 | discName, 604 | "*"), 605 | discType)) 606 | of nnkPrefix: 607 | raise newException(SyntaxError, 608 | "Did you use * to export a the discriminator? This syntax is deprecated. " & 609 | "Discriminator visibility is controlled with the 'visibility' parser option.") 610 | of nnkPragmaExpr: 611 | disc[0].expectKind(nnkIdent) 612 | disc[1].expectKind(nnkPragma) 613 | disc[1].expectLen(1) 614 | disc[1][0].expectKind(nnkIdent) 615 | assert disc[1][0].strVal == "private" 616 | discType = disc[0].copyNimTree 617 | objectMeat.add( 618 | newIdentDefs( 619 | discName, 620 | discType)) 621 | else: 622 | syntaxError() 623 | let 624 | params = newIdentDefs(discName, discType) & extraParams 625 | paramsSymbolTable = collect(newSeq): 626 | for p in params: 627 | p[0].strVal 628 | var 629 | variations = collect(newSeq): 630 | for def in rest[^1]: 631 | decodeVariation(def, paramsSymbolTable, parserOptions) 632 | for v in variations: 633 | if v.isEmpty: 634 | continue 635 | for i in 0 ..< v.fields.len - 1: 636 | if v.fields[i].val.isMagic or 637 | (v.fields[i].typ.kind == kStr and v.fields[i+1].val.valueExpr != nil): 638 | if v.fields[i+1].val.valueExpr == nil: 639 | raise newException(Defect, 640 | "Magic was used without assertion at the next field") 641 | v.fields[i].magic = v.fields[i+1] 642 | for v in variations: 643 | let left = 644 | if v.isEmpty: 645 | newNilLit() 646 | else: 647 | var rl = newTree(nnkRecList) 648 | for f in v.fields: 649 | if f.val.name != "": 650 | var impl = newTree(nnkNone) 651 | if f.ops.len > 0: 652 | for i in countdown(f.ops.len-1, 0): 653 | if f.ops[^1].typ.kind != nnkNone: 654 | impl = f.ops[^1].typ.copyNimTree 655 | break 656 | if impl.kind == nnkNone: 657 | impl = f.typ.getImpl 658 | else: 659 | impl = f.typ.getImpl 660 | if f.val.repeat != rNo: 661 | impl = quote do: seq[`impl`] 662 | if f.val.isMagic: 663 | impl = quote do: seq[`impl`] 664 | rl.add( 665 | newIdentDefs( 666 | if parserOptions.visibility == pvPublic: postfix(f.symbol, "*") 667 | else: f.symbol, 668 | impl)) 669 | rl 670 | if v.isElseBranch: 671 | objectMeat.add( 672 | nnkElse.newTree(left)) 673 | else: 674 | var branch = newTree(nnkOfBranch) 675 | branch.add(v.cases) 676 | branch.add(left) 677 | objectMeat.add(branch) 678 | let typeBody = nnkObjectTy.newTree( 679 | newEmptyNode(), 680 | newEmptyNode(), 681 | nnkRecList.newTree( 682 | objectMeat)) 683 | result.add( 684 | nnkTypeSection.newTree( 685 | nnkTypeDef.newTree( 686 | if parserOptions.visibility == pvPublic: postfix(tdef, "*") 687 | else: tdef, 688 | newEmptyNode(), 689 | if parserOptions.reference: nnkRefTy.newTree(typeBody) 690 | else: typeBody))) 691 | let readerName = genSym(nskProc) 692 | var getCaseStmt = nnkCaseStmt.newTree(discName) 693 | let readerProcForwardDecl = quote do: 694 | proc `readerName`(`bs`: BitStream): `tname` 695 | for v in variations: 696 | let inner = 697 | if v.isEmpty: 698 | nnkDiscardStmt.newTree(newEmptyNode()) 699 | else: 700 | generateReader(v.fields, v.st, paramsSymbolTable) 701 | if v.isElseBranch: 702 | getCaseStmt.add(nnkElse.newTree(inner)) 703 | else: 704 | var branch = newTree(nnkOfBranch) 705 | for b in v.cases: 706 | branch.add(b) 707 | branch.add(inner) 708 | getCaseStmt.add(branch) 709 | let reader = newStmtList( 710 | newAssignment( 711 | ident"result", 712 | nnkObjConstr.newTree( 713 | tname, 714 | newColonExpr( 715 | discName, 716 | discName))), 717 | getCaseStmt) 718 | var readerProc = quote do: 719 | proc `readerName`(`bs`: BitStream): `tname` = 720 | `reader` 721 | let writerName = genSym(nskProc) 722 | var writerProcForwardDecl = quote do: 723 | proc `writerName`(`bs`: BitStream, `input`: `tname`) 724 | var writer = nnkCaseStmt.newTree(discName) 725 | for v in variations: 726 | let inner = 727 | if v.isEmpty: 728 | nnkDiscardStmt.newTree(newEmptyNode()) 729 | else: 730 | generateWriter(v.fields, v.st, paramsSymbolTable) 731 | if v.isElseBranch: 732 | writer.add(nnkElse.newTree(inner)) 733 | else: 734 | var branch = newTree(nnkOfBranch) 735 | for b in v.cases: 736 | branch.add(b) 737 | branch.add(inner) 738 | writer.add(branch) 739 | var writerProc = quote do: 740 | proc `writerName`(`bs`: BitStream, `input`: `tname`) = 741 | `writer` 742 | for p in params: 743 | readerProcForwardDecl[3].add p.copyNimTree 744 | writerProcForwardDecl[3].add p.copyNimTree 745 | readerProc[3].add p.copyNimTree 746 | writerProc[3].add p.copyNimTree 747 | let pdeffinal = 748 | if parserOptions.visibility == pvPublic: postfix(pdef, "*") 749 | else: pdef 750 | result.add(quote do: 751 | `readerProcForwardDecl` 752 | `writerProcForwardDecl` 753 | let `pdeffinal` = (get: `readerName`, put: `writerName`) 754 | `readerProc` 755 | `writerProc`) 756 | if ppConverters in parserOptions.plugins: 757 | let (procTo, procFrom) = generateConverters(tname, pname, params, 758 | parserOptions.visibility == pvPublic) 759 | result.add(quote do: 760 | `procTo` 761 | `procFrom`) 762 | when defined(BinaryLangEcho): 763 | echo repr result 764 | -------------------------------------------------------------------------------- /binarylang.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | version = "0.8.1" 3 | author = "Stefanos Mandalas" 4 | description = "Binary parser/encoder DSL" 5 | license = "MIT" 6 | 7 | # Dependencies 8 | requires "nim >= 1.6.10" 9 | requires "bitstreams" -------------------------------------------------------------------------------- /binarylang/operations.nim: -------------------------------------------------------------------------------- 1 | template condGet*(parse, parsed, output, cond: untyped) = 2 | if cond: 3 | parse 4 | output = parsed 5 | template condPut*(encode, encoded, output, cond: untyped) = 6 | if cond: 7 | output = encoded 8 | encode 9 | 10 | template validGet*(parse, parsed, output, cond: untyped) = 11 | parse 12 | assert cond 13 | output = parsed 14 | template validPut*(encode, encoded, output, cond: untyped) = 15 | output = encoded 16 | assert cond 17 | encode 18 | 19 | template posGet*(parse, parsed, output, pos: untyped) = 20 | let save = getPosition(s) 21 | s.setPosition(pos) 22 | parse 23 | s.setPosition(save) 24 | output = parsed 25 | template posPut*(encode, encoded, output, pos: untyped) = 26 | output = encoded 27 | let save = getPosition(s) 28 | s.setPosition(pos) 29 | encode 30 | s.setPosition(save) 31 | 32 | template condPosGet*(parse, parsed, output, condAndPos: untyped) = 33 | let 34 | (cond, pos) = condAndPos 35 | save = getPosition(s) 36 | if cond: 37 | s.setPosition(pos) 38 | parse 39 | s.setPosition(save) 40 | output = parsed 41 | template condPosPut*(encode, encoded, output, condAndPos: untyped) = 42 | output = encoded 43 | let 44 | (cond, pos) = condAndPos 45 | save = getPosition(s) 46 | if cond: 47 | s.setPosition(pos) 48 | encode 49 | s.setPosition(save) 50 | 51 | ## Required imports: sequtils 52 | template toStrGet*(parse, parsed, output: untyped) = 53 | parse 54 | output = parsed.mapIt(it.char).join 55 | template toStrPut*(encode, encoded, output: untyped) = 56 | output = newSeq[byte](encoded.len) 57 | for i in 0 ..< encoded.len: 58 | output[i] = byte(encoded[i]) 59 | encode -------------------------------------------------------------------------------- /binarylang/private/codegen/astutil.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | 3 | proc prefixFields*(node: var NimNode, st, params: seq[string]; 4 | with: NimNode) {.compileTime.} = 5 | if node.kind == nnkIdent: 6 | if node.strVal in st and node.strVal notin params: 7 | node = newDotExpr(with, node) 8 | elif node.kind == nnkDotExpr: 9 | var 10 | n0 = node[0] 11 | n1 = node[1] 12 | prefixFields(n0, st, params, with) 13 | if n1.kind != nnkIdent: 14 | prefixFields(n1, st, params, with) 15 | node = newDotExpr(n0, n1) 16 | else: 17 | var i = 0 18 | while i < len(node): 19 | var n = node[i] 20 | prefixFields(n, st, params, with) 21 | node[i] = n.copyNimTree 22 | inc i 23 | 24 | proc replaceWith*(node: var NimNode; what, with: NimNode) {.compileTime.} = 25 | if node.kind == nnkIdent: 26 | if eqIdent(node, what): 27 | node = with.copyNimTree 28 | else: 29 | var i = 0 30 | while i < len(node): 31 | var n = node[i] 32 | n.replaceWith(what, with) 33 | node[i] = n 34 | inc i -------------------------------------------------------------------------------- /binarylang/private/codegen/conversion.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | 3 | proc generateConverters*(tname, pname: NimNode; params: seq[NimNode]; 4 | isExported: bool): tuple[to, `from`: NimNode] 5 | {.compileTime.} = 6 | var 7 | toConv = ident("to" & tname.strVal) 8 | fromConv = ident("from" & tname.strVal) 9 | procToParams = @[tname, newIdentDefs(ident"x", ident"string")] 10 | procToBody = newCall( 11 | newDotExpr( 12 | pname, 13 | ident"get"), 14 | newCall( 15 | ident"newStringBitStream", 16 | ident"x")) 17 | procFromParams = @[ident"string", newIdentDefs(ident"x", tname)] 18 | putCall = newCall( 19 | newDotExpr( 20 | pname, 21 | ident"put"), 22 | ident"s", 23 | ident"x") 24 | if isExported: 25 | toConv = postfix(toConv, "*") 26 | fromConv = postfix(fromConv, "*") 27 | for p in params: 28 | procToParams.add(p.copyNimTree) 29 | procToBody.add(p[0].copyNimTree) 30 | procFromParams.add(p.copyNimTree) 31 | putCall.add(p[0].copyNimTree) 32 | result = ( 33 | newProc( 34 | toConv, 35 | procToParams, 36 | procToBody), 37 | newProc( 38 | fromConv, 39 | procFromParams, 40 | newStmtList( 41 | (quote do: 42 | let s {.inject.} = newStringBitStream()), 43 | putCall, 44 | (quote do: s.seek(0)), 45 | (quote do: s.readAll)))) -------------------------------------------------------------------------------- /binarylang/private/codegen/deserialization.nim: -------------------------------------------------------------------------------- 1 | import astutil 2 | import ../types 3 | from ../dsldecoders import getImpl 4 | import macros 5 | 6 | proc getCustomReader(typ: Type; bs: NimNode; st, params: seq[string]): 7 | NimNode {.compileTime.} = 8 | result = newCall(nnkDotExpr.newTree(typ.symbol, ident"get"), bs) 9 | for arg in typ.args: 10 | result.add(arg.copyNimTree) 11 | result.prefixFields(st, params, ident"result") 12 | 13 | proc createReadStatement(sym, bs: NimNode; f: Field; st, params: seq[string]): 14 | NimNode {.compileTime.} = 15 | result = newStmtList() 16 | let 17 | kind = f.typ.kind 18 | impl = f.typ.getImpl 19 | endian = if f.typ.endian == littleEndian: 'l' else: 'b' 20 | endianNode = newLit(f.typ.endian) 21 | bitEndian = if f.typ.bitEndian == littleEndian: 'l' else: 'b' 22 | procUnaligned = ident("readbits" & bitEndian & "e") 23 | case kind 24 | of kInt, kUInt: 25 | let 26 | size = f.typ.size 27 | sizeNode = newLit(size.int) 28 | procUnalignedCall = quote do: `procUnaligned`(`bs`, `sizeNode`, `endianNode`) 29 | if size in {8, 16, 32, 64}: 30 | let numKind = if kind == kInt: 's' else: 'u' 31 | var procAlignedStr = "read" & numKind & $size 32 | if size != 8: procAlignedStr &= endian & "e" 33 | let procAligned = ident(procAlignedStr) 34 | result.add(quote do: 35 | if isAligned(`bs`): 36 | `sym` = `procAligned`(`bs`) 37 | else: 38 | `sym` = `impl`(`procUnalignedCall`)) 39 | else: 40 | result.add(quote do: 41 | if isAligned(`bs`): 42 | resetBuffer(`bs`) 43 | `sym` = `impl`(`procUnalignedCall`)) 44 | of kFloat: 45 | let 46 | size = f.typ.size 47 | sizeNode = newLit(size.int) 48 | procAligned = ident("readf" & $size & endian & "e") 49 | procUnalignedCall = quote do: `procUnaligned`(`bs`, `sizeNode`, `endianNode`) 50 | floatCast = 51 | if size == 64: quote do: cast[float64](`procUnalignedCall`) 52 | else: quote do: float32(cast[float64](`procUnalignedCall`)) 53 | result.add(quote do: 54 | if isAligned(`bs`): 55 | `sym` = `procAligned`(`bs`) 56 | else: 57 | `sym` = `floatCast`) 58 | of kStr: 59 | let expr = f.val.valueExpr 60 | result.add(quote do: 61 | if not isAligned(`bs`): 62 | raise newException(IOError, "Stream must be aligned to read a string")) 63 | result.add( 64 | if expr != nil: 65 | quote do: 66 | `sym` = readStr(`bs`, 8 * `expr`.len) 67 | else: 68 | quote do: 69 | `sym` = readStr(`bs`)) 70 | of kProduct, kSum: 71 | let call = getCustomReader(f.typ, bs, st, params) 72 | result.add(quote do: `sym` = `call`) 73 | 74 | proc createReadField(sym: NimNode; f: Field; bs: NimNode; 75 | st, params: seq[string]): NimNode {.compileTime.} = 76 | result = newStmtList() 77 | let 78 | res = ident"result" 79 | impl = f.typ.getImpl 80 | var value = f.val.valueExpr 81 | value.prefixFields(st, params, res) 82 | if f.val.repeat == rNo: 83 | result.add createReadStatement(sym, bs, f, st, params) 84 | else: 85 | var expr = f.val.repeatExpr.copyNimTree 86 | expr.prefixFields(st, params, res) 87 | case f.val.repeat 88 | of rFor: 89 | let 90 | loopIdx = ident"i" 91 | tmp = quote do: `sym`[`loopIdx`] 92 | let readStmt = createReadStatement(tmp, bs, f, st, params) 93 | result.add(quote do: 94 | `sym` = newSeq[`impl`](`expr`) 95 | for `loopIdx` in 0 ..< int(`expr`): 96 | `readStmt`) 97 | of rUntil: 98 | let 99 | tmp = genSym(nskVar) 100 | loopIdx = ident"i" 101 | expr.replaceWith(ident"_", tmp) 102 | expr.replaceWith(ident"s", bs) 103 | let readStmt = createReadStatement(tmp, bs, f, st, params) 104 | result.add (quote do: 105 | block: 106 | `sym` = newSeq[`impl`]() 107 | var 108 | `loopIdx`: int 109 | `tmp`: `impl` 110 | while true: 111 | `readStmt` 112 | `sym`.add(`tmp`) 113 | inc `loopIdx` 114 | if `expr`: 115 | break) 116 | else: discard 117 | 118 | proc generateRead*(sym: NimNode; f: Field; bs: NimNode; 119 | st, params: seq[string]): NimNode {.compileTime.} = 120 | result = newStmtList() 121 | let 122 | res = ident"result" 123 | field = f.val.name 124 | isSingleStr = f.typ.kind == kStr and f.val.repeat == rNo and not f.val.isMagic 125 | var impl = f.typ.getImpl 126 | if f.val.repeat != rNo: 127 | impl = quote do: seq[`impl`] 128 | if f.val.isMagic: 129 | impl = quote do: seq[`impl`] 130 | var value = f.val.valueExpr.copyNimTree 131 | value.prefixFields(st, params, res) 132 | if f.val.sizeExpr != nil: 133 | var size = f.val.sizeExpr.copyNimTree 134 | size.prefixFields(st, params, res) 135 | let 136 | ss = genSym(nskVar) 137 | rf = createReadField(sym, f, ss, st, params) 138 | result.add(quote do: 139 | var 140 | `ss` = createSubstream(`bs`, int(`size`)) 141 | `rf`) 142 | if value != nil: 143 | result.add(quote do: 144 | if `sym` != (`value`): 145 | raise newException(MagicError, "field '" & $`field` & "' was " & 146 | $`sym` & " instead of " & $`value`)) 147 | elif isSingleStr and f.magic != nil: 148 | let 149 | tmp = genSym(nskVar) 150 | pos = genSym(nskLet) 151 | rf = createReadField(tmp, f.magic, bs, st, params) 152 | var 153 | tmpImpl = f.magic.typ.getImpl 154 | magicVal = f.magic.val.valueExpr 155 | magicVal.prefixFields(st, params, res) 156 | if f.magic.val.repeat != rNo: 157 | tmpImpl = quote do: seq[`tmpImpl`] 158 | result.add(quote do: 159 | var 160 | `tmp`: `tmpImpl` 161 | while true: 162 | let `pos` = getPosition(`bs`) 163 | `rf` 164 | `bs`.seek(`pos`) 165 | if `tmp` == `magicVal`: 166 | break 167 | `sym`.add(readU8(`bs`).char)) 168 | elif f.val.isMagic: 169 | let 170 | elem = genSym(nskVar) 171 | magic = genSym(nskVar) 172 | pos = genSym(nskLet) 173 | readElem = createReadField(elem, f, bs, st, params) 174 | readMagic = createReadField(magic, f.magic, bs, st, params) 175 | var elemImpl = f.typ.getImpl 176 | if f.val.repeat != rNo: 177 | elemImpl = quote do: seq[`elemImpl`] 178 | var magicImpl = f.magic.typ.getImpl 179 | if f.magic.val.repeat != rNo: 180 | magicImpl = quote do: seq[`magicImpl`] 181 | var magicVal = f.magic.val.valueExpr 182 | magicVal.prefixFields(st, params, res) 183 | result.add(quote do: 184 | var 185 | `elem`: `elemImpl` 186 | `magic`: `magicImpl` 187 | while true: 188 | let `pos` = getPosition(`bs`) 189 | `readMagic` 190 | `bs`.seek(`pos`) 191 | if `magic` == `magicVal`: 192 | break 193 | `readElem` 194 | `sym`.add(`elem`)) 195 | else: 196 | let 197 | rf = createReadField(sym, f, bs, st, params) 198 | result.add(quote do: 199 | `rf`) 200 | if value != nil: 201 | result.add(quote do: 202 | if `sym` != (`value`): 203 | raise newException(MagicError, "field '" & $`field` & "' was " & 204 | $`sym` & " instead of " & $`value`)) 205 | 206 | proc generateReader*(fields: seq[Field]; fst, pst: seq[string]): 207 | NimNode {.compileTime.} = 208 | let 209 | bs = ident"s" 210 | res = ident"result" 211 | result = newStmtList() 212 | for f in fields: 213 | var rSym = genSym(nskVar) 214 | let 215 | ident = f.symbol.copyNimTree 216 | field = ident.strVal 217 | var impl = f.typ.getImpl 218 | if f.val.repeat != rNo: 219 | impl = quote do: seq[`impl`] 220 | if f.val.isMagic: 221 | impl = quote do: seq[`impl`] 222 | result.add(quote do: 223 | var `rSym`: `impl`) 224 | var 225 | read = generateRead(rSym, f, bs, fst, pst) 226 | outputSym, parsed: NimNode 227 | if f.ops.len > 0: 228 | # Infer potentially missing types for operations 229 | for i in 0 ..< f.ops.len: 230 | if f.ops[i].typ.kind == nnkNone: 231 | if i == 0: 232 | f.ops[i].typ = impl.copyNimTree 233 | else: 234 | f.ops[i].typ = f.ops[i-1].typ.copyNimTree 235 | outputSym = genSym(nskVar) 236 | parsed = genSym(nskVar) 237 | for i in 0 ..< f.ops.len: 238 | var typeImpl = f.ops[i].typ 239 | result.add(quote do: 240 | var `outputSym`, `parsed`: `typeImpl`) 241 | var op = newCall(ident(f.ops[i].name & "get"), read, rSym, outputSym) 242 | for arg in f.ops[i].args: 243 | var argVal = arg.copyNimTree 244 | argVal.prefixFields(fst, pst, res) 245 | argVal.replaceWith(ident"_", rSym) 246 | op.add(argVal) 247 | result.add(op) 248 | rSym = outputSym 249 | outputSym = genSym(nskVar) 250 | parsed = genSym(nskVar) 251 | read = quote do: `parsed` = `outputSym` 252 | else: 253 | result.add(read) 254 | if field != "": 255 | result.add(quote do: 256 | result.`ident` = `rSym`) -------------------------------------------------------------------------------- /binarylang/private/codegen/serialization.nim: -------------------------------------------------------------------------------- 1 | import astutil 2 | import ../types 3 | from ../dsldecoders import getImpl 4 | import macros 5 | 6 | proc getCustomWriter(typ: Type; sym, bs: NimNode; st, params: seq[string]): 7 | NimNode {.compileTime.} = 8 | result = newCall(nnkDotExpr.newTree(typ.symbol, ident"put"), bs) 9 | case typ.kind 10 | of kProduct: 11 | for arg in typ.args: 12 | result.add(arg.copyNimTree) 13 | result.prefixFields(st, params, ident"input") 14 | of kSum: 15 | result.add(quote do: `sym`.disc) 16 | for i in 1 ..< typ.args.len: 17 | result.add(typ.args[i].copyNimTree) 18 | result.prefixFields(st, params, ident"input") 19 | else: discard 20 | 21 | proc createWriteStatement(f: Field, sym, bs: NimNode; st, params: seq[string]): 22 | NimNode {.compileTime.} = 23 | result = newStmtList() 24 | let 25 | kind = f.typ.kind 26 | impl = f.typ.getImpl 27 | endian = if f.typ.endian == littleEndian: 'l' else: 'b' 28 | endianNode = newLit(f.typ.endian) 29 | bitEndian = if f.typ.bitEndian == littleEndian: 'l' else: 'b' 30 | case kind 31 | of kInt, kUInt, kFloat: 32 | let 33 | size = f.typ.size 34 | sizeNode = newLit(size.int) 35 | procUnaligned = ident("writebits" & bitEndian & "e") 36 | if sym == nil: 37 | result.add(quote do: 38 | `procUnaligned`(`bs`, `sizeNode`, 0)) 39 | else: 40 | if size in {8, 16, 32, 64}: 41 | let procAligned = ident("write" & endian & "e") 42 | result.add(quote do: 43 | if isAligned(`bs`): 44 | `procAligned`(`bs`, `impl`(`sym`)) 45 | else: 46 | `procUnaligned`(`bs`, `sizeNode`, `sym`, `endianNode`)) 47 | else: 48 | result.add(quote do: `procUnaligned`(`bs`, `sizeNode`, `sym`, `endianNode`)) 49 | of kStr: 50 | result.add(quote do: 51 | if not isAligned(`bs`): 52 | raise newException(IOError, "Stream must be aligned to write a string")) 53 | if sym != nil: 54 | result.add(quote do: 55 | writeStr(`bs`, `sym`)) 56 | if f.val.valueExpr == nil and (f.magic == nil or f.val.isMagic): 57 | result.add(quote do: 58 | writeBe(`bs`, 0'u8)) 59 | of kProduct, kSum: 60 | let call = getCustomWriter(f.typ, sym, bs, st, params) 61 | call.insert(2, sym) 62 | result.add(quote do: `call`) 63 | 64 | proc createWriteField(sym: NimNode; f: Field; bs: NimNode; 65 | st, params: seq[string]): NimNode {.compileTime.} = 66 | result = newStmtList() 67 | let 68 | ident = f.symbol 69 | input = ident"input" 70 | elem = if f.val.isMagic: genSym(nskForVar) 71 | else: sym 72 | var value = f.val.valueExpr 73 | value.prefixFields(st, params, input) 74 | var writeStmts = newStmtList() 75 | case f.val.repeat 76 | of rNo: 77 | writeStmts.add createWriteStatement(f, elem, bs, st, params) 78 | of rFor: 79 | var expr = f.val.repeatExpr.copyNimTree 80 | expr.prefixFields(st, params, input) 81 | let 82 | loopIdx = ident"i" 83 | loopElem = genSym(nskForVar) 84 | writeStmt = createWriteStatement(f, loopElem, bs, st, params) 85 | writeStmts.add(quote do: 86 | for `loopIdx`, `loopElem` in `elem`: 87 | `writeStmt`) 88 | of rUntil: 89 | var expr = f.val.repeatExpr.copyNimTree 90 | expr.prefixFields(st, params, input) 91 | let 92 | forSym = genSym(nskForVar) 93 | loopIdx = ident"i" 94 | expr.replaceWith(ident"_", elem) 95 | expr.replaceWith(ident"s", bs) 96 | let writeStmt = createWriteStatement(f, forSym, bs, st, params) 97 | writeStmts.add(quote do: 98 | for `loopIdx`, `forSym` in `elem`: 99 | `writeStmt`) 100 | if f.val.isMagic: 101 | result.add(quote do: 102 | for `elem` in `input`.`ident`: 103 | `writeStmts`) 104 | else: 105 | result.add(writeStmts) 106 | 107 | proc generateWrite(sym: NimNode; f: Field; bs: NimNode; 108 | st, params: seq[string]): NimNode {.compileTime.} = 109 | result = newStmtList() 110 | let input = ident"input" 111 | if f.val.sizeExpr != nil: 112 | var size = f.val.sizeExpr.copyNimTree 113 | size.prefixFields(st, params, input) 114 | let 115 | ss = genSym(nskVar) 116 | wf = createWriteField(sym, f, ss, st, params) 117 | result.add(quote do: 118 | var `ss` = newPaddedBitStream(int(`size`)) 119 | `wf` 120 | `ss`.seek(0) 121 | `bs`.writeFromSubstream(`ss`, int(`size`))) 122 | else: 123 | result.add createWriteField(sym, f, bs, st, params) 124 | 125 | proc generateWriter*(fields: seq[Field]; fst, pst: seq[string]): 126 | NimNode {.compileTime.} = 127 | result = newStmtList() 128 | let 129 | bs = ident"s" 130 | input = ident"input" 131 | for f in fields: 132 | var wSym = genSym(nskVar) 133 | let 134 | ident = f.symbol.copyNimTree 135 | field = ident.strVal 136 | var impl = f.typ.getImpl 137 | if f.val.repeat != rNo: 138 | impl = quote do: seq[`impl`] 139 | if f.val.isMagic: 140 | impl = quote do: seq[`impl`] 141 | let value = 142 | if field == "": 143 | if f.val.valueExpr == nil: nil 144 | else: f.val.valueExpr.copyNimTree 145 | else: quote do: `input`.`ident` 146 | result.add( 147 | if value == nil: 148 | quote do: 149 | var `wSym`: `impl` 150 | else: 151 | quote do: 152 | var `wSym` = `value`) 153 | if f.ops.len > 0: 154 | # Infer potentially missing types for operations 155 | for i in 0 ..< f.ops.len: 156 | if f.ops[i].typ.kind == nnkNone: 157 | if i == 0: 158 | f.ops[i].typ = impl.copyNimTree 159 | else: 160 | f.ops[i].typ = f.ops[i-1].typ.copyNimTree 161 | var 162 | encoded = genSym(nskVar) 163 | outputSym = genSym(nskVar) 164 | # Loop through all operations backwards except the first one 165 | for i in countdown(f.ops.len - 1, 1): 166 | var 167 | typeImpl = f.ops[i-1].typ 168 | write = quote do: `outputSym` = `encoded` 169 | op = newCall(ident(f.ops[i].name & "put"), write, wSym, encoded) 170 | result.add(quote do: 171 | var `encoded`, `outputSym`: `typeImpl`) 172 | for arg in f.ops[i].args: 173 | var argVal = arg.copyNimTree 174 | argVal.prefixFields(fst, pst, input) 175 | argVal.replaceWith(ident"_", wSym) 176 | op.add(argVal) 177 | result.add(op) 178 | wSym = encoded 179 | encoded = genSym(nskVar) 180 | outputSym = genSym(nskVar) 181 | result.add(quote do: 182 | var `encoded`,`outputSym`: `impl`) 183 | var op = 184 | newCall( 185 | ident(f.ops[0].name & "put"), 186 | generateWrite(encoded, f, bs, fst, pst), 187 | wSym, 188 | encoded) 189 | for arg in f.ops[0].args: 190 | var argVal = arg.copyNimTree 191 | argVal.prefixFields(fst, pst, input) 192 | argVal.replaceWith(ident"_", wSym) 193 | op.add(argVal) 194 | result.add(op) 195 | else: 196 | result.add( 197 | generateWrite(wSym, f, bs, fst, pst)) -------------------------------------------------------------------------------- /binarylang/private/dsldecoders.nim: -------------------------------------------------------------------------------- 1 | import types, errors 2 | import macros, strutils, strformat 3 | 4 | proc getImpl*(typ: Type): NimNode {.compileTime.} = 5 | case typ.kind 6 | of kInt, kUInt: 7 | var s = "" 8 | if typ.kind == kUInt: 9 | s &= "u" 10 | s &= "int" 11 | if typ.size == 0: discard 12 | elif typ.size > 32: s &= "64" 13 | elif typ.size > 16: s &= "32" 14 | elif typ.size > 8: s &= "16" 15 | else: s &= "8" 16 | result = ident(s) 17 | of kFloat: 18 | result = ident("float" & $typ.size) 19 | of kStr: 20 | result = ident"string" 21 | of kProduct, kSum: 22 | let sym = ident(typ.symbol.strVal.capitalizeAscii) 23 | result = quote do: `sym` 24 | 25 | proc decodeType*(t: NimNode, opts: ParserOptions, prefix: string): Type 26 | {.compileTime.} = 27 | var t = t 28 | result = Type() 29 | var 30 | endian = opts.endian 31 | bitEndian = opts.bitEndian 32 | case t.kind 33 | of nnkIntLit: 34 | result.kind = kInt 35 | result.size = t.intVal 36 | if result.size > 64: 37 | raise newException(Defect, "Unable to parse values larger than 64 bits") 38 | of nnkIdent: 39 | var 40 | kind = kInt 41 | letters: set[char] 42 | size: int 43 | for i, c in t.strVal: 44 | case c 45 | of 'u', 'f', 's': 46 | if letters * {'u', 'f', 's'} != {}: 47 | raise newException(Defect, "Type was specified more than once") 48 | if c == 'u': 49 | kind = kUInt 50 | elif c == 'f': 51 | kind = kFloat 52 | elif c == 's': 53 | kind = kStr 54 | of 'l', 'b': 55 | if letters * {'l', 'b'} != {}: 56 | raise newException(Defect, "Endianness was specified more than once") 57 | if c == 'b': 58 | endian = bigEndian 59 | else: 60 | endian = littleEndian 61 | of 'n', 'r': 62 | if letters * {'n', 'r'} != {}: 63 | raise newException(Defect, 64 | "Bit endianness was specified more than once") 65 | if c == 'n': 66 | bitEndian = bigEndian 67 | else: 68 | bitEndian = littleEndian 69 | else: 70 | try: size = t.strVal[i..^1].parseInt 71 | except ValueError: 72 | raise newException(Defect, &"Format {t.strVal} not supported") 73 | break 74 | letters.incl c 75 | result.kind = kind 76 | result.size = size 77 | if letters * {'l', 'b'} != {} and 's' in letters: 78 | raise newException(Defect, "Endianness for strings is not supported") 79 | if size > 64: 80 | raise newException(Defect, "Unable to parse values larger than 64 bits") 81 | if kind in {kInt, kUInt, kFloat} and size == 0: 82 | raise newException(Defect, "Unable to parse values with size 0") 83 | if kind == kFloat and size != 32 and size != 64: 84 | raise newException(Defect, "Only 32 and 64 bit floats are supported") 85 | if kind == kStr and size mod 8 != 0: 86 | raise newException(Defect, "Unaligned strings are not supported") 87 | if letters * {'l', 'b'} != {} and (size == 8 or size mod 8 != 0): 88 | raise newException(Defect, "l/b is only valid for multiple-of-8 sizes") 89 | of nnkCall: 90 | case prefix 91 | of "*": result.kind = kProduct 92 | of "+": result.kind = kSum 93 | else: syntaxError("Invalid prefix symbol for type. Valid are: '*', '+'") 94 | if t[0].kind == nnkCall: 95 | t = t[0] 96 | result.symbol = t[0] 97 | var i = 1 98 | while i < t.len: 99 | result.args.add(t[i].copyNimTree) 100 | inc i 101 | else: 102 | syntaxError("Invalid type") 103 | result.endian = endian 104 | result.bitEndian = bitEndian 105 | 106 | proc decodeOps*(node: NimNode): Operations {.compileTime.} = 107 | for child in node: 108 | var 109 | name: string 110 | typ = newTree(nnkNone) 111 | args: seq[NimNode] 112 | case child.kind 113 | of nnkIdent: 114 | name = child.strVal 115 | of nnkBracketExpr: 116 | name = child[0].strVal 117 | typ = child[1].copyNimTree 118 | of nnkCall: 119 | case child[0].kind 120 | of nnkIdent: 121 | name = child[0].strVal 122 | of nnkBracketExpr: 123 | name = child[0][0].strVal 124 | typ = child[0][1].copyNimTree 125 | else: 126 | syntaxError("Invalid syntax for operation") 127 | for i in 1 ..< child.len: 128 | args.add(child[i].copyNimTree) 129 | else: 130 | syntaxError("Invalid syntax for operation") 131 | result.add (name, typ, args) 132 | 133 | proc decodeValue*(node: NimNode, st: var seq[string]): Value {.compileTime.} = 134 | var node = node 135 | result = Value(isExported: true) 136 | while node.kind != nnkIdent: 137 | case node.kind 138 | of nnkAsgn: 139 | result.valueExpr = node[1] 140 | node = node[0] 141 | of nnkCurly: 142 | if result.valueExpr != nil: 143 | raise newException(Defect, 144 | "Magic and assertion can't be used together in the same field") 145 | result.isMagic = true 146 | node = node[0] 147 | of nnkBracketExpr: 148 | result.repeat = rFor 149 | result.repeatExpr = node[1] 150 | node = node[0] 151 | of nnkCurlyExpr: 152 | result.repeat = rUntil 153 | result.repeatExpr = node[1] 154 | node = node[0] 155 | of nnkCall: 156 | result.sizeExpr = node[1] 157 | node = node[0] 158 | of nnkPrefix: 159 | raise newException(SyntaxError, 160 | "Did you use * to export a field? This syntax is deprecated. " & 161 | "Fields are exported by default. To make them private use {.private.}") 162 | of nnkPragmaExpr: 163 | node[1].expectKind(nnkPragma) 164 | node[1].expectLen(1) 165 | assert node[1][0].strVal == "private" 166 | result.isExported = false 167 | node = node[0] 168 | else: 169 | raise newException(SyntaxError, &"Invalid syntax for field value {node.kind}") 170 | if node.strVal != "_": 171 | result.name = node.strVal 172 | st.add(result.name) 173 | 174 | const defaultOptions: ParserOptions = ( 175 | endian: bigEndian, 176 | bitEndian: bigEndian, 177 | reference: false, 178 | plugins: {}, 179 | visibility: pvPublic) 180 | 181 | proc decodeHeader*(input: seq[NimNode]): 182 | tuple[params: seq[NimNode], opts: ParserOptions] {.compileTime.} = 183 | result.opts = defaultOptions 184 | var specifiedOpts: set[ParserOption] 185 | for n in input: 186 | case n.kind 187 | of nnkExprColonExpr: 188 | result.params.add(newIdentDefs(n[0], n[1])) 189 | of nnkExprEqExpr: 190 | case n[0].strVal 191 | of "endian": 192 | if poEndian in specifiedOpts: 193 | raise newException(Defect, 194 | "Option 'endian' was specified more than once") 195 | case n[1].strVal 196 | of "b": result.opts.endian = bigEndian 197 | of "l": result.opts.endian = littleEndian 198 | of "c": result.opts.endian = cpuEndian 199 | else: 200 | raise newException(Defect, 201 | "Invalid value for endian option (valid values: l, b)") 202 | specifiedOpts.incl poEndian 203 | of "bitEndian": 204 | if poBitEndian in specifiedOpts: 205 | raise newException(Defect, 206 | "Option 'bitEndian' was specified more than once") 207 | case n[1].strVal 208 | of "n": result.opts.bitEndian = bigEndian 209 | of "r": result.opts.bitEndian = littleEndian 210 | else: 211 | raise newException(Defect, 212 | "Invalid value for 'bitEndian' option (valid values: n, r)") 213 | specifiedOpts.incl poBitEndian 214 | of "reference": 215 | if poReference in specifiedOpts: 216 | raise newException(Defect, 217 | "Option 'reference' was specified more than once") 218 | case n[1].strVal 219 | of "y": result.opts.reference = true 220 | of "n": result.opts.reference = false 221 | else: 222 | raise newException(Defect, 223 | "Invalid value for 'reference' option (valid values: y, n)") 224 | of "plugins": 225 | if poPlugins in specifiedOpts: 226 | raise newException(Defect, 227 | "Option 'plugins' was specified more than once") 228 | n[1].expectKind(nnkCurly) 229 | for id in n[1]: 230 | result.opts.plugins.incl(parseEnum[ParserPlugin](id.strVal)) 231 | of "visibility": 232 | if poVisibility in specifiedOpts: 233 | raise newException(Defect, 234 | "Option 'visibility' was specified more than once") 235 | result.opts.visibility = parseEnum[ParserVisibility](n[1].strVal) 236 | else: 237 | raise newException(Defect, &"Unknown option: {$n[0]}") 238 | else: 239 | syntaxError("Invalid header syntax") 240 | 241 | proc decodeField*(def: NimNode, st: var seq[string], opts: ParserOptions): 242 | Field {.compileTime.} = 243 | var 244 | a, b, c: NimNode 245 | prefix: string 246 | case def.kind 247 | of nnkPrefix: 248 | prefix = def[0].strVal 249 | c = def[2][0].copyNimTree 250 | case def[1].kind 251 | of nnkIdent: 252 | a = newCall(def[1].copyNimTree) 253 | of nnkCall: 254 | a = def[1].copyNimTree 255 | of nnkCommand: 256 | case def[1][0].kind 257 | of nnkIdent: 258 | a = newCall(def[1][0].copyNimTree) 259 | of nnkCall: 260 | a = def[1][0].copyNimTree 261 | else: syntaxError("Invalid field syntax") 262 | b = def[1][1].copyNimTree 263 | else: syntaxError("Invalid field syntax") 264 | of nnkCall: 265 | a = def[0].copyNimTree 266 | c = def[1][0].copyNimTree 267 | of nnkCommand: 268 | a = def[0].copyNimTree 269 | b = def[1].copyNimTree 270 | c = def[2][0].copyNimTree 271 | else: syntaxError("Invalid field syntax") 272 | result = Field( 273 | typ: decodeType(a, opts, prefix), 274 | ops: decodeOps(b), 275 | val: decodeValue(c, st)) 276 | result.symbol = 277 | ident(result.val.name) 278 | 279 | proc decodeVariation*(def: NimNode, st: seq[string], opts: ParserOptions): 280 | Variation {.compileTime.} = 281 | def.expectKind(nnkCall) 282 | var 283 | isElseBranch, isEmpty: bool 284 | cases: seq[NimNode] 285 | fields: seq[Field] 286 | if def[0].kind == nnkIdent: 287 | if not eqIdent(def[0], "_"): 288 | syntaxError("Missing parenthesis around branch expression") 289 | isElseBranch = true 290 | if def[1].len == 1 and def[1][0].kind == nnkNilLit: 291 | isEmpty = true 292 | result = Variation(isEmpty: isEmpty, isElseBranch: isElseBranch) 293 | if not isElseBranch: 294 | def[0].expectKind({nnkPar, nnkTupleConstr}) 295 | for c in def[0]: 296 | cases.add(c.copyNimTree) 297 | result.cases = cases 298 | if not isEmpty: 299 | var symbolTable = st 300 | for f in def[1]: 301 | fields.add(decodeField(f, symbolTable, opts)) 302 | result.fields = fields 303 | result.st = symbolTable -------------------------------------------------------------------------------- /binarylang/private/errors.nim: -------------------------------------------------------------------------------- 1 | type 2 | MagicError* = object of Defect 3 | SyntaxError* = object of Defect 4 | 5 | proc syntaxError*() = raise newException(SyntaxError, "Syntax error") 6 | proc syntaxError*(message: string) = raise newException(SyntaxError, message) -------------------------------------------------------------------------------- /binarylang/private/types.nim: -------------------------------------------------------------------------------- 1 | type 2 | ParserPlugin* = enum 3 | ppConverters = "converters" 4 | ParserVisibility* = enum 5 | pvPublic = "public" 6 | pvPrivate = "private" 7 | ParserOptions* = tuple 8 | endian: Endianness 9 | bitEndian: Endianness 10 | reference: bool 11 | plugins: set[ParserPlugin] 12 | visibility: ParserVisibility 13 | ParserOption* = enum 14 | poEndian 15 | poBitEndian 16 | poReference 17 | poPlugins 18 | poVisibility 19 | Kind* = enum 20 | kInt, kUInt, kFloat, kStr, kProduct, kSum 21 | Type* = object 22 | case kind*: Kind 23 | of kProduct, kSum: 24 | symbol*: NimNode 25 | args*: seq[NimNode] 26 | else: 27 | size*: BiggestInt 28 | endian*: Endianness 29 | bitEndian*: Endianness 30 | Operation* = tuple 31 | name: string 32 | typ: NimNode 33 | args: seq[NimNode] 34 | Operations* = seq[Operation] 35 | Repeat* = enum 36 | rNo 37 | rFor 38 | rUntil 39 | Value* = object 40 | name*: string 41 | case repeat*: Repeat 42 | of rFor, rUntil: repeatExpr*: NimNode 43 | of rNo: discard 44 | valueExpr*: NimNode 45 | sizeExpr*: NimNode 46 | isMagic*: bool 47 | isExported*: bool 48 | Field* = ref object 49 | typ*: Type 50 | ops*: Operations 51 | val*: Value 52 | symbol*: NimNode 53 | magic*: Field 54 | Variation* = object 55 | case isElseBranch*: bool: 56 | of false: 57 | cases*: seq[NimNode] 58 | of true: 59 | discard 60 | case isEmpty*: bool: 61 | of false: 62 | fields*: seq[Field] 63 | st*: seq[string] 64 | of true: 65 | discard -------------------------------------------------------------------------------- /docs/changelog.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | docs/changelog 21 | 22 | 23 | 24 | 25 | 67 | 68 | 69 | 70 |
71 |
72 |

docs/changelog

73 | 74 |

v0.8.1 (18 June 2023)

76 | 77 |

Bugfixes

  • #28 Symbols are now exported, unless visibility is set to private.
  • 78 |
79 | 80 |

v0.8.0 (12 January 2023)

  • Operations extension system is no longer experimental
  • 81 |
82 | 83 |

API changes

  • Changed binarylang/plugins to binarylang/operations
  • 84 |
  • Reworked visibility system:
    • Everything is exported by default
    • 85 |
    • The visibility of parsers, discriminator fields (for unions) and symbols generated by plugins is controlled by the new parser option visibility.
    • 86 |
    • Specific fields (including discriminators) can be marked as private using the {.private.} pragma.
    • 87 |
    88 |
  • 89 |
90 | 91 |

Bugfixes

  • Fixed a bug in operation related to type inference
  • 92 |
93 | 94 |

v0.7.0 (11 January 2023)

  • Implemented a plugin system for modular and toggleable codegen
  • 95 |
96 | 97 |

API changes

  • Changed binarylang/plugins to binarylang/operations to avoid terminology confusion in relation to the new plugin system
  • 98 |
99 | 100 |

v0.6.0 (11 January 2023)

  • Added toStr plugin
  • 101 |
  • Added cpu endian parser option
  • 102 |
  • You can now choose between value or ref semantics with a new parser option
  • 103 |
  • Split codebase in multiple source files
  • 104 |
  • Removed deprecated procs
  • 105 |
  • MagicError is now exported
  • 106 |
107 | 108 |

API changes

  • Default semantics changed from ref to value
  • 109 |
110 | 111 |

Bugfixes

  • Multiple values in a single union branch selector did not work in the latest Nim compiler
  • 112 |
113 | 114 |

v0.5.1 (8 April 2021)

115 |

Bugfixes

  • BinaryLang now correctly uses the alterned type specified by the last operation, in the struct's/union's type declaration.
  • 116 |
117 | 118 |

v0.5.0 (8 April 2021)

119 |

API changes

  • The plugin system is reworked as follows:
    • Call syntax is used instead of expression-bracket-expression, in order to support arbitrary number of arguments.
    • 120 |
    • The API is homogenic regardless of whether the plugin interfaces with the stream or not.
    • 121 |
    • Type conversion is supported by explicit annotation.
    • 122 |
    • Due to the above, properties are rendered redundant, and therefore not a thing anymore (completely removed).
    • 123 |
    124 |
  • 125 |
126 | 127 |

v0.4.0 (3 April 2021)

128 |

API changes

  • createParser/createVariantParser were renamed to struct/union respectively
  • 129 |
  • complex type is now divided into product and sum type for parsers created with the struct/union macro respectively. Sum parsers differ from product parsers in that the first argument is mandatory and is treated differently since it refers to the discriminator. Specifically, the passed argument is calculated only during parsing, whereas during serialization the value stored in the disc field is used.
  • 130 |
  • The discriminator field of sum parsers is always called disc implicitly. The second argument of the union macro must be a single identifier denoting the type of the discriminator -as opposed to an expression-colon-expression-.
  • 131 |
132 | 133 |

v0.3.3 (2 April 2021)

  • Converters are now also exported when exporting the corresponding parser
  • 134 |
135 | 136 |

Bugfixes

  • Magic now works properly in createVariantParser
  • 137 |
138 | 139 |

v0.3.2 (2 April 2021)

140 |

Bugfixes:

  • symbol table under branches of variant parsers was not updated after each field
  • 141 |
  • anonymous fields in variant parsers were not discarded
  • 142 |
143 | 144 |

v0.3.1 (25 March 2021)

  • Implemented converters properly. They are now procs with the names:
    • from<parser>
    • 145 |
    • to<parser>
    • 146 |
    147 |
  • 148 |
149 | 150 |

v0.3.0 (25 March 2021)

  • Bugfixes for @put/@hook properties.
  • 151 |
  • The underlying field when using properties can now be accessed with the identifier <field>Impl.
  • 152 |
  • Exportation of fields using * now also works when using properties.
  • 153 |
  • Parser and parser's type symbols can now be exported by prepending * to the parser's name.
  • 154 |
  • createParser and createVariantParser now also generate converters from/to string.
  • 155 |
156 | 157 |

v0.2.0 (22 March 2021)

With many breaking changes, this marks the beginning of version control!

158 |
  • Documentation is updated and refined to reflect the new API.
  • 159 |
  • createParser now declares an object type which is used by the parsing/encoding procs. This enables describing recursive parsers.
  • 160 |
  • The first argument of createParser/createVariantParser must now mandatorily be in lowercase and it has an extra meaning: it is used for deriving the name of the object type: objnamecapitalizeAscii(parsername).
  • 161 |
  • @hook property is implemented
  • 162 |
  • typeGetter is marked as deprecated.
  • 163 |
  • Switched from unittest to testament.
  • 164 |
165 | 166 | 167 | 168 |
169 | 174 |
175 |
176 |
177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | v0.8.1 (18 June 2023) 2 | ------------------------------------------------------------------------------- 3 | - Added a 4 | [developer manual](https://sealmove.github.io/binarylang/devmanual.html). 5 | 6 | ## Bugfixes 7 | - [#28](https://github.com/sealmove/binarylang/issues/28) Symbols are now 8 | exported, unless ``visibility`` is set to ``private``. 9 | 10 | v0.8.0 (12 January 2023) 11 | ------------------------------------------------------------------------------- 12 | - [Operations](https://sealmove.github.io/binarylang/#extensions-operations) 13 | extension system is no longer experimental 14 | 15 | ## API changes 16 | - Changed ``binarylang/plugins`` to ``binarylang/operations`` 17 | - Reworked visibility system: 18 | - Everything is exported by default 19 | - The visibility of parsers, discriminator fields (for unions) and symbols 20 | generated by plugins is controlled by the new parser option ``visibility``. 21 | - Specific fields (including discriminators) can be marked as private using 22 | the ``{.private.}`` pragma. 23 | 24 | ## Bugfixes 25 | - Fixed a bug in operation related to type inference 26 | 27 | v0.7.0 (11 January 2023) 28 | ------------------------------------------------------------------------------- 29 | - Implemented a plugin system for modular and toggleable codegen 30 | 31 | ## API changes 32 | - Changed ``binarylang/plugins`` to ``binarylang/operations`` to avoid 33 | terminology confusion in relation to the new plugin system 34 | 35 | v0.6.0 (11 January 2023) 36 | ------------------------------------------------------------------------------- 37 | - Added ``toStr`` plugin 38 | - Added cpu endian parser option 39 | - You can now choose between value or ref semantics with a new parser option 40 | - Split codebase in multiple source files 41 | - Removed deprecated procs 42 | - MagicError is now exported 43 | 44 | ## API changes 45 | - Default semantics changed from ref to value 46 | 47 | ## Bugfixes 48 | - Multiple values in a single union branch selector did not work in the 49 | latest Nim compiler 50 | 51 | v0.5.1 (8 April 2021) 52 | ------------------------------------------------------------------------------- 53 | ## Bugfixes 54 | - BinaryLang now correctly uses the alterned type specified by the last 55 | operation, in the struct's/union's type declaration. 56 | 57 | v0.5.0 (8 April 2021) 58 | ------------------------------------------------------------------------------- 59 | ## API changes 60 | - The plugin system is reworked as follows: 61 | - Call syntax is used instead of expression-bracket-expression, in order to 62 | support arbitrary number of arguments. 63 | - The API is homogenic regardless of whether the plugin interfaces with the 64 | stream or not. 65 | - Type conversion is supported by explicit annotation. 66 | - Due to the above, *properties* are rendered redundant, and therefore not 67 | a thing anymore (completely removed). 68 | 69 | v0.4.0 (3 April 2021) 70 | ------------------------------------------------------------------------------- 71 | ## API changes 72 | - ``createParser``/``createVariantParser`` were renamed to 73 | ``struct``/``union`` respectively 74 | - *complex* type is now divided into *product* and *sum* type for 75 | parsers created with the ``struct``/``union`` macro respectively. Sum parsers 76 | differ from product parsers in that the first argument is mandatory and is 77 | treated differently since it refers to the *discriminator*. Specifically, the 78 | passed argument is calculated only during parsing, whereas during 79 | serialization the value stored in the ``disc`` field is used. 80 | - The discriminator field of sum parsers is always called ``disc`` 81 | implicitly. The second argument of the ``union`` macro must be a single 82 | identifier denoting the type of the discriminator -as opposed to an 83 | expression-colon-expression-. 84 | 85 | v0.3.3 (2 April 2021) 86 | ------------------------------------------------------------------------------- 87 | - Converters are now also exported when exporting the corresponding parser 88 | 89 | ## Bugfixes 90 | - Magic now works properly in ``createVariantParser`` 91 | 92 | v0.3.2 (2 April 2021) 93 | ------------------------------------------------------------------------------- 94 | ## Bugfixes: 95 | - symbol table under branches of variant parsers was not updated after 96 | each field 97 | - anonymous fields in variant parsers were not discarded 98 | 99 | v0.3.1 (25 March 2021) 100 | ------------------------------------------------------------------------------- 101 | - Implemented *converters* properly. They are now procs with the names: 102 | - ``from`` 103 | - ``to`` 104 | 105 | v0.3.0 (25 March 2021) 106 | ------------------------------------------------------------------------------- 107 | - Bugfixes for ``@put``/``@hook`` properties. 108 | - The underlying field when using properties can now be accessed with the 109 | identifier ``Impl``. 110 | - Exportation of fields using ``*`` now also works when using properties. 111 | - Parser and parser's type symbols can now be exported by prepending ``*`` to 112 | the parser's name. 113 | - ``createParser`` and ``createVariantParser`` now also generate converters 114 | from/to ``string``. 115 | 116 | v0.2.0 (22 March 2021) 117 | ------------------------------------------------------------------------------- 118 | With many breaking changes, this marks the beginning of version control! 119 | 120 | - Documentation is updated and refined to reflect the new API. 121 | - ``createParser`` now declares an object type which is used by the 122 | parsing/encoding procs. This enables describing recursive parsers. 123 | - The first argument of ``createParser``/``createVariantParser`` must now 124 | *mandatorily* be in lowercase and it has an extra meaning: it is used for 125 | deriving the name of the object type: 126 | ``objname`` ≡ ``capitalizeAscii(parsername)``. 127 | - ``@hook`` property is implemented 128 | - ``typeGetter`` is marked as deprecated. 129 | - Switched from unittest to testament. 130 | -------------------------------------------------------------------------------- /docs/devmanual.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | docs/devmanual 21 | 22 | 23 | 24 | 25 | 67 | 68 | 69 | 70 |
71 |
72 |

docs/devmanual

73 | 74 |

Examples

For larger/real-world examples see my n4n6 repo.

75 | 76 |

Running the tests

cd tests
 77 | testament pattern "*.nim"

Clean up files produced by testament:

78 |
find . -maxdepth 1 -type f ! -name '*.nim' -delete
 79 | rm -r testresults
80 |

Releasing a new version

  1. Increment the version in your .nimble file
  2. 81 |
  3. Add a new entry in changelog.rst
  4. 82 |
  5. Commit your changes
  6. 83 |
  7. Tag your release (git tag <tagname>)
  8. 84 |
  9. Push the new tag and commits (git push origin <tagname>)
  10. 85 |
86 | 87 | 88 | 89 |
90 | 95 |
96 |
97 |
98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /docs/devmanual.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ------------------------------------------------------------------------------- 3 | 4 | For larger/real-world examples see my 5 | `n4n6 repo `_. 6 | 7 | Running the tests 8 | ------------------------------------------------------------------------------- 9 | 10 | .. code-block:: cmd 11 | cd tests 12 | testament pattern "*.nim" 13 | 14 | Clean up files produced by testament: 15 | 16 | .. code-block:: cmd 17 | find . -maxdepth 1 -type f ! -name '*.nim' -delete 18 | rm -r testresults 19 | 20 | Releasing a new version 21 | ------------------------------------------------------------------------------- 22 | 1. Increment the version in your `.nimble` file 23 | 2. Add a new entry in `changelog.rst` 24 | 3. Commit your changes 25 | 4. Tag your release (`git tag `) 26 | 5. Push the new tag and commits (`git push origin `) -------------------------------------------------------------------------------- /docs/nimdoc.out.css: -------------------------------------------------------------------------------- 1 | /* 2 | Stylesheet for use with Docutils/rst2html. 3 | 4 | See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to 5 | customize this style sheet. 6 | 7 | Modified from Chad Skeeters' rst2html-style 8 | https://bitbucket.org/cskeeters/rst2html-style/ 9 | 10 | Modified by Boyd Greenfield and narimiran 11 | */ 12 | 13 | :root { 14 | --primary-background: #fff; 15 | --secondary-background: ghostwhite; 16 | --third-background: #e8e8e8; 17 | --info-background: #50c050; 18 | --warning-background: #c0a000; 19 | --error-background: #e04040; 20 | --border: #dde; 21 | --text: #222; 22 | --anchor: #07b; 23 | --anchor-focus: #607c9f; 24 | --input-focus: #1fa0eb; 25 | --strong: #3c3c3c; 26 | --hint: #9A9A9A; 27 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAN4AAAA9CAYAAADCt9ebAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjAzOjQ4KzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMzM0ZjAxYS0yMDExLWE1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"); 28 | 29 | --keyword: #5e8f60; 30 | --identifier: #222; 31 | --comment: #484a86; 32 | --operator: #155da4; 33 | --punctuation: black; 34 | --other: black; 35 | --escapeSequence: #c4891b; 36 | --number: #252dbe; 37 | --literal: #a4255b; 38 | --program: #6060c0; 39 | --option: #508000; 40 | --raw-data: #a4255b; 41 | } 42 | 43 | [data-theme="dark"] { 44 | --primary-background: #171921; 45 | --secondary-background: #1e202a; 46 | --third-background: #2b2e3b; 47 | --info-background: #008000; 48 | --warning-background: #807000; 49 | --error-background: #c03000; 50 | --border: #0e1014; 51 | --text: #fff; 52 | --anchor: #8be9fd; 53 | --anchor-focus: #8be9fd; 54 | --input-focus: #8be9fd; 55 | --strong: #bd93f9; 56 | --hint: #7A7C85; 57 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARMAAABMCAYAAABOBlMuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjE4OjIyKzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDplZGViMzU3MC1iNmZjLWQy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"); 58 | 59 | --keyword: #ff79c6; 60 | --identifier: #f8f8f2; 61 | --comment: #6272a4; 62 | --operator: #ff79c6; 63 | --punctuation: #f8f8f2; 64 | --other: #f8f8f2; 65 | --escapeSequence: #bd93f9; 66 | --number: #bd93f9; 67 | --literal: #f1fa8c; 68 | --program: #9090c0; 69 | --option: #90b010; 70 | --raw-data: #8be9fd; 71 | } 72 | 73 | .theme-switch-wrapper { 74 | display: flex; 75 | align-items: center; 76 | } 77 | 78 | .theme-switch-wrapper em { 79 | margin-left: 10px; 80 | font-size: 1rem; 81 | } 82 | 83 | .theme-switch { 84 | display: inline-block; 85 | height: 22px; 86 | position: relative; 87 | width: 50px; 88 | } 89 | 90 | .theme-switch input { 91 | display: none; 92 | } 93 | 94 | .slider { 95 | background-color: #ccc; 96 | bottom: 0; 97 | cursor: pointer; 98 | left: 0; 99 | position: absolute; 100 | right: 0; 101 | top: 0; 102 | transition: .4s; 103 | } 104 | 105 | .slider:before { 106 | background-color: #fff; 107 | bottom: 4px; 108 | content: ""; 109 | height: 13px; 110 | left: 4px; 111 | position: absolute; 112 | transition: .4s; 113 | width: 13px; 114 | } 115 | 116 | input:checked + .slider { 117 | background-color: #66bb6a; 118 | } 119 | 120 | input:checked + .slider:before { 121 | transform: translateX(26px); 122 | } 123 | 124 | .slider.round { 125 | border-radius: 17px; 126 | } 127 | 128 | .slider.round:before { 129 | border-radius: 50%; 130 | } 131 | 132 | html { 133 | font-size: 100%; 134 | -webkit-text-size-adjust: 100%; 135 | -ms-text-size-adjust: 100%; } 136 | 137 | body { 138 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 139 | font-weight: 400; 140 | font-size: 1.125em; 141 | line-height: 1.5; 142 | color: var(--text); 143 | background-color: var(--primary-background); } 144 | 145 | /* Skeleton grid */ 146 | .container { 147 | position: relative; 148 | width: 100%; 149 | max-width: 1050px; 150 | margin: 0 auto; 151 | padding: 0; 152 | box-sizing: border-box; } 153 | 154 | .column, 155 | .columns { 156 | width: 100%; 157 | float: left; 158 | box-sizing: border-box; 159 | margin-left: 1%; 160 | } 161 | 162 | .column:first-child, 163 | .columns:first-child { 164 | margin-left: 0; } 165 | 166 | .three.columns { 167 | width: 22%; 168 | } 169 | 170 | .nine.columns { 171 | width: 77.0%; } 172 | 173 | .twelve.columns { 174 | width: 100%; 175 | margin-left: 0; } 176 | 177 | @media screen and (max-width: 860px) { 178 | .three.columns { 179 | display: none; 180 | } 181 | .nine.columns { 182 | width: 98.0%; 183 | } 184 | body { 185 | font-size: 1em; 186 | line-height: 1.35; 187 | } 188 | } 189 | 190 | cite { 191 | font-style: italic !important; } 192 | 193 | 194 | /* Nim search input */ 195 | div#searchInputDiv { 196 | margin-bottom: 1em; 197 | } 198 | input#searchInput { 199 | width: 80%; 200 | } 201 | 202 | /* 203 | * Some custom formatting for input forms. 204 | * This also fixes input form colors on Firefox with a dark system theme on Linux. 205 | */ 206 | input { 207 | -moz-appearance: none; 208 | background-color: var(--secondary-background); 209 | color: var(--text); 210 | border: 1px solid var(--border); 211 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 212 | font-size: 0.9em; 213 | padding: 6px; 214 | } 215 | 216 | input:focus { 217 | border: 1px solid var(--input-focus); 218 | box-shadow: 0 0 3px var(--input-focus); 219 | } 220 | 221 | select { 222 | -moz-appearance: none; 223 | background-color: var(--secondary-background); 224 | color: var(--text); 225 | border: 1px solid var(--border); 226 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 227 | font-size: 0.9em; 228 | padding: 6px; 229 | } 230 | 231 | select:focus { 232 | border: 1px solid var(--input-focus); 233 | box-shadow: 0 0 3px var(--input-focus); 234 | } 235 | 236 | /* Docgen styles */ 237 | 238 | :target { 239 | border: 2px solid #B5651D; 240 | border-style: dotted; 241 | } 242 | 243 | /* Links */ 244 | a { 245 | color: var(--anchor); 246 | text-decoration: none; 247 | } 248 | 249 | a span.Identifier { 250 | text-decoration: underline; 251 | text-decoration-color: #aab; 252 | } 253 | 254 | a.reference-toplevel { 255 | font-weight: bold; 256 | } 257 | 258 | a.toc-backref { 259 | text-decoration: none; 260 | color: var(--text); } 261 | 262 | a.link-seesrc { 263 | color: #607c9f; 264 | font-size: 0.9em; 265 | font-style: italic; } 266 | 267 | a:hover, 268 | a:focus { 269 | color: var(--anchor-focus); 270 | text-decoration: underline; } 271 | 272 | a:hover span.Identifier { 273 | color: var(--anchor); 274 | } 275 | 276 | 277 | sub, 278 | sup { 279 | position: relative; 280 | font-size: 75%; 281 | line-height: 0; 282 | vertical-align: baseline; } 283 | 284 | sup { 285 | top: -0.5em; } 286 | 287 | sub { 288 | bottom: -0.25em; } 289 | 290 | img { 291 | width: auto; 292 | height: auto; 293 | max-width: 100%; 294 | vertical-align: middle; 295 | border: 0; 296 | -ms-interpolation-mode: bicubic; } 297 | 298 | @media print { 299 | * { 300 | color: black !important; 301 | text-shadow: none !important; 302 | background: transparent !important; 303 | box-shadow: none !important; } 304 | 305 | a, 306 | a:visited { 307 | text-decoration: underline; } 308 | 309 | a[href]:after { 310 | content: " (" attr(href) ")"; } 311 | 312 | abbr[title]:after { 313 | content: " (" attr(title) ")"; } 314 | 315 | .ir a:after, 316 | a[href^="javascript:"]:after, 317 | a[href^="#"]:after { 318 | content: ""; } 319 | 320 | pre, 321 | blockquote { 322 | border: 1px solid #999; 323 | page-break-inside: avoid; } 324 | 325 | thead { 326 | display: table-header-group; } 327 | 328 | tr, 329 | img { 330 | page-break-inside: avoid; } 331 | 332 | img { 333 | max-width: 100% !important; } 334 | 335 | @page { 336 | margin: 0.5cm; } 337 | 338 | h1 { 339 | page-break-before: always; } 340 | 341 | h1.title { 342 | page-break-before: avoid; } 343 | 344 | p, 345 | h2, 346 | h3 { 347 | orphans: 3; 348 | widows: 3; } 349 | 350 | h2, 351 | h3 { 352 | page-break-after: avoid; } 353 | } 354 | 355 | 356 | p { 357 | margin-top: 0.5em; 358 | margin-bottom: 0.5em; 359 | } 360 | 361 | small { 362 | font-size: 85%; } 363 | 364 | strong { 365 | font-weight: 600; 366 | font-size: 0.95em; 367 | color: var(--strong); 368 | } 369 | 370 | em { 371 | font-style: italic; } 372 | 373 | h1 { 374 | font-size: 1.8em; 375 | font-weight: 400; 376 | padding-bottom: .25em; 377 | border-bottom: 6px solid var(--third-background); 378 | margin-top: 2.5em; 379 | margin-bottom: 1em; 380 | line-height: 1.2em; } 381 | 382 | h1.title { 383 | padding-bottom: 1em; 384 | border-bottom: 0px; 385 | font-size: 2.5em; 386 | text-align: center; 387 | font-weight: 900; 388 | margin-top: 0.75em; 389 | margin-bottom: 0em; 390 | } 391 | 392 | h2 { 393 | font-size: 1.3em; 394 | margin-top: 2em; } 395 | 396 | h2.subtitle { 397 | margin-top: 0em; 398 | text-align: center; } 399 | 400 | h3 { 401 | font-size: 1.125em; 402 | font-style: italic; 403 | margin-top: 1.5em; } 404 | 405 | h4 { 406 | font-size: 1.125em; 407 | margin-top: 1em; } 408 | 409 | h5 { 410 | font-size: 1.125em; 411 | margin-top: 0.75em; } 412 | 413 | h6 { 414 | font-size: 1.1em; } 415 | 416 | 417 | ul, 418 | ol { 419 | padding: 0; 420 | margin-top: 0.5em; 421 | margin-left: 0.75em; } 422 | 423 | ul ul, 424 | ul ol, 425 | ol ol, 426 | ol ul { 427 | margin-bottom: 0; 428 | margin-left: 1.25em; } 429 | 430 | ul.simple > li { 431 | list-style-type: circle; 432 | } 433 | 434 | ul.simple-boot li { 435 | list-style-type: none; 436 | margin-left: 0em; 437 | margin-bottom: 0.5em; 438 | } 439 | 440 | ol.simple > li, ul.simple > li { 441 | margin-bottom: 0.2em; 442 | margin-left: 0.4em } 443 | 444 | ul.simple.simple-toc > li { 445 | margin-top: 1em; 446 | } 447 | 448 | ul.simple-toc { 449 | list-style: none; 450 | font-size: 0.9em; 451 | margin-left: -0.3em; 452 | margin-top: 1em; } 453 | 454 | ul.simple-toc > li { 455 | list-style-type: none; 456 | } 457 | 458 | ul.simple-toc-section { 459 | list-style-type: circle; 460 | margin-left: 0.8em; 461 | color: #6c9aae; } 462 | 463 | ul.nested-toc-section { 464 | list-style-type: circle; 465 | margin-left: -0.75em; 466 | color: var(--text); 467 | } 468 | 469 | ul.nested-toc-section > li { 470 | margin-left: 1.25em; 471 | } 472 | 473 | 474 | ol.arabic { 475 | list-style: decimal; } 476 | 477 | ol.loweralpha { 478 | list-style: lower-alpha; } 479 | 480 | ol.upperalpha { 481 | list-style: upper-alpha; } 482 | 483 | ol.lowerroman { 484 | list-style: lower-roman; } 485 | 486 | ol.upperroman { 487 | list-style: upper-roman; } 488 | 489 | ul.auto-toc { 490 | list-style-type: none; } 491 | 492 | 493 | dl { 494 | margin-bottom: 1.5em; } 495 | 496 | dt { 497 | margin-bottom: -0.5em; 498 | margin-left: 0.0em; } 499 | 500 | dd { 501 | margin-left: 2.0em; 502 | margin-bottom: 3.0em; 503 | margin-top: 0.5em; } 504 | 505 | 506 | hr { 507 | margin: 2em 0; 508 | border: 0; 509 | border-top: 1px solid #aaa; } 510 | 511 | hr.footnote { 512 | width: 25%; 513 | border-top: 0.15em solid #999; 514 | margin-bottom: 0.15em; 515 | margin-top: 0.15em; 516 | } 517 | div.footnote-group { 518 | margin-left: 1em; } 519 | div.footnote-label { 520 | display: inline-block; 521 | min-width: 1.7em; 522 | } 523 | 524 | div.option-list { 525 | border: 0.1em solid var(--border); 526 | } 527 | div.option-list-item { 528 | padding-left: 12em; 529 | padding-right: 0; 530 | padding-bottom: 0.3em; 531 | padding-top: 0.3em; 532 | } 533 | div.odd { 534 | background-color: var(--secondary-background); 535 | } 536 | div.option-list-label { 537 | margin-left: -11.5em; 538 | margin-right: 0em; 539 | min-width: 11.5em; 540 | display: inline-block; 541 | vertical-align: top; 542 | } 543 | div.option-list-description { 544 | width: calc(100% - 1em); 545 | padding-left: 1em; 546 | padding-right: 0; 547 | display: inline-block; 548 | } 549 | 550 | blockquote { 551 | font-size: 0.9em; 552 | font-style: italic; 553 | padding-left: 0.5em; 554 | margin-left: 0; 555 | border-left: 5px solid #bbc; 556 | } 557 | 558 | .pre, span.tok { 559 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 560 | font-weight: 500; 561 | font-size: 0.85em; 562 | color: var(--text); 563 | background-color: var(--third-background); 564 | padding-left: 3px; 565 | padding-right: 3px; 566 | border-radius: 4px; 567 | } 568 | 569 | span.tok { 570 | border: 1px solid #808080; 571 | padding-bottom: 0.1em; 572 | margin-right: 0.2em; 573 | } 574 | 575 | pre { 576 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 577 | color: var(--text); 578 | font-weight: 500; 579 | display: inline-block; 580 | box-sizing: border-box; 581 | min-width: 100%; 582 | padding: 0.5em; 583 | margin-top: 0.5em; 584 | margin-bottom: 0.5em; 585 | font-size: 0.85em; 586 | white-space: pre !important; 587 | overflow-y: hidden; 588 | overflow-x: visible; 589 | background-color: var(--secondary-background); 590 | border: 1px solid var(--border); 591 | -webkit-border-radius: 6px; 592 | -moz-border-radius: 6px; 593 | border-radius: 6px; } 594 | 595 | .pre-scrollable { 596 | max-height: 340px; 597 | overflow-y: scroll; } 598 | 599 | 600 | /* Nim line-numbered tables */ 601 | .line-nums-table { 602 | width: 100%; 603 | table-layout: fixed; } 604 | 605 | table.line-nums-table { 606 | border-radius: 4px; 607 | border: 1px solid #cccccc; 608 | background-color: ghostwhite; 609 | border-collapse: separate; 610 | margin-top: 15px; 611 | margin-bottom: 25px; } 612 | 613 | .line-nums-table tbody { 614 | border: none; } 615 | 616 | .line-nums-table td pre { 617 | border: none; 618 | background-color: transparent; } 619 | 620 | .line-nums-table td.blob-line-nums { 621 | width: 28px; } 622 | 623 | .line-nums-table td.blob-line-nums pre { 624 | color: #b0b0b0; 625 | -webkit-filter: opacity(75%); 626 | filter: opacity(75%); 627 | text-align: right; 628 | border-color: transparent; 629 | background-color: transparent; 630 | padding-left: 0px; 631 | margin-left: 0px; 632 | padding-right: 0px; 633 | margin-right: 0px; } 634 | 635 | 636 | table { 637 | max-width: 100%; 638 | background-color: transparent; 639 | margin-top: 0.5em; 640 | margin-bottom: 1.5em; 641 | border-collapse: collapse; 642 | border-color: var(--third-background); 643 | border-spacing: 0; 644 | font-size: 0.9em; 645 | } 646 | 647 | table th, table td { 648 | padding: 0px 0.5em 0px; 649 | border-color: var(--third-background); 650 | } 651 | 652 | table th { 653 | background-color: var(--third-background); 654 | border-color: var(--third-background); 655 | font-weight: bold; } 656 | 657 | table th.docinfo-name { 658 | background-color: transparent; 659 | text-align: right; 660 | } 661 | 662 | table tr:hover { 663 | background-color: var(--third-background); } 664 | 665 | 666 | /* rst2html default used to remove borders from tables and images */ 667 | .borderless, table.borderless td, table.borderless th { 668 | border: 0; } 669 | 670 | table.borderless td, table.borderless th { 671 | /* Override padding for "table.docutils td" with "! important". 672 | The right padding separates the table cells. */ 673 | padding: 0 0.5em 0 0 !important; } 674 | 675 | .admonition { 676 | padding: 0.3em; 677 | background-color: var(--secondary-background); 678 | border-left: 0.4em solid #7f7f84; 679 | margin-bottom: 0.5em; 680 | -webkit-box-shadow: 0 5px 8px -6px rgba(0,0,0,.2); 681 | -moz-box-shadow: 0 5px 8px -6px rgba(0,0,0,.2); 682 | box-shadow: 0 5px 8px -6px rgba(0,0,0,.2); 683 | } 684 | .admonition-info { 685 | border-color: var(--info-background); 686 | } 687 | .admonition-info-text { 688 | color: var(--info-background); 689 | } 690 | .admonition-warning { 691 | border-color: var(--warning-background); 692 | } 693 | .admonition-warning-text { 694 | color: var(--warning-background); 695 | } 696 | .admonition-error { 697 | border-color: var(--error-background); 698 | } 699 | .admonition-error-text { 700 | color: var(--error-background); 701 | } 702 | 703 | .first { 704 | /* Override more specific margin styles with "! important". */ 705 | margin-top: 0 !important; } 706 | 707 | .last, .with-subtitle { 708 | margin-bottom: 0 !important; } 709 | 710 | .hidden { 711 | display: none; } 712 | 713 | blockquote.epigraph { 714 | margin: 2em 5em; } 715 | 716 | dl.docutils dd { 717 | margin-bottom: 0.5em; } 718 | 719 | object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { 720 | overflow: hidden; } 721 | 722 | 723 | div.figure { 724 | margin-left: 2em; 725 | margin-right: 2em; } 726 | 727 | div.footer, div.header { 728 | clear: both; 729 | text-align: center; 730 | color: #666; 731 | font-size: smaller; } 732 | 733 | div.footer { 734 | padding-top: 5em; 735 | } 736 | 737 | div.line-block { 738 | display: block; 739 | margin-top: 1em; 740 | margin-bottom: 1em; } 741 | 742 | div.line-block div.line-block { 743 | margin-top: 0; 744 | margin-bottom: 0; 745 | margin-left: 1.5em; } 746 | 747 | div.topic { 748 | margin: 2em; } 749 | 750 | div.search_results { 751 | background-color: var(--third-background); 752 | margin: 3em; 753 | padding: 1em; 754 | border: 1px solid #4d4d4d; 755 | } 756 | 757 | div#global-links ul { 758 | margin-left: 0; 759 | list-style-type: none; 760 | } 761 | 762 | div#global-links > simple-boot { 763 | margin-left: 3em; 764 | } 765 | 766 | hr.docutils { 767 | width: 75%; } 768 | 769 | img.align-left, .figure.align-left, object.align-left { 770 | clear: left; 771 | float: left; 772 | margin-right: 1em; } 773 | 774 | img.align-right, .figure.align-right, object.align-right { 775 | clear: right; 776 | float: right; 777 | margin-left: 1em; } 778 | 779 | img.align-center, .figure.align-center, object.align-center { 780 | display: block; 781 | margin-left: auto; 782 | margin-right: auto; } 783 | 784 | .align-left { 785 | text-align: left; } 786 | 787 | .align-center { 788 | clear: both; 789 | text-align: center; } 790 | 791 | .align-right { 792 | text-align: right; } 793 | 794 | /* reset inner alignment in figures */ 795 | div.align-right { 796 | text-align: inherit; } 797 | 798 | p.attribution { 799 | text-align: right; 800 | margin-left: 50%; } 801 | 802 | p.caption { 803 | font-style: italic; } 804 | 805 | p.credits { 806 | font-style: italic; 807 | font-size: smaller; } 808 | 809 | p.label { 810 | white-space: nowrap; } 811 | 812 | p.rubric { 813 | font-weight: bold; 814 | font-size: larger; 815 | color: maroon; 816 | text-align: center; } 817 | 818 | p.topic-title { 819 | font-weight: bold; } 820 | 821 | pre.address { 822 | margin-bottom: 0; 823 | margin-top: 0; 824 | font: inherit; } 825 | 826 | pre.literal-block, pre.doctest-block, pre.math, pre.code { 827 | margin-left: 2em; 828 | margin-right: 2em; } 829 | 830 | pre.code .ln { 831 | color: grey; } 832 | 833 | /* line numbers */ 834 | pre.code, code { 835 | background-color: #eeeeee; } 836 | 837 | pre.code .comment, code .comment { 838 | color: #5c6576; } 839 | 840 | pre.code .keyword, code .keyword { 841 | color: #3B0D06; 842 | font-weight: bold; } 843 | 844 | pre.code .literal.string, code .literal.string { 845 | color: #0c5404; } 846 | 847 | pre.code .name.builtin, code .name.builtin { 848 | color: #352b84; } 849 | 850 | pre.code .deleted, code .deleted { 851 | background-color: #DEB0A1; } 852 | 853 | pre.code .inserted, code .inserted { 854 | background-color: #A3D289; } 855 | 856 | span.classifier { 857 | font-style: oblique; } 858 | 859 | span.classifier-delimiter { 860 | font-weight: bold; } 861 | 862 | span.problematic { 863 | color: #b30000; } 864 | 865 | span.section-subtitle { 866 | /* font-size relative to parent (h1..h6 element) */ 867 | font-size: 80%; } 868 | 869 | span.DecNumber { 870 | color: var(--number); } 871 | 872 | span.BinNumber { 873 | color: var(--number); } 874 | 875 | span.HexNumber { 876 | color: var(--number); } 877 | 878 | span.OctNumber { 879 | color: var(--number); } 880 | 881 | span.FloatNumber { 882 | color: var(--number); } 883 | 884 | span.Identifier { 885 | color: var(--identifier); } 886 | 887 | span.Keyword { 888 | font-weight: 600; 889 | color: var(--keyword); } 890 | 891 | span.StringLit { 892 | color: var(--literal); } 893 | 894 | span.LongStringLit { 895 | color: var(--literal); } 896 | 897 | span.CharLit { 898 | color: var(--literal); } 899 | 900 | span.EscapeSequence { 901 | color: var(--escapeSequence); } 902 | 903 | span.Operator { 904 | color: var(--operator); } 905 | 906 | span.Punctuation { 907 | color: var(--punctuation); } 908 | 909 | span.Comment, span.LongComment { 910 | font-style: italic; 911 | font-weight: 400; 912 | color: var(--comment); } 913 | 914 | span.RegularExpression { 915 | color: darkviolet; } 916 | 917 | span.TagStart { 918 | color: darkviolet; } 919 | 920 | span.TagEnd { 921 | color: darkviolet; } 922 | 923 | span.Key { 924 | color: #252dbe; } 925 | 926 | span.Value { 927 | color: #252dbe; } 928 | 929 | span.RawData { 930 | color: var(--raw-data); } 931 | 932 | span.Assembler { 933 | color: #252dbe; } 934 | 935 | span.Preprocessor { 936 | color: #252dbe; } 937 | 938 | span.Directive { 939 | color: #252dbe; } 940 | 941 | span.option { 942 | font-weight: bold; 943 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 944 | color: var(--option); 945 | } 946 | 947 | span.Prompt { 948 | font-weight: bold; 949 | color: red; } 950 | 951 | span.ProgramOutput { 952 | font-weight: bold; 953 | color: #808080; } 954 | 955 | span.program { 956 | font-weight: bold; 957 | color: var(--program); 958 | text-decoration: underline; 959 | text-decoration-color: var(--hint); 960 | text-decoration-thickness: 0.05em; 961 | text-underline-offset: 0.15em; 962 | } 963 | 964 | span.Command, span.Rule, span.Hyperlink, span.Label, span.Reference, 965 | span.Other { 966 | color: var(--other); } 967 | 968 | /* Pop type, const, proc, and iterator defs in nim def blocks */ 969 | dt pre > span.Identifier, dt pre > span.Operator { 970 | color: var(--identifier); 971 | font-weight: 700; } 972 | 973 | dt pre > span.Keyword ~ span.Identifier, dt pre > span.Identifier ~ span.Identifier, 974 | dt pre > span.Operator ~ span.Identifier, dt pre > span.Other ~ span.Identifier { 975 | color: var(--identifier); 976 | font-weight: inherit; } 977 | 978 | /* Nim sprite for the footer (taken from main page favicon) */ 979 | .nim-sprite { 980 | display: inline-block; 981 | width: 51px; 982 | height: 14px; 983 | background-position: 0 0; 984 | background-size: 51px 14px; 985 | -webkit-filter: opacity(50%); 986 | filter: opacity(50%); 987 | background-repeat: no-repeat; 988 | background-image: var(--nim-sprite-base64); 989 | margin-bottom: 5px; } 990 | 991 | span.pragmadots { 992 | /* Position: relative frees us up to make the dots 993 | look really nice without fucking up the layout and 994 | causing bulging in the parent container */ 995 | position: relative; 996 | /* 1px down looks slightly nicer */ 997 | top: 1px; 998 | padding: 2px; 999 | background-color: var(--third-background); 1000 | border-radius: 4px; 1001 | margin: 0 2px; 1002 | cursor: pointer; 1003 | font-size: 0.8em; 1004 | } 1005 | 1006 | span.pragmadots:hover { 1007 | background-color: var(--hint); 1008 | } 1009 | span.pragmawrap { 1010 | display: none; 1011 | } 1012 | 1013 | span.attachedType { 1014 | display: none; 1015 | visibility: hidden; 1016 | } 1017 | -------------------------------------------------------------------------------- /docs/testresults.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Testament Test Results 6 | 7 | 8 | 9 | 10 | 121 | 122 | 123 |
124 |

Testament Test Results Nim Tester

125 |
126 |
Hostname
127 |
d412dcb1719a
128 |
Git Commit
129 |
1dc9ea3a30c
130 |
Branch ref.
131 |
main
132 |
133 |
134 |
All Tests
135 |
136 | 137 | 15 138 |
139 |
Successful Tests
140 |
141 | 142 | 15 (100.00%) 143 |
144 |
Skipped Tests
145 |
146 | 147 | 0 (0.00%) 148 |
149 |
Failed Tests
150 |
151 | 152 | 0 (0.00%) 153 |
154 |
155 |
156 | 157 | 158 | 159 | 167 | 168 | 169 | 170 | 178 | 179 | 180 | 181 | 189 | 190 | 191 | 192 | 200 | 201 |
All Tests 160 |
161 | 162 | 163 | 164 | 165 |
166 |
Successful Tests 171 |
172 | 173 | 174 | 175 | 176 |
177 |
Skipped Tests 182 |
183 | 184 | 185 | 186 | 187 |
188 |
Failed Tests 193 |
194 | 195 | 196 | 197 | 198 |
199 |
202 |
203 |
204 |
205 | 216 |
217 |
218 |
Name
219 |
aligned.nim c
220 |
Category
221 |
pattern
222 |
Timestamp
223 |
unknown
224 |
Nim Action
225 |
run
226 |
Nim Backend Target
227 |
c
228 |
Code
229 |
reSuccess
230 |
231 |

No output details

232 |
233 |
234 |
235 | 246 |
247 |
248 |
Name
249 |
assertions.nim c
250 |
Category
251 |
pattern
252 |
Timestamp
253 |
unknown
254 |
Nim Action
255 |
run
256 |
Nim Backend Target
257 |
c
258 |
Code
259 |
reSuccess
260 |
261 |

No output details

262 |
263 |
264 |
265 | 276 |
277 |
278 |
Name
279 |
bitendian.nim c
280 |
Category
281 |
pattern
282 |
Timestamp
283 |
unknown
284 |
Nim Action
285 |
run
286 |
Nim Backend Target
287 |
c
288 |
Code
289 |
reSuccess
290 |
291 |

No output details

292 |
293 |
294 |
295 | 306 |
307 |
308 |
Name
309 |
complex.nim c
310 |
Category
311 |
pattern
312 |
Timestamp
313 |
unknown
314 |
Nim Action
315 |
run
316 |
Nim Backend Target
317 |
c
318 |
Code
319 |
reSuccess
320 |
321 |

No output details

322 |
323 |
324 |
325 | 336 |
337 |
338 |
Name
339 |
operations.nim c
340 |
Category
341 |
pattern
342 |
Timestamp
343 |
unknown
344 |
Nim Action
345 |
run
346 |
Nim Backend Target
347 |
c
348 |
Code
349 |
reSuccess
350 |
351 |

No output details

352 |
353 |
354 |
355 | 366 |
367 |
368 |
Name
369 |
options.nim c
370 |
Category
371 |
pattern
372 |
Timestamp
373 |
unknown
374 |
Nim Action
375 |
run
376 |
Nim Backend Target
377 |
c
378 |
Code
379 |
reSuccess
380 |
381 |

No output details

382 |
383 |
384 |
385 | 396 |
397 |
398 |
Name
399 |
plugins.nim c
400 |
Category
401 |
pattern
402 |
Timestamp
403 |
unknown
404 |
Nim Action
405 |
run
406 |
Nim Backend Target
407 |
c
408 |
Code
409 |
reSuccess
410 |
411 |

No output details

412 |
413 |
414 |
415 | 426 |
427 |
428 |
Name
429 |
recursion.nim c
430 |
Category
431 |
pattern
432 |
Timestamp
433 |
unknown
434 |
Nim Action
435 |
run
436 |
Nim Backend Target
437 |
c
438 |
Code
439 |
reSuccess
440 |
441 |

No output details

442 |
443 |
444 |
445 | 456 |
457 |
458 |
Name
459 |
repetition.nim c
460 |
Category
461 |
pattern
462 |
Timestamp
463 |
unknown
464 |
Nim Action
465 |
run
466 |
Nim Backend Target
467 |
c
468 |
Code
469 |
reSuccess
470 |
471 |

No output details

472 |
473 |
474 |
475 | 486 |
487 |
488 |
Name
489 |
strings.nim c
490 |
Category
491 |
pattern
492 |
Timestamp
493 |
unknown
494 |
Nim Action
495 |
run
496 |
Nim Backend Target
497 |
c
498 |
Code
499 |
reSuccess
500 |
501 |

No output details

502 |
503 |
504 |
505 | 516 |
517 |
518 |
Name
519 |
substreams.nim c
520 |
Category
521 |
pattern
522 |
Timestamp
523 |
unknown
524 |
Nim Action
525 |
run
526 |
Nim Backend Target
527 |
c
528 |
Code
529 |
reSuccess
530 |
531 |

No output details

532 |
533 |
534 |
535 | 546 |
547 |
548 |
Name
549 |
tlv.nim c
550 |
Category
551 |
pattern
552 |
Timestamp
553 |
unknown
554 |
Nim Action
555 |
run
556 |
Nim Backend Target
557 |
c
558 |
Code
559 |
reSuccess
560 |
561 |

No output details

562 |
563 |
564 |
565 | 576 |
577 |
578 |
Name
579 |
unaligned.nim c
580 |
Category
581 |
pattern
582 |
Timestamp
583 |
unknown
584 |
Nim Action
585 |
run
586 |
Nim Backend Target
587 |
c
588 |
Code
589 |
reSuccess
590 |
591 |

No output details

592 |
593 |
594 |
595 | 606 |
607 |
608 |
Name
609 |
unnamedfields.nim c
610 |
Category
611 |
pattern
612 |
Timestamp
613 |
unknown
614 |
Nim Action
615 |
run
616 |
Nim Backend Target
617 |
c
618 |
Code
619 |
reSuccess
620 |
621 |

No output details

622 |
623 |
624 |
625 | 636 |
637 |
638 |
Name
639 |
visibility.nim c
640 |
Category
641 |
pattern
642 |
Timestamp
643 |
unknown
644 |
Nim Action
645 |
run
646 |
Nim Backend Target
647 |
c
648 |
Code
649 |
reSuccess
650 |
651 |

No output details

652 |
653 |
654 |
655 |
656 | 663 |
664 | 665 | 666 | -------------------------------------------------------------------------------- /tests/aligned.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | 7 | struct(parser): 8 | 16: beword 9 | l32: ledword 10 | f32: befloat 11 | lf64: ledouble 12 | s: str(3) 13 | s: term = "DEF" 14 | 15 | block: 16 | var fbs = newFileBitStream("data/aligned.hex") 17 | defer: close(fbs) 18 | let data = parser.get(fbs) 19 | assert data.beword == 0x1234 20 | assert data.ledword == 0x1234_5678 21 | assert data.befloat == 0x1234_5678'f32 22 | assert data.ledouble == 0x1234_5678_90AB_CDEF'f64 23 | assert data.str == "ABC" 24 | assert data.term == "DEF" 25 | 26 | # Serialization 27 | var sbs = newStringBitStream() 28 | defer: close(sbs) 29 | parser.put(sbs, data) 30 | sbs.seek(0) 31 | let reparsed = parser.get(sbs) 32 | assert data.beword == reparsed.beword 33 | assert data.ledword == reparsed.ledword 34 | assert data.befloat == reparsed.befloat 35 | assert data.ledouble == reparsed.ledouble 36 | assert data.str == reparsed.str 37 | assert data.term == reparsed.term -------------------------------------------------------------------------------- /tests/assertions.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | 7 | struct(inner): 8 | 8: bytes[4] 9 | struct(parser): 10 | s: str = "ABC" 11 | 8: x = 1 12 | 8: y = 2 13 | 8: z = x + y 14 | *inner: inr 15 | 16 | block: 17 | var fbs = newFileBitStream("data/assertions.hex") 18 | defer: close(fbs) 19 | let data = parser.get(fbs) 20 | assert data.str == "ABC" 21 | assert data.x == 1 22 | assert data.y == 2 23 | assert data.z == 3 24 | assert data.inr.bytes == @[0'i8, 1, 2, 3] 25 | 26 | # Serialization 27 | var sbs = newStringBitStream() 28 | defer: close(sbs) 29 | parser.put(sbs, data) 30 | sbs.seek(0) 31 | let reparsed = parser.get(sbs) 32 | assert data.str == reparsed.str 33 | assert data.x == reparsed.x 34 | assert data.y == reparsed.y 35 | assert data.z == reparsed.z 36 | assert data.inr.bytes == reparsed.inr.bytes -------------------------------------------------------------------------------- /tests/bitendian.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | 7 | struct(parser, bitEndian = r): 8 | 1: b1 9 | 1: b2 10 | 1: b3 11 | 1: b4 12 | 1: b5 13 | 1: b6 14 | 1: b7 15 | 1: b8 16 | 1: b9 17 | 1: b10 18 | 1: b11 19 | 1: b12 20 | 1: b13 21 | 1: b14 22 | 1: b15 23 | 1: b16 24 | 25 | block: 26 | var fbs = newFileBitStream("data/bitendian.hex") 27 | defer: close(fbs) 28 | let data = parser.get(fbs) 29 | assert data.b1 == 0 30 | assert data.b2 == 1 31 | assert data.b3 == 0 32 | assert data.b4 == 0 33 | assert data.b5 == 1 34 | assert data.b6 == 0 35 | assert data.b7 == 0 36 | assert data.b8 == 0 37 | assert data.b9 == 0 38 | assert data.b10 == 0 39 | assert data.b11 == 1 40 | assert data.b12 == 0 41 | assert data.b13 == 1 42 | assert data.b14 == 1 43 | assert data.b15 == 0 44 | assert data.b16 == 0 45 | 46 | # Serialization 47 | var sbs = newStringBitStream() 48 | defer: close(sbs) 49 | parser.put(sbs, data) 50 | sbs.seek(0) 51 | let reparsed = parser.get(sbs) 52 | assert data.b1 == reparsed.b1 53 | assert data.b2 == reparsed.b2 54 | assert data.b3 == reparsed.b3 55 | assert data.b4 == reparsed.b4 56 | assert data.b5 == reparsed.b5 57 | assert data.b6 == reparsed.b6 58 | assert data.b7 == reparsed.b7 59 | assert data.b8 == reparsed.b8 60 | assert data.b9 == reparsed.b9 61 | assert data.b10 == reparsed.b10 62 | assert data.b11 == reparsed.b11 63 | assert data.b12 == reparsed.b12 64 | assert data.b13 == reparsed.b13 65 | assert data.b14 == reparsed.b14 66 | assert data.b15 == reparsed.b15 67 | assert data.b16 == reparsed.b16 -------------------------------------------------------------------------------- /tests/complex.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | 7 | struct(inner): 8 | 8: x 9 | struct(innerWithArgs, a: int8, b: Inner): 10 | 8: x = a 11 | 8: y = b.x 12 | struct(parser): 13 | 8: x 14 | *inner: y 15 | *innerWithArgs(x, y): z 16 | 17 | block: 18 | var fbs = newFileBitStream("data/complex.hex") 19 | defer: close(fbs) 20 | let data = parser.get(fbs) 21 | assert data.x == 0x55 22 | assert data.y.x == 0xAA'i8 23 | assert data.z.x == 0x55 24 | assert data.z.y == 0xAA'i8 25 | 26 | # Serialization 27 | var sbs = newStringBitStream() 28 | defer: close(sbs) 29 | parser.put(sbs, data) 30 | sbs.seek(0) 31 | let reparsed = parser.get(sbs) 32 | assert data.x == reparsed.x 33 | assert data.y.x == reparsed.y.x 34 | assert data.z.x == reparsed.z.x 35 | assert data.z.y == reparsed.z.y -------------------------------------------------------------------------------- /tests/data/aligned.hex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sealmove/binarylang/91797a4585a69b0f12f277dda83cd7f5806f79b4/tests/data/aligned.hex -------------------------------------------------------------------------------- /tests/data/assertions.hex: -------------------------------------------------------------------------------- 1 | ABC -------------------------------------------------------------------------------- /tests/data/bitendian.hex: -------------------------------------------------------------------------------- 1 | 4 -------------------------------------------------------------------------------- /tests/data/complex.hex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sealmove/binarylang/91797a4585a69b0f12f277dda83cd7f5806f79b4/tests/data/complex.hex -------------------------------------------------------------------------------- /tests/data/options.hex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sealmove/binarylang/91797a4585a69b0f12f277dda83cd7f5806f79b4/tests/data/options.hex -------------------------------------------------------------------------------- /tests/data/plugins.hex: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /tests/data/recursion.hex: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /tests/data/repetition.hex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sealmove/binarylang/91797a4585a69b0f12f277dda83cd7f5806f79b4/tests/data/repetition.hex -------------------------------------------------------------------------------- /tests/data/strings.hex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sealmove/binarylang/91797a4585a69b0f12f277dda83cd7f5806f79b4/tests/data/strings.hex -------------------------------------------------------------------------------- /tests/data/substreams.hex: -------------------------------------------------------------------------------- 1 | 4Vx -------------------------------------------------------------------------------- /tests/data/unaligned.hex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sealmove/binarylang/91797a4585a69b0f12f277dda83cd7f5806f79b4/tests/data/unaligned.hex -------------------------------------------------------------------------------- /tests/operations.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | from ../binarylang/operations import condGet, condPut, validGet, validPut 7 | from strutils import intToStr, parseInt 8 | 9 | template addGet(parse, parsed, output, n: untyped) = 10 | parse 11 | output = parsed + n 12 | template addPut(encode, encoded, output, n: untyped) = 13 | output = encoded - n 14 | encode 15 | 16 | struct(parser): 17 | 16: x 18 | l32 {add(x), valid((_ * (x - 0x1232)) == 0x2468_D158'i32)}: y 19 | f32 {cond(x != 0x1234)}: no 20 | f32 {cond(x == 0x1234)}: yes 21 | 22 | template toIntGet(parse, parsed, output) = 23 | parse 24 | output = parsed.int 25 | template toIntPut(encode, encoded, output) = 26 | output = encoded.int16 27 | encode 28 | 29 | template toStrGet(parse, parsed, output) = 30 | parse 31 | output = parsed.intToStr 32 | template toStrPut(encode, encoded, output) = 33 | output = encoded.parseInt 34 | encode 35 | 36 | struct(typing): 37 | 16 {toInt[int], add(5), toStr[string]}: x 38 | 39 | block: 40 | var fbs = newFileBitStream("data/aligned.hex") 41 | defer: close(fbs) 42 | let data = parser.get(fbs) 43 | assert data.x == 0x1234 44 | assert data.y == 0x1234_68AC 45 | assert data.no == 0 46 | assert data.yes == 0x1234_5678'f32 47 | 48 | # Serialization 49 | var sbs = newStringBitStream() 50 | defer: close(sbs) 51 | parser.put(sbs, data) 52 | sbs.seek(0) 53 | let reparsed = parser.get(sbs) 54 | assert data.x == reparsed.x 55 | assert data.y == reparsed.y 56 | assert data.no == reparsed.no 57 | assert data.yes == reparsed.yes 58 | 59 | block: 60 | var fbs = newFileBitStream("data/aligned.hex") 61 | defer: close(fbs) 62 | let data = typing.get(fbs) 63 | assert data.x == "4665" 64 | -------------------------------------------------------------------------------- /tests/options.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | 7 | struct(parser, endian = l, bitEndian = r): 8 | 16: little 9 | b16: big 10 | 4: first 11 | 4: second 12 | n4: third 13 | n4: fourth 14 | 15 | block: 16 | var fbs = newFileBitStream("data/options.hex") 17 | defer: close(fbs) 18 | let data = parser.get(fbs) 19 | assert data.little == 128 20 | assert data.big == -32768 21 | assert data.first == 1 22 | assert data.second == 2 23 | assert data.third == 3 24 | assert data.fourth == 4 25 | 26 | # Serialization 27 | var sbs = newStringBitStream() 28 | defer: close(sbs) 29 | parser.put(sbs, data) 30 | sbs.seek(0) 31 | let reparsed = parser.get(sbs) 32 | assert data.little == reparsed.little 33 | assert data.big == reparsed.big 34 | assert data.first == reparsed.first 35 | assert data.second == reparsed.second 36 | assert data.third == reparsed.third 37 | assert data.fourth == reparsed.fourth -------------------------------------------------------------------------------- /tests/plugins.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | 7 | struct(data, plugins = {converters}): 8 | 8: x 9 | 10 | block: 11 | var fileContent = readFile("data/plugins.hex") 12 | let data = fileContent.toData 13 | assert data.x == 0x41 14 | 15 | # Serialization 16 | let reparsed = data.fromData 17 | assert reparsed == "A" -------------------------------------------------------------------------------- /tests/recursion.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang, ../binarylang/operations 6 | 7 | struct(parser, reference = y): 8 | u8: hasChild 9 | *parser {cond(hasChild.bool)}: child 10 | 11 | block: 12 | var fbs = newFileBitStream("data/recursion.hex") 13 | defer: close(fbs) 14 | let data = parser.get(fbs) 15 | assert data.hasChild == 1 16 | assert data.child.hasChild == 1 17 | assert data.child.child.hasChild == 0 18 | 19 | # Serialization 20 | var sbs = newStringBitStream() 21 | defer: close(sbs) 22 | parser.put(sbs, data) 23 | sbs.seek(0) 24 | let reparsed = parser.get(sbs) 25 | assert data.hasChild == reparsed.hasChild 26 | assert data.child.hasChild == reparsed.child.hasChild 27 | assert data.child.child.hasChild == reparsed.child.child.hasChild 28 | -------------------------------------------------------------------------------- /tests/repetition.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | 7 | struct(inner, size: int): 8 | 8: x[size] 9 | struct(parser): 10 | *inner(i+1): complex[3] 11 | 8: size 12 | 4: nibbles[size] 13 | 8: bytes{_ == 2} 14 | 2: duets{2*i > 7} 15 | 3: trios{s.atEnd} 16 | 17 | block: 18 | var fbs = newFileBitStream("data/repetition.hex") 19 | defer: close(fbs) 20 | let data = parser.get(fbs) 21 | assert data.complex[0].x == @[1'i8] 22 | assert data.complex[1].x == @[2'i8, 3] 23 | assert data.complex[2].x == @[4'i8, 5, 6] 24 | assert data.nibbles == @[0'i8, 1, 2, 3] 25 | assert data.bytes == @[0'i8, 1, 2] 26 | assert data.duets == @[0'i8, 1, 2, 3] 27 | assert data.trios == @[3'i8, 4, 5, 6, 7, 0, 1, 2] 28 | 29 | # Serialization 30 | var sbs = newStringBitStream() 31 | defer: close(sbs) 32 | parser.put(sbs, data) 33 | sbs.seek(0) 34 | let reparsed = parser.get(sbs) 35 | assert data.complex[0].x == reparsed.complex[0].x 36 | assert data.complex[1].x == reparsed.complex[1].x 37 | assert data.complex[2].x == reparsed.complex[2].x 38 | assert data.size == reparsed.size 39 | assert data.nibbles == reparsed.nibbles 40 | assert data.bytes == reparsed.bytes 41 | assert data.duets == reparsed.duets 42 | assert data.trios == reparsed.trios -------------------------------------------------------------------------------- /tests/strings.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | 7 | struct(parser): 8 | s: a 9 | s: b(2) 10 | s: c = "E" 11 | s: d 12 | s: _ = "H" 13 | s: e[2] 14 | s: {f} 15 | u8: term = 0xFF 16 | s: {g[2]} 17 | s: _ = "END" 18 | 19 | block: 20 | var fbs = newFileBitStream("data/strings.hex") 21 | defer: close(fbs) 22 | let data = parser.get(fbs) 23 | assert data.a == "AB" 24 | assert data.b == "CD" 25 | assert data.c == "E" 26 | assert data.d == "FG" 27 | assert data.e == @["IJ", "KL"] 28 | assert data.f == @["M", "NO", "PQR"] 29 | assert data.g == @[@["01", "234"], @["5", "678"]] 30 | 31 | # Serialization 32 | var sbs = newStringBitStream() 33 | defer: close(sbs) 34 | parser.put(sbs, data) 35 | sbs.seek(0) 36 | let reparsed = parser.get(sbs) 37 | assert data.a == reparsed.a 38 | assert data.b == reparsed.b 39 | assert data.c == reparsed.c 40 | assert data.d == reparsed.d 41 | assert data.e == reparsed.e 42 | assert data.f == reparsed.f 43 | assert data.term == reparsed.term 44 | assert data.g == reparsed.g -------------------------------------------------------------------------------- /tests/substreams.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | 7 | struct(aux): 8 | 8: x 9 | struct(parser): 10 | *aux: x(3) 11 | 8: y 12 | 13 | block: 14 | var fbs = newFileBitStream("data/substreams.hex") 15 | defer: close(fbs) 16 | let data = parser.get(fbs) 17 | assert data.x.x == 0x12 18 | assert data.y == 0x78 19 | 20 | # Serialization 21 | var sbs = newStringBitStream() 22 | defer: close(sbs) 23 | parser.put(sbs, data) 24 | sbs.seek(0) 25 | let reparsed = parser.get(sbs) 26 | assert data.x.x == reparsed.x.x 27 | assert data.y == reparsed.y -------------------------------------------------------------------------------- /tests/tlv.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | 7 | union(someTlv, byte): 8 | (0x12): u16: a 9 | (0x34, 0x56): 10 | u32: b 11 | u16: c 12 | _: nil 13 | struct(parser): 14 | u8: code1 15 | u8: code2 16 | +someTlv(code1): variant1 17 | +someTlv(code2): variant2 18 | 19 | block: 20 | var fbs = newFileBitStream("data/aligned.hex") 21 | defer: close(fbs) 22 | let data = parser.get(fbs) 23 | assert data.code1 == 0x12 24 | assert data.code2 == 0x34 25 | assert data.variant1.a == 0x7856 26 | assert data.variant2.b == 0x34121234 27 | assert data.variant2.c == 0x5678 28 | 29 | # Serialization 30 | var sbs = newStringBitStream() 31 | defer: close(sbs) 32 | parser.put(sbs, data) 33 | sbs.seek(0) 34 | let reparsed = parser.get(sbs) 35 | assert data.code1 == reparsed.code1 36 | assert data.code2 == reparsed.code2 37 | assert data.variant1.a == reparsed.variant1.a 38 | assert data.variant2.b == reparsed.variant2.b -------------------------------------------------------------------------------- /tests/unaligned.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | 7 | struct(unaligned): 8 | 1: a 9 | 5: b 10 | 10: c 11 | r12: d 12 | r20: e 13 | 7: f 14 | l64: g 15 | 57: h 16 | 17 | block: 18 | var fbs = newFileBitStream("data/unaligned.hex") 19 | defer: close(fbs) 20 | let data = unaligned.get(fbs) 21 | assert data.a == 1 22 | assert data.b == 5 23 | assert data.c == 10 24 | assert data.d == 12 25 | assert data.e == 20 26 | assert data.f == 7 27 | assert data.g == 64 28 | assert data.h == 57 29 | 30 | # Serialization 31 | var sbs = newStringBitStream() 32 | defer: close(sbs) 33 | unaligned.put(sbs, data) 34 | sbs.seek(0) 35 | let reparsed = unaligned.get(sbs) 36 | assert data.a == reparsed.a 37 | assert data.b == reparsed.b 38 | assert data.c == reparsed.c 39 | assert data.d == reparsed.d 40 | assert data.e == reparsed.e 41 | assert data.f == reparsed.f 42 | assert data.g == reparsed.g 43 | assert data.h == reparsed.h -------------------------------------------------------------------------------- /tests/unnamedfields.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | 7 | struct(parser): 8 | 16: _ = 0x1234 9 | l32: _ 10 | f32: _ 11 | lf64: _ 12 | s: _(3) 13 | s: _ = "DEF" 14 | 15 | block: 16 | var fbs = newFileBitStream("data/aligned.hex") 17 | defer: close(fbs) 18 | discard parser.get(fbs) -------------------------------------------------------------------------------- /tests/visibility.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | cmd: "nim c -r $file" 3 | """ 4 | 5 | import ../binarylang 6 | import tlv 7 | 8 | block: 9 | # struct 10 | var fbs = newFileBitStream("data/aligned.hex") 11 | defer: close(fbs) 12 | let data = parser.get(fbs) 13 | discard data.code1 == 0x12 14 | 15 | var sbs = newStringBitStream() 16 | defer: close(sbs) 17 | parser.put(sbs, data) 18 | sbs.seek(0) 19 | 20 | # union 21 | let data2 = someTlv.get(sbs, 0x12'u8) 22 | discard data2.a == 0x12 --------------------------------------------------------------------------------