├── test ├── empty.hi ├── braces-empty.hi ├── fail-invalid-key-3.hi ├── single-dict.hi ├── single-list.hi ├── fail-invalid-key-1.hi ├── fail-invalid-key-2.hi ├── fail-empty-dict-comma.hi ├── fail-empty-list-comma.hi ├── fail-float-hexdot.hi ├── fail-float-octalexp.hi ├── fail-invalid-bool-1.hi ├── fail-invalid-bool-2.hi ├── fail-unterminated-dict-1.hi ├── fail-unterminated-list-1.hi ├── single-bool-false.hi ├── single-bool-true.hi ├── single-dict-no-colon.hi ├── single-list-no-colon.hi ├── fail-float-double-exp.hi ├── fail-float-doubledot-1.hi ├── fail-float-doubledot-2.hi ├── fail-float-octaldot.hi ├── fail-integer-double-sign-1.hi ├── fail-integer-double-sign-2.hi ├── fail-integer-double-sign-3.hi ├── fail-unterminated-bool.hi ├── fail-unterminated-dict-2.hi ├── fail-unterminated-escape-1.hi ├── fail-unterminated-escape-2.hi ├── fail-unterminated-escape-3.hi ├── fail-unterminated-list-2.hi ├── fail-unterminated-string-1.hi ├── fail-unterminated-string-3.hi ├── single-string.hi ├── comment.hi ├── fail-hep-001-annot-1.hi ├── fail-hep-001-annot-3.hi ├── fail-unbalanced-braces-1.hi ├── fail-unbalanced-braces-2.hi ├── fail-unterminated-dict-3.hi ├── fail-unterminated-list-3.hi ├── fail-unterminated-string-2.hi ├── single-bool-false-no-colon.hi ├── single-bool-true-no-colon.hi ├── single-string-no-colon.hi ├── fail-duplicate-annot-1.hi ├── fail-hep-001-annot-4.hi ├── fail-hep-001-annot-2.hi ├── fail-invalid-key-1.err ├── fail-duplicate-annot-2.hi ├── fail-float-hexdot.err ├── fail-float-octaldot.err ├── fail-float-octalexp.err ├── fail-invalid-bool-1.err ├── fail-invalid-bool-2.err ├── fail-invalid-key-2.err ├── fail-invalid-key-3.err ├── fail-duplicate-annot-1.err ├── fail-duplicate-annot-2.err ├── fail-empty-dict-comma.err ├── fail-empty-list-comma.err ├── fail-float-double-exp.err ├── fail-float-doubledot-1.err ├── fail-float-doubledot-2.err ├── fail-unbalanced-braces-2.err ├── fail-unterminated-dict-1.err ├── fail-unterminated-dict-2.err ├── fail-hep-001-annot-3.err ├── fail-hep-001-annot-4.err ├── fail-integer-double-sign-1.err ├── fail-integer-double-sign-2.err ├── fail-integer-double-sign-3.err ├── fail-unbalanced-braces-1.err ├── fail-unterminated-escape-1.err ├── fail-unterminated-escape-2.err ├── fail-unterminated-escape-3.err ├── fail-unterminated-string-1.err ├── fail-unterminated-string-2.err ├── fail-unterminated-string-3.err ├── fail-hep-001-annot-2.err ├── fail-hep-001-annot-1.err ├── floats.hi ├── comment-multiline.hi ├── comment-interleaved.hi ├── oneline.hi ├── string-escapes.hi ├── dict-rehash.hi ├── integers.hi ├── lists.hi ├── dicts.hi └── hep-001.hi ├── .gitmodules ├── .gitignore ├── .github └── workflows │ ├── build.yml │ └── coverage.yml ├── doc ├── index.rst ├── Makefile ├── make.bat ├── quickstart.rst ├── conf.py └── apiref.rst ├── hipack-list.c ├── README.md ├── CHANGELOG.md ├── tools ├── extract-docs.awk ├── hipack-parse.c ├── strtonum.c ├── hipack-cat.c ├── hipack-roundtrip.c ├── run-tests ├── hipack-get.c └── hipack-test-api.c ├── hipack-alloc.c ├── hipack-misc.c ├── hipack-string.c ├── Makefile ├── hipack-dict.c ├── hipack-writer.c ├── hipack-parser.c └── hipack.h /test/empty.hi: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/braces-empty.hi: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/fail-invalid-key-3.hi: -------------------------------------------------------------------------------- 1 | ]: 2 | -------------------------------------------------------------------------------- /test/single-dict.hi: -------------------------------------------------------------------------------- 1 | dict: { } 2 | -------------------------------------------------------------------------------- /test/single-list.hi: -------------------------------------------------------------------------------- 1 | list: [ ] 2 | -------------------------------------------------------------------------------- /test/fail-invalid-key-1.hi: -------------------------------------------------------------------------------- 1 | bad,: 1 2 | -------------------------------------------------------------------------------- /test/fail-invalid-key-2.hi: -------------------------------------------------------------------------------- 1 | ,: 1 2 | -------------------------------------------------------------------------------- /test/fail-empty-dict-comma.hi: -------------------------------------------------------------------------------- 1 | bad: {,} 2 | -------------------------------------------------------------------------------- /test/fail-empty-list-comma.hi: -------------------------------------------------------------------------------- 1 | bad: [,] 2 | -------------------------------------------------------------------------------- /test/fail-float-hexdot.hi: -------------------------------------------------------------------------------- 1 | bad: 0x3.2 2 | -------------------------------------------------------------------------------- /test/fail-float-octalexp.hi: -------------------------------------------------------------------------------- 1 | bad: 0123e1 2 | -------------------------------------------------------------------------------- /test/fail-invalid-bool-1.hi: -------------------------------------------------------------------------------- 1 | bool: Yes 2 | -------------------------------------------------------------------------------- /test/fail-invalid-bool-2.hi: -------------------------------------------------------------------------------- 1 | bool: FALSE 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-dict-1.hi: -------------------------------------------------------------------------------- 1 | bad: { 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-list-1.hi: -------------------------------------------------------------------------------- 1 | bad: [ 2 | -------------------------------------------------------------------------------- /test/single-bool-false.hi: -------------------------------------------------------------------------------- 1 | bool: false 2 | -------------------------------------------------------------------------------- /test/single-bool-true.hi: -------------------------------------------------------------------------------- 1 | boolean: True 2 | -------------------------------------------------------------------------------- /test/single-dict-no-colon.hi: -------------------------------------------------------------------------------- 1 | dict { } 2 | -------------------------------------------------------------------------------- /test/single-list-no-colon.hi: -------------------------------------------------------------------------------- 1 | list [ ] 2 | -------------------------------------------------------------------------------- /test/fail-float-double-exp.hi: -------------------------------------------------------------------------------- 1 | bad: 1e1e1 2 | -------------------------------------------------------------------------------- /test/fail-float-doubledot-1.hi: -------------------------------------------------------------------------------- 1 | bad: 1.1.1 2 | -------------------------------------------------------------------------------- /test/fail-float-doubledot-2.hi: -------------------------------------------------------------------------------- 1 | bad: 1..1 2 | -------------------------------------------------------------------------------- /test/fail-float-octaldot.hi: -------------------------------------------------------------------------------- 1 | bad: 0123.56 2 | -------------------------------------------------------------------------------- /test/fail-integer-double-sign-1.hi: -------------------------------------------------------------------------------- 1 | bad: --1 2 | -------------------------------------------------------------------------------- /test/fail-integer-double-sign-2.hi: -------------------------------------------------------------------------------- 1 | bad: -+1 2 | -------------------------------------------------------------------------------- /test/fail-integer-double-sign-3.hi: -------------------------------------------------------------------------------- 1 | bad: +-1 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-bool.hi: -------------------------------------------------------------------------------- 1 | bool: Fal 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-dict-2.hi: -------------------------------------------------------------------------------- 1 | bad: { a: 1 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-escape-1.hi: -------------------------------------------------------------------------------- 1 | bad: "\ 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-escape-2.hi: -------------------------------------------------------------------------------- 1 | bad: " 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-escape-3.hi: -------------------------------------------------------------------------------- 1 | bad: "\a" 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-list-2.hi: -------------------------------------------------------------------------------- 1 | bad: [ a: 1 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-string-1.hi: -------------------------------------------------------------------------------- 1 | string: " 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-string-3.hi: -------------------------------------------------------------------------------- 1 | bad: "\" 2 | -------------------------------------------------------------------------------- /test/single-string.hi: -------------------------------------------------------------------------------- 1 | string: "a string" 2 | -------------------------------------------------------------------------------- /test/comment.hi: -------------------------------------------------------------------------------- 1 | # Only a comment, empty otherwise 2 | -------------------------------------------------------------------------------- /test/fail-hep-001-annot-1.hi: -------------------------------------------------------------------------------- 1 | bad-type :.int True 2 | -------------------------------------------------------------------------------- /test/fail-hep-001-annot-3.hi: -------------------------------------------------------------------------------- 1 | empty-annot :. 0 2 | -------------------------------------------------------------------------------- /test/fail-unbalanced-braces-1.hi: -------------------------------------------------------------------------------- 1 | bad: [ { ] } 2 | -------------------------------------------------------------------------------- /test/fail-unbalanced-braces-2.hi: -------------------------------------------------------------------------------- 1 | bad: { [ } ] 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-dict-3.hi: -------------------------------------------------------------------------------- 1 | bad: { a: 1, 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-list-3.hi: -------------------------------------------------------------------------------- 1 | bad: [ a: 1, 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-string-2.hi: -------------------------------------------------------------------------------- 1 | string: "a str 2 | -------------------------------------------------------------------------------- /test/single-bool-false-no-colon.hi: -------------------------------------------------------------------------------- 1 | bool false 2 | -------------------------------------------------------------------------------- /test/single-bool-true-no-colon.hi: -------------------------------------------------------------------------------- 1 | boolean True 2 | -------------------------------------------------------------------------------- /test/single-string-no-colon.hi: -------------------------------------------------------------------------------- 1 | string "a string" 2 | -------------------------------------------------------------------------------- /test/fail-duplicate-annot-1.hi: -------------------------------------------------------------------------------- 1 | bad :annot :annot 0 2 | -------------------------------------------------------------------------------- /test/fail-hep-001-annot-4.hi: -------------------------------------------------------------------------------- 1 | invalid-intrinsic :.xyz 0 2 | -------------------------------------------------------------------------------- /test/fail-hep-001-annot-2.hi: -------------------------------------------------------------------------------- 1 | unmatched-annots :.int :.bool 0 2 | -------------------------------------------------------------------------------- /test/fail-invalid-key-1.err: -------------------------------------------------------------------------------- 1 | line 1, column 4: missing separator 2 | -------------------------------------------------------------------------------- /test/fail-duplicate-annot-2.hi: -------------------------------------------------------------------------------- 1 | bad :foo :bar :baz :foo :bar :baz 0 2 | -------------------------------------------------------------------------------- /test/fail-float-hexdot.err: -------------------------------------------------------------------------------- 1 | line 1, column 9: invalid numeric value 2 | -------------------------------------------------------------------------------- /test/fail-float-octaldot.err: -------------------------------------------------------------------------------- 1 | line 1, column 10: invalid numeric value 2 | -------------------------------------------------------------------------------- /test/fail-float-octalexp.err: -------------------------------------------------------------------------------- 1 | line 1, column 10: invalid numeric value 2 | -------------------------------------------------------------------------------- /test/fail-invalid-bool-1.err: -------------------------------------------------------------------------------- 1 | line 1, column 7: invalid numeric value 2 | -------------------------------------------------------------------------------- /test/fail-invalid-bool-2.err: -------------------------------------------------------------------------------- 1 | line 1, column 8: invalid boolean value 2 | -------------------------------------------------------------------------------- /test/fail-invalid-key-2.err: -------------------------------------------------------------------------------- 1 | line 1, column 1: missing dictionary key 2 | -------------------------------------------------------------------------------- /test/fail-invalid-key-3.err: -------------------------------------------------------------------------------- 1 | line 1, column 1: missing dictionary key 2 | -------------------------------------------------------------------------------- /test/fail-duplicate-annot-1.err: -------------------------------------------------------------------------------- 1 | line 1, column 19: duplicate annotation 2 | -------------------------------------------------------------------------------- /test/fail-duplicate-annot-2.err: -------------------------------------------------------------------------------- 1 | line 1, column 25: duplicate annotation 2 | -------------------------------------------------------------------------------- /test/fail-empty-dict-comma.err: -------------------------------------------------------------------------------- 1 | line 1, column 7: missing dictionary key 2 | -------------------------------------------------------------------------------- /test/fail-empty-list-comma.err: -------------------------------------------------------------------------------- 1 | line 1, column 7: invalid numeric value 2 | -------------------------------------------------------------------------------- /test/fail-float-double-exp.err: -------------------------------------------------------------------------------- 1 | line 1, column 9: invalid numeric value 2 | -------------------------------------------------------------------------------- /test/fail-float-doubledot-1.err: -------------------------------------------------------------------------------- 1 | line 1, column 9: invalid numeric value 2 | -------------------------------------------------------------------------------- /test/fail-float-doubledot-2.err: -------------------------------------------------------------------------------- 1 | line 1, column 8: invalid numeric value 2 | -------------------------------------------------------------------------------- /test/fail-unbalanced-braces-2.err: -------------------------------------------------------------------------------- 1 | line 1, column 8: missing dictionary key 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-dict-1.err: -------------------------------------------------------------------------------- 1 | line 2, column 1: missing dictionary key 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-dict-2.err: -------------------------------------------------------------------------------- 1 | line 2, column 1: missing dictionary key 2 | -------------------------------------------------------------------------------- /test/fail-hep-001-annot-3.err: -------------------------------------------------------------------------------- 1 | line 1, column 16: invalid intrinsic annotation 2 | -------------------------------------------------------------------------------- /test/fail-hep-001-annot-4.err: -------------------------------------------------------------------------------- 1 | line 1, column 25: invalid intrinsic annotation 2 | -------------------------------------------------------------------------------- /test/fail-integer-double-sign-1.err: -------------------------------------------------------------------------------- 1 | line 1, column 7: invalid numeric value 2 | -------------------------------------------------------------------------------- /test/fail-integer-double-sign-2.err: -------------------------------------------------------------------------------- 1 | line 1, column 7: invalid numeric value 2 | -------------------------------------------------------------------------------- /test/fail-integer-double-sign-3.err: -------------------------------------------------------------------------------- 1 | line 1, column 7: invalid numeric value 2 | -------------------------------------------------------------------------------- /test/fail-unbalanced-braces-1.err: -------------------------------------------------------------------------------- 1 | line 1, column 10: missing dictionary key 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-escape-1.err: -------------------------------------------------------------------------------- 1 | line 2, column 1: invalid escape sequence 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-escape-2.err: -------------------------------------------------------------------------------- 1 | line 2, column 1: unterminated string value 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-escape-3.err: -------------------------------------------------------------------------------- 1 | line 1, column 9: invalid escape sequence 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-string-1.err: -------------------------------------------------------------------------------- 1 | line 2, column 1: unterminated string value 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-string-2.err: -------------------------------------------------------------------------------- 1 | line 2, column 1: unterminated string value 2 | -------------------------------------------------------------------------------- /test/fail-unterminated-string-3.err: -------------------------------------------------------------------------------- 1 | line 2, column 1: unterminated string value 2 | -------------------------------------------------------------------------------- /test/fail-hep-001-annot-2.err: -------------------------------------------------------------------------------- 1 | line 1, column 31: multiple intrinsic type annotations 2 | -------------------------------------------------------------------------------- /test/fail-hep-001-annot-1.err: -------------------------------------------------------------------------------- 1 | line 2, column 1: annotated type does not match value type 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "fpconv"] 2 | path = fpconv 3 | url = https://github.com/night-shift/fpconv/ 4 | -------------------------------------------------------------------------------- /test/floats.hi: -------------------------------------------------------------------------------- 1 | dotted [ 2 | 1.1 -1.1 3 | 0.1 -0.1 4 | ] 5 | sci [ 6 | 1e1 -1e1 7 | 1e-1 -1e-1 8 | 1.1e1 -1.1e-1 9 | ] 10 | -------------------------------------------------------------------------------- /test/comment-multiline.hi: -------------------------------------------------------------------------------- 1 | # Only a comment, the file 2 | # is empty otherwise. 3 | # And the comment spans. 4 | 5 | # Multiple 6 | # 7 | # lines. 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | .*.sw[op] 4 | *.gcda 5 | *.gcno 6 | /tools/hipack-cat 7 | /tools/hipack-get 8 | /tools/hipack-parse 9 | /tools/hipack-roundtrip 10 | /tools/hipack-test-api 11 | /doc/_build/ 12 | /.cache/ 13 | /compile_commands.json 14 | -------------------------------------------------------------------------------- /test/comment-interleaved.hi: -------------------------------------------------------------------------------- 1 | # Only a comment, empty otherwise 2 | authors? True 3 | 4 | # List of HiPack authors 5 | authors [ 6 | "Adrián Pérez" # Main developer 7 | "Óscar G. Amor" # Contributor 8 | "Andrés J. Díaz" # Another contributor 9 | ] 10 | -------------------------------------------------------------------------------- /test/oneline.hi: -------------------------------------------------------------------------------- 1 | # Using colons and commas 2 | person{name:"Peter",lastname:"Parker",male:True} 3 | # Using whitespace 4 | person{name "Peter" lastname "Parker" male True} 5 | # Mixing, using some extra optional colons 6 | person:{name:"Peter" lastname:"Parker",male True} 7 | -------------------------------------------------------------------------------- /test/string-escapes.hi: -------------------------------------------------------------------------------- 1 | hex-escape: "\20" # This is actually a space 2 | unicode: "\e2\8c\a8" # UTF-8 keyboard symbol 3 | hex-digits: "\00\12\34\56\78\9a\bc\de\fA\BC\DE\F0" 4 | tab: "\t" 5 | new-line: "\n" 6 | carriage-return: "\r" 7 | double-quote: "\"" 8 | backslash: "\\" 9 | -------------------------------------------------------------------------------- /test/dict-rehash.hi: -------------------------------------------------------------------------------- 1 | This: 1 2 | is: 2 3 | a: 3 4 | file: 4 5 | with: 5 6 | many: 6 7 | keys: 7 8 | in: 8 9 | order: 9 10 | to: 10 11 | trigger: 11 12 | rehash: 13 13 | of: 14 14 | the: 15 15 | underlying: 16 16 | dictionary: 17 17 | storing: 18 18 | values: 19 19 | once: 20 20 | we: 21 21 | hit: 22 22 | hashtable: 23 23 | rehashing: 24 24 | ratio: 25 25 | -------------------------------------------------------------------------------- /test/integers.hi: -------------------------------------------------------------------------------- 1 | # Valid numbers 2 | decimal { 3 | zero: 0 4 | minus-zero: -0 5 | the-answer-to-everything: 42 6 | I: -1 7 | fibonacci [ 1 2 3 5 8 13 21 ] 8 | } 9 | octal { 10 | mode: 0755 # Typical usage for file permission bits. 11 | negative: -034 12 | } 13 | hex { 14 | num-chars [ 0x1234 0x5678 0x9abc 0xdef0 ] 15 | negative: -0x8 16 | caps: 0xCAFE 17 | mixed-caps: 0xCaFe 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-22.04 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | persist-credentials: false 24 | - name: Compile 25 | run: make -j$(nproc) 26 | - name: Test 27 | run: make hipack-check 28 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. hipack-c documentation master file, created by 2 | sphinx-quickstart on Thu Dec 17 20:03:40 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to hipack-c's documentation! 7 | ==================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | quickstart 15 | apiref 16 | 17 | 18 | Indices and tables 19 | ================== 20 | 21 | * :ref:`genindex` 22 | * :ref:`modindex` 23 | * :ref:`search` 24 | 25 | -------------------------------------------------------------------------------- /test/lists.hi: -------------------------------------------------------------------------------- 1 | empty: [ ] 2 | empty-no-colon [ ] 3 | empty-no-separator[ ] 4 | empty-no-spaces: [] 5 | empty-no-spaces-no-colon [] 6 | empty-no-spaces-no-separator[] 7 | empty-no-space-after-colon:[] 8 | single-item: [True] 9 | single-item-dangling-comma: [True,] 10 | multiple-items: [True, True] 11 | multiple-items-no-commas: [True False] 12 | multiple-items-some-commas: [True, False True] 13 | multiple-items-dangling-comma: [True, True,] 14 | nested-empty: [[]] 15 | nested-empties: [[], [], []] 16 | mixed-values: [{}, True, "a string"] 17 | multiline: [ 18 | True, 19 | False, 20 | True 21 | ] 22 | multiline-dangling-comma: [ 23 | True, 24 | False, 25 | True, 26 | ] 27 | multiline-no-commas: [ 28 | True 29 | False 30 | False 31 | ] 32 | -------------------------------------------------------------------------------- /test/dicts.hi: -------------------------------------------------------------------------------- 1 | empty: { } 2 | empty-no-colon { } 3 | empty-no-spaces: {} 4 | empty-no-spaces-no-colon {} 5 | empty-no-space-after-colon:{} 6 | single-item: { item: True } 7 | single-item-dangling-comma: { item: True, } 8 | multiple-items: { item1: True, Item2: True } 9 | multiple-items-no-commas: { item1: True item2: True } 10 | multiple-items-some-commas: { item1: True, item2: True item3: True } 11 | nested-empty: { nested: {} } 12 | nested-empties: { nested1: {}, nested2 {} } 13 | mixed-values: { list: [], nested: {}, bool: True } 14 | multiline: { 15 | list: [], 16 | nested: {}, 17 | bool: True 18 | } 19 | multiline-dangling-comma: { 20 | list: [], 21 | nested: {}, 22 | bool: True, 23 | } 24 | multiline-no-commas: { 25 | list: [] 26 | nested: {} 27 | bool: True 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Coverage 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-22.04 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | persist-credentials: false 24 | - name: Install Tools 25 | run: pip install cpp-coveralls 26 | - name: Gather Coverage 27 | run: > 28 | make -j$(nproc) 29 | CC="${CC:-gcc} -fprofile-arcs -ftest-coverage -O0 -g" 30 | hipack-check 31 | - name: Submit 32 | uses: threeal/gcovr-action@v1 33 | with: 34 | coveralls-send: true 35 | excludes: | 36 | fpconv 37 | test 38 | tools 39 | -------------------------------------------------------------------------------- /hipack-list.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hipack-list.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "hipack.h" 9 | #include 10 | 11 | 12 | static hipack_list_t s_empty_list = { .size = 0 }; 13 | 14 | 15 | hipack_list_t* 16 | hipack_list_new (uint32_t size) 17 | { 18 | hipack_list_t *list; 19 | 20 | if (size) { 21 | list = hipack_alloc_array_extra (NULL, size, 22 | sizeof (hipack_value_t), 23 | sizeof (hipack_list_t)); 24 | list->size = size; 25 | } else { 26 | list = &s_empty_list; 27 | } 28 | return list; 29 | } 30 | 31 | 32 | void 33 | hipack_list_free (hipack_list_t *list) 34 | { 35 | if (list && list != &s_empty_list) { 36 | for (uint32_t i = 0; i < list->size; i++) 37 | hipack_value_free (&list->data[i]); 38 | hipack_alloc_free (list); 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hipack-c 2 | ======== 3 | 4 | [![Build](https://github.com/aperezdc/hipack-c/actions/workflows/build.yml/badge.svg?event=push)](https://github.com/aperezdc/hipack-c/actions/workflows/build.yml) 5 | [![Code coverage](https://img.shields.io/coveralls/aperezdc/hipack-c/master.svg?style=flat)](https://coveralls.io/r/aperezdc/hipack-c?branch=master) 6 | [![Documentation Status](https://readthedocs.org/projects/hipack-c/badge/?version=latest)](https://hipack-c.readthedocs.org/en/latest) 7 | 8 | Implementation of the [HiPack](http://hipack.org) serialization format C. 9 | Features: 10 | 11 | * Reference implementation of the [current stable HiPack 12 | specification](https://github.com/aperezdc/hipack/blob/gh-pages/spec.rst), 13 | plus the following HEPs: 14 | - [HEP-1: Value Annotations](https://github.com/aperezdc/hipack/blob/gh-pages/heps/hep-001.rst) 15 | * Straightforward implementation. 16 | * Written in portable C99. 17 | * Minimal dependencies. 18 | 19 | 20 | Licensing 21 | --------- 22 | 23 | `hipack-c` is distributed under the terms of the [MIT 24 | license](http://opensource.org/licenses/mit). 25 | 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [Unreleased] 6 | 7 | ## [v0.1.2] - 2015-12-27 8 | ### Added 9 | - [API reference documentation](http://hipack-c.readthedocs.org/en/latest/apiref.html) and [Quickstart guide](http://hipack-c.readthedocs.org/en/latest/quickstart.html). 10 | - Support for [HEP-1 Value Annotations](https://github.com/aperezdc/hipack/blob/gh-pages/heps/hep-001.rst). 11 | - Utility functions to create `hipack_value_t` values. 12 | - Improved test suite with additional test cases. 13 | - Improve Travis-CI build times by using containerized builds and preserving the contents of `~/.cache/pip` between builds. 14 | 15 | ### Fixed 16 | - Do not allow exponent letter (`e` or `E`) in octal numbers. 17 | - Fixed checkout of `fpconv` submodule to avoid main targets being always rebuilt. 18 | 19 | ## v0.1.0 20 | - First release. 21 | 22 | [Unreleased]: https://github.com/aperezdc/hipack-c/compare/v0.1.2...HEAD 23 | [v0.1.2]: https://github.com/aperezdc/hipack-c/compare/v0.1.0...v0.1.2 24 | -------------------------------------------------------------------------------- /tools/extract-docs.awk: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | OBJ_MAP["f"] = "function"; 3 | OBJ_MAP["m"] = "member"; 4 | OBJ_MAP["M"] = "macro"; 5 | OBJ_MAP["t"] = "type"; 6 | OBJ_MAP["v"] = "var"; 7 | in_doc = 0; 8 | indent = 0; 9 | } 10 | 11 | /^[[:space:]]*\/\*[\*\~][fmMtv]?/ { 12 | if (!in_doc) { 13 | in_doc = 1; 14 | } 15 | 16 | objtype = substr($1, length($1)); 17 | if (objtype in OBJ_MAP) { 18 | objtype = OBJ_MAP[objtype]; 19 | indent = (index($0, "/") - 1); 20 | if (indent) { 21 | indent = indent / 4; 22 | } 23 | indent++; 24 | } else { 25 | indent = 0; 26 | objtype = ""; 27 | } 28 | 29 | if (length(objtype)) { 30 | for (i = 1; i < indent; i++) printf(" "); 31 | printf(".. c:%s::", objtype); 32 | for (i = 2; i <= NF; i++) printf(" %s", $(i)); 33 | } 34 | printf("\n\n"); 35 | next; 36 | } 37 | 38 | /^[[:space:]]*\*\// { 39 | in_doc = 0; 40 | printf("\n"); 41 | next; 42 | } 43 | 44 | /^[[:space:]]*\*[[:space:]]*$/ { 45 | if (in_doc) print ""; 46 | next; 47 | } 48 | 49 | { 50 | if (in_doc != 0) { 51 | gsub(/^[[:space:]]*\*[[:space:]]/, ""); 52 | if (indent) { 53 | for (i = 0; i < indent; i++) printf(" "); 54 | } 55 | print; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tools/hipack-parse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hipack-parse.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "../hipack.h" 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | int 15 | main (int argc, const char *argv[]) 16 | { 17 | if (argc != 2) { 18 | fprintf (stderr, "Usage: %s PATH\n", argv[0]); 19 | return EXIT_FAILURE; 20 | } 21 | 22 | FILE *fp = fopen (argv[1], "rb"); 23 | if (!fp) { 24 | fprintf (stderr, "%s: Cannot open '%s' (%s)\n", 25 | argv[0], argv[1], strerror (errno)); 26 | return EXIT_FAILURE; 27 | } 28 | 29 | int retcode = EXIT_SUCCESS; 30 | hipack_reader_t reader = { 31 | .getchar = hipack_stdio_getchar, 32 | .getchar_data = fp, 33 | }; 34 | hipack_dict_t *message = hipack_read (&reader); 35 | if (!message) { 36 | assert (reader.error); 37 | fprintf (stderr, "line %u, column %u: %s\n", 38 | reader.error_line, reader.error_column, 39 | (reader.error == HIPACK_READ_ERROR) 40 | ? strerror (errno) : reader.error); 41 | retcode = EXIT_FAILURE; 42 | } 43 | hipack_dict_free (message); 44 | 45 | fclose (fp); 46 | return retcode; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /test/hep-001.hi: -------------------------------------------------------------------------------- 1 | single-annotations { 2 | integer-value :annot 0 3 | float-value :annot 1.1 4 | string-value :annot "Hello" 5 | bool-value :annot True 6 | list-value :annot [] 7 | dict-value :annot {} 8 | } 9 | multiple-annotations { 10 | integer-value :annot1 :annot2 0 11 | float-value :annot1 :annot2 1.1 12 | string-value :annot1 :annot2 :annot3 "Three" 13 | bool-value :has :two :annots False 14 | list-value :annot1 :annot2 [] 15 | dict-value :annot1 :annot2 :annot3 :annot4 {} 16 | } 17 | annotation-delimiter-variations { 18 | double-colon-no-space::annot 0 19 | double-colon-space: :annot 0 20 | multi-double-colon-no-space::annot1:annot2 0 21 | multi-double-colon-space: :anot1:annot2 0 22 | multi-spaced-double-colon-nospace::annot1 :annot2 0 23 | multi-spaced-double-colon-space: :annot1 :annot2 0 24 | } 25 | intrinsic-annotations { 26 | integer-value :.int 0 27 | float-value :.float 1.1 28 | string-value :.string "Hello" 29 | bool-value :.bool True 30 | list-value :.list [] 31 | dict-value :.dict {} 32 | } 33 | list-item-annotations { 34 | spaced-items [:annot1 "item1" :annot2 "item2"] 35 | comma-items [:annot1 "item1",:annot2 "item2"] 36 | commaspaced-items [:annot1 "item1", :annot2 "item2"] 37 | leading-space [ :annot1 "item1" :annot2 "item2"] 38 | trailing-space [:annot1 "item1" :annot2 "item2" ] 39 | leadtrail-space [ :annot1 "item1" :annot2 "item2" ] 40 | } 41 | -------------------------------------------------------------------------------- /hipack-alloc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hipack-alloc.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "hipack.h" 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | void* (*hipack_alloc) (void*, size_t) = hipack_alloc_stdlib; 15 | 16 | 17 | void* 18 | hipack_alloc_stdlib (void *optr, size_t size) 19 | { 20 | if (size) { 21 | optr = optr ? realloc (optr, size) : malloc (size); 22 | if (!optr) { 23 | fprintf (stderr, "aborted: %s\n", strerror (errno)); 24 | fflush (stderr); 25 | abort (); 26 | } 27 | } else { 28 | free (optr); 29 | optr = NULL; 30 | } 31 | return optr; 32 | } 33 | 34 | 35 | /* 36 | * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX 37 | * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW 38 | */ 39 | #define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) 40 | 41 | void* 42 | hipack_alloc_array_extra (void *optr, size_t nmemb, size_t size, size_t extra) 43 | { 44 | if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && 45 | nmemb > 0 && SIZE_MAX / nmemb < size) { 46 | fprintf (stderr, "aborted: %s\n", strerror (ENOMEM)); 47 | fflush (stderr); 48 | abort (); 49 | } 50 | return (*hipack_alloc) (optr, size * nmemb + extra); 51 | } 52 | 53 | 54 | -------------------------------------------------------------------------------- /tools/strtonum.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: strtonum.c,v 1.7 2013/04/17 18:40:58 tedu Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2004 Ted Unangst and Todd Miller 5 | * All rights reserved. 6 | * 7 | * Permission to use, copy, modify, and distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #define INVALID 1 25 | #define TOOSMALL 2 26 | #define TOOLARGE 3 27 | 28 | long long 29 | strtonum(const char *numstr, long long minval, long long maxval, 30 | const char **errstrp) 31 | { 32 | long long ll = 0; 33 | int error = 0; 34 | char *ep; 35 | struct errval { 36 | const char *errstr; 37 | int err; 38 | } ev[4] = { 39 | { NULL, 0 }, 40 | { "invalid", EINVAL }, 41 | { "too small", ERANGE }, 42 | { "too large", ERANGE }, 43 | }; 44 | 45 | ev[0].err = errno; 46 | errno = 0; 47 | if (minval > maxval) { 48 | error = INVALID; 49 | } else { 50 | ll = strtoll(numstr, &ep, 10); 51 | if (numstr == ep || *ep != '\0') 52 | error = INVALID; 53 | else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) 54 | error = TOOSMALL; 55 | else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) 56 | error = TOOLARGE; 57 | } 58 | if (errstrp != NULL) 59 | *errstrp = ev[error].errstr; 60 | errno = ev[error].err; 61 | if (error) 62 | ll = 0; 63 | 64 | return (ll); 65 | } 66 | -------------------------------------------------------------------------------- /hipack-misc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hipack-misc.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "hipack.h" 9 | #include 10 | 11 | 12 | bool 13 | hipack_value_equal (const hipack_value_t *a, 14 | const hipack_value_t *b) 15 | { 16 | assert (a); 17 | assert (b); 18 | 19 | if (a->type != b->type) 20 | return false; 21 | 22 | switch (a->type) { 23 | case HIPACK_INTEGER: 24 | return a->v_integer == b->v_integer; 25 | case HIPACK_BOOL: 26 | return a->v_bool == b->v_bool; 27 | case HIPACK_FLOAT: 28 | return fabs(b->v_float - a->v_float) < 1e-15; 29 | case HIPACK_STRING: 30 | return hipack_string_equal (a->v_string, b->v_string); 31 | case HIPACK_LIST: 32 | return hipack_list_equal (a->v_list, b->v_list); 33 | case HIPACK_DICT: 34 | return hipack_dict_equal (a->v_dict, b->v_dict); 35 | default: 36 | assert (false); // Unreachable. 37 | } 38 | } 39 | 40 | 41 | bool 42 | hipack_list_equal (const hipack_list_t *a, 43 | const hipack_list_t *b) 44 | { 45 | assert (a); 46 | assert (b); 47 | 48 | if (a->size != b->size) 49 | return false; 50 | 51 | for (uint32_t i = 0; i < a->size; i++) 52 | if (!hipack_value_equal (&a->data[i], &b->data[i])) 53 | return false; 54 | 55 | return true; 56 | } 57 | 58 | 59 | bool 60 | hipack_dict_equal (const hipack_dict_t *a, 61 | const hipack_dict_t *b) 62 | { 63 | assert (a); 64 | assert (b); 65 | 66 | if (a->count != b->count) 67 | return false; 68 | 69 | const hipack_string_t *key; 70 | hipack_value_t *a_value; 71 | HIPACK_DICT_FOREACH (a, key, a_value) { 72 | hipack_value_t *b_value = hipack_dict_get (b, key); 73 | if (!b_value || !hipack_value_equal (a_value, b_value)) { 74 | return false; 75 | } 76 | } 77 | 78 | return true; 79 | } 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /hipack-string.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hipack-string.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "hipack.h" 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | static hipack_string_t s_empty_string = { .size = 0 }; 15 | 16 | 17 | hipack_string_t* 18 | hipack_string_new_from_string (const char *str) 19 | { 20 | assert (str); 21 | return hipack_string_new_from_lstring (str, strlen (str)); 22 | } 23 | 24 | 25 | hipack_string_t* 26 | hipack_string_new_from_lstring (const char *str, uint32_t len) 27 | { 28 | assert (str); 29 | if (len > 0) { 30 | hipack_string_t *hstr = hipack_alloc_array_extra (NULL, 31 | len, sizeof (uint8_t), sizeof (hipack_string_t)); 32 | memcpy (hstr->data, str, len); 33 | hstr->size = len; 34 | return hstr; 35 | } else { 36 | return &s_empty_string; 37 | } 38 | } 39 | 40 | 41 | hipack_string_t* 42 | hipack_string_copy (const hipack_string_t *hstr) 43 | { 44 | assert (hstr); 45 | 46 | if (hstr == &s_empty_string) 47 | return &s_empty_string; 48 | 49 | return hipack_string_new_from_lstring ((const char*) hstr->data, 50 | hstr->size); 51 | } 52 | 53 | 54 | void 55 | hipack_string_free (hipack_string_t *hstr) 56 | { 57 | if (hstr && hstr != &s_empty_string) { 58 | hipack_alloc_free (hstr); 59 | } 60 | } 61 | 62 | 63 | uint32_t 64 | hipack_string_hash (const hipack_string_t *hstr) 65 | { 66 | assert (hstr); 67 | 68 | uint32_t ret = 0; 69 | uint32_t ctr = 0; 70 | 71 | for (uint32_t i = 0; i < hstr->size; i++) { 72 | ret ^= hstr->data[i] << ctr; 73 | ctr = (ctr + 1) % sizeof (void*); 74 | } 75 | 76 | return ret; 77 | } 78 | 79 | 80 | bool 81 | hipack_string_equal (const hipack_string_t *hstr1, 82 | const hipack_string_t *hstr2) 83 | { 84 | assert (hstr1); 85 | assert (hstr2); 86 | 87 | return (hstr1->size == hstr2->size) && 88 | memcmp (hstr1->data, hstr2->data, hstr1->size) == 0; 89 | } 90 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -std=c99 2 | hipack_PATH ?= . 3 | hipack_OBJS = ${hipack_PATH}/hipack-parser.o \ 4 | ${hipack_PATH}/hipack-writer.o \ 5 | ${hipack_PATH}/hipack-string.o \ 6 | ${hipack_PATH}/hipack-alloc.o \ 7 | ${hipack_PATH}/hipack-list.o \ 8 | ${hipack_PATH}/hipack-dict.o \ 9 | ${hipack_PATH}/hipack-misc.o 10 | hipack = ${hipack_PATH}/libhipack.a 11 | 12 | hipack: ${hipack} 13 | hipack-clean: 14 | ${RM} ${hipack} ${hipack_OBJS} 15 | ${RM} ${hipack_PATH}/tools/*.o \ 16 | ${hipack_PATH}/tools/hipack-cat \ 17 | ${hipack_PATH}/tools/hipack-get \ 18 | ${hipack_PATH}/tools/hipack-parse \ 19 | ${hipack_PATH}/tools/hipack-roundtrip \ 20 | ${hipack_PATH}/tools/hipack-test-api 21 | 22 | ${hipack_OBJS}: ${hipack_PATH}/hipack.h 23 | ${hipack}: ${hipack_OBJS} 24 | ${AR} rc ${hipack} ${hipack_OBJS} 25 | 26 | hipack-tools: \ 27 | ${hipack_PATH}/tools/hipack-cat \ 28 | ${hipack_PATH}/tools/hipack-get \ 29 | ${hipack_PATH}/tools/hipack-parse \ 30 | ${hipack_PATH}/tools/hipack-roundtrip \ 31 | ${hipack_PATH}/tools/hipack-test-api 32 | 33 | ${hipack_PATH}/tools/hipack-cat: \ 34 | ${hipack_PATH}/tools/hipack-cat.o ${hipack} 35 | 36 | ${hipack_PATH}/tools/hipack-get: \ 37 | ${hipack_PATH}/tools/hipack-get.o ${hipack} 38 | 39 | ${hipack_PATH}/tools/hipack-parse: \ 40 | ${hipack_PATH}/tools/hipack-parse.o ${hipack} 41 | 42 | ${hipack_PATH}/tools/hipack-roundtrip: \ 43 | ${hipack_PATH}/tools/hipack-roundtrip.o ${hipack} 44 | 45 | ${hipack_PATH}/tools/hipack-test-api: \ 46 | ${hipack_PATH}/tools/hipack-test-api.o ${hipack} 47 | 48 | hipack-check: hipack-tools 49 | @${hipack_PATH}/tools/hipack-test-api 50 | @bash --norc ${hipack_PATH}/tools/run-tests 51 | 52 | ${hipack_PATH}/hipack-writer.o: ${hipack_PATH}/fpconv/src/fpconv.c 53 | ${hipack_PATH}/fpconv/src/fpconv.c: ${hipack_PATH}/.gitmodules 54 | cd ${hipack_PATH} && git submodule init fpconv 55 | cd ${hipack_PATH} && git submodule update fpconv 56 | touch $@ 57 | 58 | ${hipack_PATH}/doc/apiref.rst: ${hipack_PATH}/hipack.h ${hipack_PATH}/tools/extract-docs.awk 59 | awk -f ${hipack_PATH}/tools/extract-docs.awk $< > $@ 60 | 61 | doc: ${hipack_PATH}/doc/apiref.rst 62 | ${MAKE} -C ${hipack_PATH}/doc html 63 | 64 | .PHONY: hipack hipack-objs hipack-tools hipack-check hipack-clean doc 65 | -------------------------------------------------------------------------------- /tools/hipack-cat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hipack-cat.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #define _POSIX_C_SOURCE 2 9 | #include "../hipack.h" 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | static void 16 | usage (const char *argv0, int code) 17 | { 18 | FILE *output = (code == EXIT_FAILURE) ? stderr : stdout; 19 | fprintf (output, "Usage: %s [-c] PATH\n", argv0); 20 | exit (code); 21 | } 22 | 23 | 24 | int 25 | main (int argc, char *argv[]) 26 | { 27 | bool compact = false; 28 | int opt; 29 | 30 | while ((opt = getopt (argc, argv, "hc")) != -1) { 31 | switch (opt) { 32 | case 'c': 33 | compact = true; 34 | break; 35 | case 'h': 36 | usage (argv[0], EXIT_SUCCESS); 37 | break; 38 | default: 39 | usage (argv[0], EXIT_FAILURE); 40 | } 41 | } 42 | 43 | if (optind >= argc) { 44 | usage (argv[0], EXIT_FAILURE); 45 | } 46 | 47 | FILE *fp = fopen (argv[optind], "rb"); 48 | if (!fp) { 49 | fprintf (stderr, "%s: Cannot open '%s' (%s)\n", 50 | argv[0], argv[optind], strerror (errno)); 51 | return EXIT_FAILURE; 52 | } 53 | 54 | int retcode = EXIT_SUCCESS; 55 | hipack_reader_t reader = { 56 | .getchar = hipack_stdio_getchar, 57 | .getchar_data = fp, 58 | }; 59 | hipack_dict_t *message = hipack_read (&reader); 60 | if (!message) { 61 | assert (reader.error); 62 | fprintf (stderr, "line %u, column %u: %s\n", 63 | reader.error_line, reader.error_column, 64 | (reader.error == HIPACK_READ_ERROR) 65 | ? strerror (errno) : reader.error); 66 | retcode = EXIT_FAILURE; 67 | goto cleanup; 68 | } 69 | 70 | hipack_writer_t writer = { 71 | .putchar = hipack_stdio_putchar, 72 | .putchar_data = stdout, 73 | .indent = compact ? HIPACK_WRITER_COMPACT : HIPACK_WRITER_INDENTED, 74 | }; 75 | hipack_write (&writer, message); 76 | 77 | cleanup: 78 | fclose (fp); 79 | hipack_dict_free (message); 80 | return retcode; 81 | } 82 | 83 | -------------------------------------------------------------------------------- /tools/hipack-roundtrip.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hipack-roundtrip.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #define _POSIX_C_SOURCE 2 9 | #include "../hipack.h" 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | static void 16 | usage (const char *argv0, int code) 17 | { 18 | FILE *output = (code == EXIT_FAILURE) ? stderr : stdout; 19 | fprintf (output, "Usage: %s [-c] PATH\n", argv0); 20 | exit (code); 21 | } 22 | 23 | 24 | int 25 | main (int argc, char *argv[]) 26 | { 27 | bool compact = false; 28 | int opt; 29 | 30 | while ((opt = getopt (argc, argv, "hc")) != -1) { 31 | switch (opt) { 32 | case 'c': 33 | compact = true; 34 | break; 35 | case 'h': 36 | usage (argv[0], EXIT_SUCCESS); 37 | break; 38 | default: 39 | usage (argv[0], EXIT_FAILURE); 40 | } 41 | } 42 | 43 | if (optind >= argc) { 44 | usage (argv[0], EXIT_FAILURE); 45 | } 46 | 47 | FILE *fp = fopen (argv[optind], "rb"); 48 | if (!fp) { 49 | fprintf (stderr, "%s: Cannot open '%s' (%s)\n", 50 | argv[0], argv[optind], strerror (errno)); 51 | return EXIT_FAILURE; 52 | } 53 | 54 | int retcode = EXIT_SUCCESS; 55 | hipack_reader_t reader = { 56 | .getchar = hipack_stdio_getchar, 57 | .getchar_data = fp, 58 | }; 59 | hipack_dict_t *message1 = hipack_read (&reader); 60 | hipack_dict_t *message2 = NULL; 61 | if (!message1) { 62 | assert (reader.error); 63 | fprintf (stderr, "[pass 1] line %u, column %u: %s\n", 64 | reader.error_line, reader.error_column, 65 | (reader.error == HIPACK_READ_ERROR) 66 | ? strerror (errno) : reader.error); 67 | retcode = EXIT_FAILURE; 68 | goto cleanup; 69 | } 70 | 71 | fclose (fp); 72 | 73 | /* Create a temporary file and write the message to it. */ 74 | fp = tmpfile (); 75 | hipack_writer_t writer = { 76 | .putchar = hipack_stdio_putchar, 77 | .putchar_data = fp, 78 | .indent = compact ? HIPACK_WRITER_COMPACT : HIPACK_WRITER_INDENTED, 79 | }; 80 | 81 | if (hipack_write (&writer, message1)) { 82 | fprintf (stderr, "write error: %s\n", strerror (errno)); 83 | retcode = EXIT_FAILURE; 84 | goto cleanup; 85 | } 86 | 87 | /* Re-read the dumped file. */ 88 | rewind (fp); 89 | reader = (hipack_reader_t) { 90 | .getchar = hipack_stdio_getchar, 91 | .getchar_data = fp, 92 | }; 93 | message2 = hipack_read (&reader); 94 | if (!message2) { 95 | assert (reader.error); 96 | fprintf (stderr, "[pass 2] line %u, column %u: %s\n", 97 | reader.error_line, reader.error_column, 98 | (reader.error == HIPACK_READ_ERROR) 99 | ? strerror (errno) : reader.error); 100 | retcode = EXIT_FAILURE; 101 | goto cleanup; 102 | } 103 | 104 | if (!hipack_dict_equal (message1, message2)) { 105 | fprintf (stderr, "Messages are different\n"); 106 | retcode = EXIT_FAILURE; 107 | } 108 | 109 | cleanup: 110 | hipack_dict_free (message1); 111 | hipack_dict_free (message2); 112 | if (fp) fclose (fp); 113 | return retcode; 114 | } 115 | 116 | -------------------------------------------------------------------------------- /tools/run-tests: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -e 3 | 4 | abspath () { 5 | pushd "$1" > /dev/null 6 | local path=$(pwd) 7 | popd > /dev/null 8 | echo "${path}" 9 | } 10 | 11 | 12 | declare -r srcdir=$(abspath "${0%/*}/../") 13 | declare -a tests=( ) 14 | declare -a fails=( ) 15 | declare -a okays=( ) 16 | declare -i current=0 17 | 18 | for hi in "${srcdir}/test"/*.hi ; do 19 | name=${hi##*/} 20 | name=${name%.hi} 21 | tests=( "${tests[@]}" "${name}" ) 22 | if [[ ${name} != fail-* ]] ; then 23 | tests=( "${tests[@]}" "roundtrip/${name}" ) 24 | fi 25 | done 26 | 27 | declare -ir total=${#tests[@]} 28 | 29 | : ${TEST_TIMEOUT:=10} 30 | GOT_TIMEOUT=false 31 | if timeout --version 2> /dev/null | grep -qF 'GNU coreutils' 32 | then 33 | function with_timeout () { 34 | local code=0 35 | GOT_TIMEOUT=false 36 | timeout ${TEST_TIMEOUT} "$@" 37 | code=$? 38 | if [[ ${code} -eq 124 ]] ; then 39 | GOT_TIMEOUT=true 40 | fi 41 | return ${code} 42 | } 43 | else 44 | function with_timeout () { 45 | "$@" 46 | } 47 | fi 48 | 49 | LAST_CMD='' 50 | function run_cmd () { 51 | LAST_CMD="$*" 52 | with_timeout "$@" 53 | } 54 | 55 | if [[ -z ${DUMB_CONSOLE} || ${DUMB_CONSOLE} -eq 0 ]] ; then 56 | echo 57 | report_progress () { 58 | printf '[%%%3i|+%2i|-%2i] %s\n' \ 59 | $(( 100 * ++current / total )) ${#okays[@]} ${#fails[@]} "$1" 60 | } 61 | else 62 | report_progress () { 63 | local status='ok' 64 | if ! $2 ; then 65 | if ${GOT_TIMEOUT} ; then 66 | status='timeout' 67 | else 68 | status='failed' 69 | fi 70 | fi 71 | printf '[%i/%i] %s (%s)\n' \ $(( ++current )) ${total} "$1" ${status} 72 | } 73 | fi 74 | 75 | 76 | for name in "${tests[@]}" ; do 77 | if [[ ${name} = fail-* ]] ; then 78 | expect_fail=true 79 | else 80 | expect_fail=false 81 | fi 82 | 83 | if [[ ${name} = roundtrip/* ]] ; then 84 | if run_cmd \ 85 | "${srcdir}/tools/hipack-roundtrip" \ 86 | "${srcdir}/test/${name#roundtrip/}.hi" && \ 87 | "${srcdir}/tools/hipack-roundtrip" -c \ 88 | "${srcdir}/test/${name#roundtrip/}.hi" 89 | then 90 | passed=true 91 | else 92 | passed=false 93 | fi &> "/tmp/hipack-$$-${name//\//-}.err" 94 | else 95 | if run_cmd \ 96 | "${srcdir}/tools/hipack-parse" \ 97 | "${srcdir}/test/${name}.hi" \ 98 | &> "/tmp/hipack-$$-${name}.err" 99 | then 100 | if ${expect_fail} ; then passed=false ; else passed=true ; fi 101 | else 102 | if ${expect_fail} ; then passed=true ; else passed=false ; fi 103 | fi 104 | 105 | diff_text='' 106 | if ${passed} && [[ -r ${srcdir}/test/${name}.err ]] ; then 107 | # Show diff 108 | if ! diff -q \ 109 | "${srcdir}/test/${name}.err" \ 110 | "/tmp/hipack-$$-${name}.err" \ 111 | &> /dev/null 112 | then 113 | # Diffs don't match, show 'em 114 | diff_text=$(diff -ud \ 115 | "${srcdir}/test/${name}.err" \ 116 | "/tmp/hipack-$$-${name}.err" \ 117 | || true) 118 | passed=false 119 | fi 120 | fi 121 | fi 122 | 123 | report_progress "${name}" ${passed} 124 | 125 | if ${passed} ; then 126 | okays=( "${okays[@]}" "${name}" ) 127 | else 128 | fails=( "${fails[@]}" "${name}" ) 129 | if [[ -n ${diff_text} ]] ; then 130 | echo "${diff_text}" 131 | elif [[ -s /tmp/hipack-$$-${name//\//-}.err ]] ; then 132 | cat "/tmp/hipack-$$-${name//\//-}.err" 133 | fi 134 | echo "Command: ${LAST_CMD}" 135 | echo 136 | echo 137 | fi 138 | rm -f "/tmp/hipack-$$-${name//\//-}.err" 139 | done 140 | 141 | echo "Total: ${total}" 142 | echo "Passed: ${#okays[@]} ($(( 100 * ${#okays[@]} / total ))%)" 143 | if [[ ${#fails[@]} -gt 0 ]] ; then 144 | echo "Failed: ${#fails[@]} ($(( 100 * ${#fails[@]} / total ))%)" 145 | for name in "${fails[@]}" ; do 146 | echo " ${name}" 147 | done 148 | fi 149 | [[ ${#fails[@]} -eq 0 ]] || exit 1 150 | -------------------------------------------------------------------------------- /tools/hipack-get.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hipack-get.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "../hipack.h" 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | #if !defined(__OpenBSD__) 15 | /* Make sure that strtonum() is defined as "static". */ 16 | static long long strtonum (const char*, long long, long long, const char**); 17 | #include "strtonum.c" 18 | #endif /* !__OpenBSD__ */ 19 | 20 | 21 | int 22 | main (int argc, const char *argv[]) 23 | { 24 | if (argc < 2) { 25 | fprintf (stderr, "Usage: %s <-|PATH> [key...]\n", argv[0]); 26 | return EXIT_FAILURE; 27 | } 28 | 29 | bool use_stdin = argv[1][0] == '-' && argv[1][1] == '\0'; 30 | FILE *fp = use_stdin ? stdin : fopen (argv[1], "rb"); 31 | if (!fp) { 32 | fprintf (stderr, "%s: Cannot open '%s' (%s)\n", 33 | argv[0], argv[1], strerror (errno)); 34 | return EXIT_FAILURE; 35 | } 36 | 37 | int retcode = EXIT_SUCCESS; 38 | hipack_reader_t reader = { 39 | .getchar = hipack_stdio_getchar, 40 | .getchar_data = fp, 41 | }; 42 | hipack_dict_t *message = hipack_read (&reader); 43 | if (!message) { 44 | assert (reader.error); 45 | fprintf (stderr, "line %u, column %u: %s\n", 46 | reader.error_line, reader.error_column, 47 | (reader.error == HIPACK_READ_ERROR) 48 | ? strerror (errno) : reader.error); 49 | retcode = EXIT_FAILURE; 50 | } 51 | fclose (fp); 52 | 53 | if (retcode != EXIT_SUCCESS) 54 | return retcode; 55 | 56 | hipack_value_t *value = &((hipack_value_t) { 57 | .type = HIPACK_DICT, 58 | .v_dict = message 59 | }); 60 | 61 | for (unsigned i = 2; i < argc && value; i++) { 62 | switch (hipack_value_type (value)) { 63 | case HIPACK_DICT: { 64 | /* Use argv[i] as dictionary key. */ 65 | hipack_string_t *key = hipack_string_new_from_string (argv[i]); 66 | value = hipack_dict_get (value->v_dict, key); 67 | hipack_string_free (key); 68 | break; 69 | } 70 | case HIPACK_LIST: { 71 | /* Use argv[i] as a list index. */ 72 | const char *error = NULL; 73 | long long index = strtonum (argv[i], 74 | 0, 75 | hipack_list_size (value->v_list), 76 | &error); 77 | if (error) { 78 | fprintf (stderr, "%s: number '%s' is %s\n", 79 | argv[0], argv[i], error); 80 | retcode = EXIT_FAILURE; 81 | goto cleanup_dict; 82 | } 83 | value = HIPACK_LIST_AT (value->v_list, index); 84 | break; 85 | } 86 | default: 87 | fprintf (stderr, "%s: value is not a list or dictionary\n", 88 | argv[0]); 89 | retcode = EXIT_FAILURE; 90 | goto cleanup_dict; 91 | } 92 | } 93 | 94 | if (value) { 95 | hipack_writer_t writer = { 96 | .putchar = hipack_stdio_putchar, 97 | .putchar_data = stdout, 98 | }; 99 | if (hipack_write_value (&writer, value)) { 100 | fprintf (stderr, "%s: write error (%s)\n", 101 | argv[0], strerror (errno)); 102 | retcode = EXIT_FAILURE; 103 | } 104 | putchar ('\n'); 105 | } else { 106 | fprintf (stderr, "%s: No value for the specified key.\n", argv[0]); 107 | retcode = EXIT_FAILURE; 108 | } 109 | 110 | cleanup_dict: 111 | hipack_dict_free (message); 112 | return retcode; 113 | } 114 | -------------------------------------------------------------------------------- /tools/hipack-test-api.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | #include 6 | #include "../hipack.h" 7 | 8 | enum test_result { 9 | TEST_PASS, 10 | TEST_FAIL, 11 | TEST_SKIP, 12 | }; 13 | 14 | static inline enum test_result 15 | check_log_failure(const char *file, unsigned line, const char *func, const char *message) 16 | { 17 | const char *basename = strrchr(file, '/'); 18 | if (!basename) basename = file; 19 | fprintf(stderr, " %s (%s:%u): %s\n", func, file, line, message); 20 | fflush(stderr); 21 | return TEST_FAIL; 22 | } 23 | 24 | #define TEST(name) static enum test_result test_ ## name(void) 25 | #define check(expr) do { \ 26 | if (!(expr)) \ 27 | return check_log_failure(__FILE__, __LINE__, __func__, "check failed: " #expr); \ 28 | } while (false) 29 | 30 | #define cleanup(kind) __attribute__((cleanup(cleanup_ ## kind))) 31 | 32 | static inline void 33 | cleanup_value(hipack_value_t *valp) 34 | { 35 | if (valp) 36 | hipack_value_free(valp); 37 | } 38 | 39 | TEST(value_equal) 40 | { 41 | hipack_value_t a cleanup(value) = hipack_integer(42); 42 | hipack_value_t b cleanup(value) = hipack_integer(32); 43 | hipack_value_t c cleanup(value) = hipack_string(hipack_string_new_from_string("Hi there!")); 44 | hipack_value_t d cleanup(value) = hipack_float(3.14); 45 | hipack_value_t e cleanup(value) = hipack_dict(hipack_dict_new()); 46 | hipack_value_t f cleanup(value) = hipack_list(hipack_list_new(0)); 47 | hipack_value_t g cleanup(value) = hipack_list(hipack_list_new(0)); 48 | 49 | check(hipack_value_equal(&a, &a)); 50 | check(hipack_value_equal(&b, &b)); 51 | check(hipack_value_equal(&c, &c)); 52 | check(hipack_value_equal(&d, &d)); 53 | check(hipack_value_equal(&e, &e)); 54 | check(hipack_value_equal(&f, &f)); 55 | check(hipack_value_equal(&f, &g)); 56 | 57 | check(!hipack_value_equal(&a, &b)); 58 | check(!hipack_value_equal(&a, &c)); 59 | check(!hipack_value_equal(&a, &d)); 60 | check(!hipack_value_equal(&a, &e)); 61 | 62 | return TEST_PASS; 63 | } 64 | 65 | TEST(list_equal) 66 | { 67 | hipack_list_t *l0 = hipack_list_new(0); 68 | hipack_list_t *l1 = hipack_list_new(1); 69 | hipack_list_t *l3 = hipack_list_new(3); 70 | hipack_list_t *lI = hipack_list_new(1); 71 | 72 | hipack_value_t v0 cleanup(value) = hipack_list(l0); 73 | hipack_value_t v1 cleanup(value) = hipack_list(l1); 74 | hipack_value_t v3 cleanup(value) = hipack_list(l3); 75 | hipack_value_t vI cleanup(value) = hipack_list(lI); 76 | 77 | *HIPACK_LIST_AT(l1, 0) = hipack_integer(22); 78 | *HIPACK_LIST_AT(lI, 0) = hipack_integer(22); 79 | 80 | *HIPACK_LIST_AT(l3, 0) = hipack_bool(true); 81 | *HIPACK_LIST_AT(l3, 2) = hipack_bool(true); 82 | *HIPACK_LIST_AT(l3, 2) = hipack_bool(false); 83 | 84 | check(hipack_list_equal(l0, l0)); 85 | check(hipack_list_equal(l3, l3)); 86 | check(hipack_list_equal(l1, l1)); 87 | check(hipack_list_equal(l1, lI)); 88 | check(!hipack_list_equal(l0, l1)); 89 | check(!hipack_list_equal(l0, l3)); 90 | check(!hipack_list_equal(l0, lI)); 91 | 92 | check(hipack_value_equal(&v0, &v0)); 93 | check(!hipack_value_equal(&v0, &v1)); 94 | 95 | return TEST_PASS; 96 | } 97 | 98 | #undef TEST 99 | 100 | static size_t stat_skipped = 0; 101 | static size_t stat_passed = 0; 102 | static size_t stat_failed = 0; 103 | 104 | static void 105 | run_test(const char *name, enum test_result (*run)(void)) 106 | { 107 | fprintf(stderr, "%s...\n", name); 108 | fflush(stderr); 109 | 110 | const char *status; 111 | switch ((*run)()) { 112 | case TEST_PASS: 113 | stat_passed++; 114 | status = "ok"; 115 | break; 116 | case TEST_FAIL: 117 | stat_failed++; 118 | status = "FAILED"; 119 | break; 120 | case TEST_SKIP: 121 | stat_skipped++; 122 | status = "skipped"; 123 | break; 124 | } 125 | 126 | fprintf(stderr, "%s... %s\n", name, status); 127 | fflush(stderr); 128 | } 129 | 130 | int 131 | main(int argc, char *argv[]) 132 | { 133 | static const struct { 134 | const char *name; 135 | enum test_result (*run)(void); 136 | } tests[] = { 137 | #define TEST(name) { #name, test_ ## name } 138 | TEST(value_equal), 139 | TEST(list_equal), 140 | #undef TEST 141 | }; 142 | 143 | if (argc > 1) { 144 | for (size_t j = 1; j < argc; j++) { 145 | for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { 146 | if (!strcmp(argv[j], tests[i].name)) { 147 | run_test(tests[i].name, tests[i].run); 148 | goto next; 149 | } 150 | } 151 | fprintf(stderr, "%s: unknown test '%s'\n", argv[0], argv[j]); 152 | next: 153 | (void)0; 154 | } 155 | } else { 156 | for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) 157 | run_test(tests[i].name, tests[i].run); 158 | } 159 | 160 | const size_t stat_run = stat_passed + stat_failed; 161 | const size_t stat_total = stat_passed + stat_skipped + stat_failed; 162 | 163 | printf("Passed %zu/%zu tests (%.2f%%) - %zu ok, %zu skipped, %zu failed.\n", 164 | stat_run, stat_total, 100.0 * (double) stat_run / stat_total, 165 | stat_passed, stat_skipped, stat_failed); 166 | 167 | return stat_failed ? EXIT_FAILURE : EXIT_SUCCESS; 168 | } 169 | -------------------------------------------------------------------------------- /hipack-dict.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hipack-dict.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "hipack.h" 9 | #include 10 | #include 11 | 12 | 13 | #ifndef HIPACK_DICT_DEFAULT_SIZE 14 | #define HIPACK_DICT_DEFAULT_SIZE 16 15 | #endif /* !HIPACK_DICT_DEFAULT_SIZE */ 16 | 17 | #ifndef HIPACK_DICT_RESIZE_FACTOR 18 | #define HIPACK_DICT_RESIZE_FACTOR 3 19 | #endif /* !HIPACK_DICT_RESIZE_FACTOR */ 20 | 21 | #ifndef HIPACK_DICT_COUNT_TO_SIZE_RATIO 22 | #define HIPACK_DICT_COUNT_TO_SIZE_RATIO 1.2 23 | #endif /* !HIPACK_DICT_COUNT_TO_SIZE_RATIO */ 24 | 25 | 26 | struct hipack_dict_node { 27 | hipack_value_t value; 28 | hipack_string_t *key; 29 | hipack_dict_node_t *next; 30 | hipack_dict_node_t *next_node; 31 | hipack_dict_node_t *prev_node; 32 | }; 33 | 34 | 35 | static inline hipack_dict_node_t* 36 | make_node (hipack_string_t *key, 37 | const hipack_value_t *value) 38 | { 39 | assert (key->size); 40 | hipack_dict_node_t *node = 41 | hipack_alloc_bzero (sizeof (hipack_dict_node_t)); 42 | memcpy (&node->value, value, sizeof (hipack_value_t)); 43 | node->key = key; 44 | return node; 45 | } 46 | 47 | 48 | static inline void 49 | free_node (hipack_dict_node_t *node) 50 | { 51 | hipack_string_free (node->key); 52 | hipack_value_free (&node->value); 53 | hipack_alloc_free (node); 54 | } 55 | 56 | 57 | static void 58 | free_all_nodes (hipack_dict_t *dict) 59 | { 60 | hipack_dict_node_t *next; 61 | 62 | for (hipack_dict_node_t *node = dict->first; node; node = next) { 63 | next = node->next_node; 64 | free_node (node); 65 | } 66 | } 67 | 68 | 69 | static inline void 70 | rehash (hipack_dict_t *dict) 71 | { 72 | for (hipack_dict_node_t *node = dict->first; node; node = node->next_node) 73 | node->next = NULL; 74 | 75 | dict->size *= HIPACK_DICT_RESIZE_FACTOR; 76 | dict->nodes = hipack_alloc_array (dict->nodes, 77 | sizeof (hipack_dict_node_t*), 78 | dict->size); 79 | memset (dict->nodes, 0, sizeof (hipack_dict_node_t*) * dict->size); 80 | 81 | for (hipack_dict_node_t *node = dict->first; node; node = node->next_node) { 82 | uint32_t hash_val = hipack_string_hash (node->key) % dict->size; 83 | hipack_dict_node_t *n = dict->nodes[hash_val]; 84 | if (!n) { 85 | dict->nodes[hash_val] = node; 86 | } else { 87 | for (;; n = n->next) { 88 | if (!n->next) { 89 | n->next = node; 90 | break; 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | 98 | hipack_dict_t* 99 | hipack_dict_new (void) 100 | { 101 | hipack_dict_t *dict = hipack_alloc_bzero (sizeof (hipack_dict_t)); 102 | dict->size = HIPACK_DICT_DEFAULT_SIZE; 103 | dict->nodes = hipack_alloc_array (NULL, 104 | sizeof (hipack_dict_node_t*), 105 | dict->size); 106 | memset (dict->nodes, 0, sizeof (hipack_dict_node_t*) * dict->size); 107 | return dict; 108 | } 109 | 110 | 111 | void 112 | hipack_dict_free (hipack_dict_t *dict) 113 | { 114 | if (dict) { 115 | free_all_nodes (dict); 116 | hipack_alloc_free (dict->nodes); 117 | hipack_alloc_free (dict); 118 | } 119 | } 120 | 121 | 122 | void 123 | hipack_dict_set (hipack_dict_t *dict, 124 | const hipack_string_t *key, 125 | const hipack_value_t *value) 126 | { 127 | hipack_string_t *key_copy = hipack_string_copy (key); 128 | hipack_dict_set_adopt_key (dict, &key_copy, value); 129 | } 130 | 131 | 132 | void hipack_dict_set_adopt_key (hipack_dict_t *dict, 133 | hipack_string_t **key, 134 | const hipack_value_t *value) 135 | { 136 | assert (dict); 137 | assert (key); 138 | assert (*key); 139 | assert (value); 140 | 141 | uint32_t hash_val = hipack_string_hash (*key) % dict->size; 142 | hipack_dict_node_t *node = dict->nodes[hash_val]; 143 | 144 | for (; node; node = node->next) { 145 | if (hipack_string_equal (node->key, *key)) { 146 | hipack_value_free (&node->value); 147 | memcpy (&node->value, value, sizeof (hipack_value_t)); 148 | hipack_string_free (*key); 149 | *key = NULL; 150 | return; 151 | } 152 | } 153 | 154 | node = make_node (*key, value); 155 | *key = NULL; 156 | 157 | if (dict->nodes[hash_val]) { 158 | node->next = dict->nodes[hash_val]; 159 | } 160 | dict->nodes[hash_val] = node; 161 | 162 | node->next_node = dict->first; 163 | if (dict->first) { 164 | dict->first->prev_node = node; 165 | } 166 | dict->first = node; 167 | dict->count++; 168 | 169 | if (dict->count > (dict->size * HIPACK_DICT_COUNT_TO_SIZE_RATIO)) { 170 | rehash (dict); 171 | } 172 | } 173 | 174 | 175 | hipack_value_t* 176 | hipack_dict_get (const hipack_dict_t *dict, 177 | const hipack_string_t *key) 178 | { 179 | assert (dict); 180 | assert (key); 181 | 182 | uint32_t hash_val = hipack_string_hash (key) % dict->size; 183 | hipack_dict_node_t *node = dict->nodes[hash_val]; 184 | 185 | if (node) { 186 | if (hipack_string_equal (key, node->key)) { 187 | return &node->value; 188 | } 189 | 190 | hipack_dict_node_t *last_node = node; 191 | node = node->next; 192 | while (node) { 193 | if (hipack_string_equal (key, node->key)) { 194 | last_node->next = node->next; 195 | node->next = dict->nodes[hash_val]; 196 | dict->nodes[hash_val] = node; 197 | return &node->value; 198 | } 199 | last_node = node; 200 | node = node->next; 201 | } 202 | } 203 | return NULL; 204 | } 205 | 206 | 207 | void 208 | hipack_dict_del (hipack_dict_t *dict, 209 | const hipack_string_t *key) 210 | { 211 | assert (dict); 212 | assert (key); 213 | 214 | uint32_t hash_val = hipack_string_hash (key) % dict->size; 215 | for (hipack_dict_node_t *node = dict->nodes[hash_val]; node; node = node->next) { 216 | if (hipack_string_equal (node->key, key)) { 217 | hipack_dict_node_t *prev_node = node->prev_node; 218 | hipack_dict_node_t *next_node = node->next_node; 219 | 220 | if (prev_node) prev_node->next_node = next_node; 221 | else dict->first = next_node; 222 | if (next_node) next_node->prev_node = prev_node; 223 | 224 | dict->nodes[hash_val] = node->next; 225 | dict->count--; 226 | 227 | free_node (node); 228 | return; 229 | } 230 | } 231 | } 232 | 233 | 234 | hipack_value_t* 235 | hipack_dict_first (const hipack_dict_t *dict, 236 | const hipack_string_t **key) 237 | { 238 | assert (dict); 239 | assert (key); 240 | 241 | if (dict->first) { 242 | *key = dict->first->key; 243 | return (hipack_value_t*) dict->first; 244 | } else { 245 | *key = NULL; 246 | return NULL; 247 | } 248 | } 249 | 250 | 251 | hipack_value_t* 252 | hipack_dict_next (hipack_value_t *value, 253 | const hipack_string_t **key) 254 | { 255 | assert (value); 256 | assert (key); 257 | 258 | hipack_dict_node_t *node = (hipack_dict_node_t*) value; 259 | if (node->next_node) { 260 | *key = node->next_node->key; 261 | return (hipack_value_t*) node->next_node; 262 | } else { 263 | *key = NULL; 264 | return NULL; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/hipack-c.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/hipack-c.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/hipack-c" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/hipack-c" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 1>NUL 2>NUL 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\hipack-c.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\hipack-c.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /doc/quickstart.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Quickstart 3 | ========== 4 | 5 | This guide will walk you through the usage of the ``hipack-c`` C library. 6 | 7 | .. note:: All the examples in this guide need a compiler which supports C99. 8 | 9 | 10 | Reading (deserialization) 11 | ========================= 12 | 13 | Let's start by parsing some data from the file which contains a HiPack message 14 | like the following:: 15 | 16 | title: "Quickstart" 17 | is-documentation? True 18 | 19 | First, we need to include the ``hipack.h`` header in the source of our 20 | program, and then use a :c:func:`hipack_read()` to parse it. In this example 21 | we use the :c:func:`hipack_stdio_getchar()` function included in the library 22 | which is used to read data from a ``FILE*``: 23 | 24 | .. code-block:: c 25 | 26 | #include 27 | #include /* Needed for FILE* streams */ 28 | 29 | static void handle_message (hipack_dict_t *message); 30 | 31 | int main (int argc, const char *argv[]) { 32 | hipack_reader_t reader = { 33 | .getchar_data = fopen ("input.hipack", "rb"), 34 | .getchar = hipack_stdio_getchar, 35 | }; 36 | hipack_dict_t *message = hipack_read (&reader); 37 | fclose (reader.getchar_data); 38 | 39 | handle_message (message); /* Use "message". */ 40 | 41 | hipack_dict_free (message); 42 | return 0; 43 | } 44 | 45 | Once the message was parsed, a dictionary (:c:type:`hipack_dict_t`) is 46 | returned. The :ref:`dict_funcs` can be used to inspect the message. For 47 | example, adding the following as the ``handle_message()`` function 48 | prints the value of the ``title`` element: 49 | 50 | .. code-block:: c 51 | 52 | void handle_message (hipack_dict_t *message) { 53 | hipack_string_t *key = hipack_string_new_from_string ("title"); 54 | hipack_value_t *value = hipack_dict_get (message, key); 55 | 56 | printf ("Title is '%s'\n", hipack_value_get_string (value)); 57 | 58 | hipack_string_free (key); // Free memory used by "key". 59 | } 60 | 61 | Note how objects created by us, like the ``key`` string, have to be freed 62 | by us. In general, the user of the library is responsible for freeing any 63 | objects created by them. On the other hand, objects allocated by the library 64 | are freed by library functions. 65 | 66 | In our example, the memory area which contains the ``value`` is owned by the 67 | message (more precisely: by the dictionary that represents the message), and 68 | the call to :c:func:`hipack_value_get()` returns a pointer to the memory area 69 | owned by the message. The same happens with the call to 70 | :c:func:`hipack_value_get_string()`: it returns a pointer to a memory area 71 | containing the bytes of the string value, which is owned by the 72 | :c:type:`hipack_value_t` structure. This means that we *must not* free the 73 | value structure or the string, because when the whole message is freed —by 74 | calling :c:func:`hipack_dict_free()` at the end of our ``main()`` function— 75 | the memory areas used by the value structure and the string will be freed as 76 | well. 77 | 78 | 79 | Values 80 | ====== 81 | 82 | Objects of type :c:type:`hipack_value_t` represent a single value of those 83 | supported by HiPack: an integer number, a floating point number, a boolean, 84 | a list, or a dictionary. Creating an object if a “basic” value, that is all 85 | except lists and dictionary, can be done using the C99 designated initializer 86 | syntax. For example, to create a floating point number value: 87 | 88 | .. code-block:: c 89 | 90 | hipack_value_t flt_val = { 91 | .type = HIPACK_FLOAT, 92 | .v_float = 4.5e-1 93 | }; 94 | 95 | Alternatively, it is also possible to use provided utility functions to 96 | create values. The example above is equivalent to: 97 | 98 | .. code-block:: c 99 | 100 | hipack_value_t flt_val = hipack_float (4.5e-1); 101 | 102 | When using the C99 initializer syntax directly, the name of the field 103 | containing the value depends on the type. The following table summarizes the 104 | equivalence between types, the field to use in :c:type:`hipack_value_t`, and 105 | the utility function for constructing values: 106 | 107 | ================== ============== ============================== 108 | Type Field Macro 109 | ================== ============== ============================== 110 | ``HIPACK_INTEGER`` ``.v_integer`` :c:func:`hipack_integer()` 111 | ``HIPACK_FLOAT`` ``.v_float`` :c:func:`hipack_float()` 112 | ``HIPACK_BOOL`` ``.v_bool`` :c:func:`hipack_bool()` 113 | ``HIPACK_STRING`` ``.v_string`` :c:func:`hipack_string()` 114 | ``HIPACK_LIST`` ``.v_list`` :c:func:`hipack_list()` 115 | ``HIPACK_DICT`` ``.v_dict`` :c:func:`hipack_dict()` 116 | ================== ============== ============================== 117 | 118 | Note that strings, lists, and dictionaries may have additional memory 119 | allocated. When wrapping them into a :c:type:`hipack_value_t` only the pointer 120 | is stored, and it is still your responsibility to make sure the memory is 121 | freed when the values are not used anymore: 122 | 123 | .. code-block:: c 124 | 125 | hipack_string_t *str = hipack_string_new_from_string ("spam"); 126 | hipack_value_t str_val = hipack_string (str); 127 | assert (str == hipack_value_get_string (&str_val)); // Always true 128 | 129 | For convenience, a :c:func:`hipack_value_free()` function which will ensure 130 | the memory allocated by values referenced by a :c:type:`hipack_value_t` will 131 | be freed properly. This way, one can write code in which the value objects 132 | (:c:type:`hipack_value_t`) are considered to be the owners of the memory which 133 | has been dynamically allocated: 134 | 135 | .. code-block:: c 136 | 137 | // Ownership of the hipack_string_t is passed to "str_val" 138 | hipack_value_t str_val = 139 | hipack_string (hipack_string_new_from_string ("spam")); 140 | // … 141 | hipack_value_free (&str_val); // Free memory. 142 | 143 | This behaviour is particularly handy when assembling complex values: all items 144 | contained in lists and dictionaries can be just added to them, and when 145 | freeing the container, all the values in them will be freed as well. Consider 146 | the following function which creates a HiPack message with a few fields: 147 | 148 | .. code-block:: c 149 | 150 | /* 151 | * Creates a message which would serialize as: 152 | * 153 | * street: "Infinite Loop" 154 | * lat: 37.332 155 | * lon: -122.03 156 | * number: 1 157 | */ 158 | hipack_dict_t* make_address_message (void) { 159 | hipack_dict_t *message = hipack_dict_new (); 160 | hipack_dict_set_adopt_key (message, 161 | hipack_string_new_from_string ("lat"), 162 | hipack_float (37.332)); 163 | hipack_dict_set_adopt_key (message, 164 | hipack_string_new_from_string ("lon"), 165 | hipack_float (-122.03)); 166 | hipack_dict_set_adopt_key (message, 167 | hipack_string_new_from_string ("street"), 168 | hipack_string ( 169 | hipack_string_new_from_string ("Infinite Loop"))); 170 | hipack_dict_set_adopt_key (message, 171 | hipack_string_new_from_string ("number"), 172 | hipack_integer (1)); 173 | return message; 174 | } 175 | 176 | Note how it is not needed to free any of the values because they are “owned” 177 | by the dictionary, which gets returned from the function. Also, we use 178 | :c:func:`hipack_dict_set_adopt_key()` (instead of :c:func:`hipack_dict_set()`) 179 | to pass ownership of the keys to the dictionary as well and, at the same time, 180 | avoiding creating copies of the strings. The caller of this function would be 181 | the owner of the memory allocated by the returned dictionary and all its 182 | contained values. 183 | 184 | 185 | 186 | Writing (serialization) 187 | ======================= 188 | 189 | In order to serialize messages, we need a dictionary containing the values 190 | which we want to have serialized — remember that any dictionary can be 191 | considered a HiPack message. In order to serialize a message, we use 192 | :c:func:`hipack_write()`, and in particular using 193 | :c:func:`hipack_stdio_putchar()` we can directly write to a ``FILE*`` stream. 194 | Given the ``make_address_message()`` function from the previous section: 195 | 196 | .. code-block:: c 197 | 198 | int main (int argc, char *argv[]) { 199 | hipack_dict_t *message = make_address_message (); 200 | hipack_writer_t writer = { 201 | .putchar_data = fopen ("address.hipack", "wb"), 202 | .putchar = hipack_stdio_putchar, 203 | }; 204 | hipack_write (&writer, message); 205 | hipack_dict_free (message); 206 | return 0; 207 | } 208 | 209 | The resulting ``address.hipack`` file will have the following contents (the 210 | order of the elements may vary):: 211 | 212 | number: 1 213 | street: "Infinite Loop" 214 | lat: 37.332 215 | lon: -122.03 216 | 217 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # hipack-c documentation build configuration file, created by 5 | # sphinx-quickstart on Thu Dec 17 20:03:40 2015. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | import shlex 19 | 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | #sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # The suffix(es) of source filenames. 39 | # You can specify multiple suffix as a list of string: 40 | # source_suffix = ['.rst', '.md'] 41 | source_suffix = '.rst' 42 | 43 | # The encoding of source files. 44 | #source_encoding = 'utf-8-sig' 45 | 46 | # The master toctree document. 47 | master_doc = 'index' 48 | 49 | # General information about the project. 50 | project = 'hipack-c' 51 | copyright = '2015, Adrian Perez de Castro' 52 | author = 'Adrian Perez de Castro' 53 | 54 | # The version info for the project you're documenting, acts as replacement for 55 | # |version| and |release|, also used in various other places throughout the 56 | # built documents. 57 | # 58 | # The short X.Y version. 59 | version = '0.1.2' 60 | # The full version, including alpha/beta/rc tags. 61 | release = '0.1.2' 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # There are two options for replacing |today|: either, you set today to some 71 | # non-false value, then it is used: 72 | #today = '' 73 | # Else, today_fmt is used as the format for a strftime call. 74 | #today_fmt = '%B %d, %Y' 75 | 76 | # List of patterns, relative to source directory, that match files and 77 | # directories to ignore when looking for source files. 78 | exclude_patterns = ['_build'] 79 | 80 | # The reST default role (used for this markup: `text`) to use for all 81 | # documents. 82 | #default_role = None 83 | 84 | # If true, '()' will be appended to :func: etc. cross-reference text. 85 | #add_function_parentheses = True 86 | 87 | # If true, the current module name will be prepended to all description 88 | # unit titles (such as .. function::). 89 | #add_module_names = True 90 | 91 | # If true, sectionauthor and moduleauthor directives will be shown in the 92 | # output. They are ignored by default. 93 | #show_authors = False 94 | 95 | # The name of the Pygments (syntax highlighting) style to use. 96 | pygments_style = 'sphinx' 97 | 98 | # A list of ignored prefixes for module index sorting. 99 | #modindex_common_prefix = [] 100 | 101 | # If true, keep warnings as "system message" paragraphs in the built documents. 102 | #keep_warnings = False 103 | 104 | # If true, `todo` and `todoList` produce output, else they produce nothing. 105 | todo_include_todos = False 106 | 107 | 108 | # -- Options for HTML output ---------------------------------------------- 109 | 110 | # The theme to use for HTML and HTML Help pages. See the documentation for 111 | # a list of builtin themes. 112 | html_theme = 'alabaster' 113 | 114 | # Theme options are theme-specific and customize the look and feel of a theme 115 | # further. For a list of options available for each theme, see the 116 | # documentation. 117 | #html_theme_options = {} 118 | 119 | # Add any paths that contain custom themes here, relative to this directory. 120 | #html_theme_path = [] 121 | 122 | # The name for this set of Sphinx documents. If None, it defaults to 123 | # " v documentation". 124 | #html_title = None 125 | 126 | # A shorter title for the navigation bar. Default is the same as html_title. 127 | #html_short_title = None 128 | 129 | # The name of an image file (relative to this directory) to place at the top 130 | # of the sidebar. 131 | #html_logo = None 132 | 133 | # The name of an image file (within the static path) to use as favicon of the 134 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 135 | # pixels large. 136 | #html_favicon = None 137 | 138 | # Add any paths that contain custom static files (such as style sheets) here, 139 | # relative to this directory. They are copied after the builtin static files, 140 | # so a file named "default.css" will overwrite the builtin "default.css". 141 | html_static_path = ['_static'] 142 | 143 | # Add any extra paths that contain custom files (such as robots.txt or 144 | # .htaccess) here, relative to this directory. These files are copied 145 | # directly to the root of the documentation. 146 | #html_extra_path = [] 147 | 148 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 149 | # using the given strftime format. 150 | #html_last_updated_fmt = '%b %d, %Y' 151 | 152 | # If true, SmartyPants will be used to convert quotes and dashes to 153 | # typographically correct entities. 154 | #html_use_smartypants = True 155 | 156 | # Custom sidebar templates, maps document names to template names. 157 | #html_sidebars = {} 158 | 159 | # Additional templates that should be rendered to pages, maps page names to 160 | # template names. 161 | #html_additional_pages = {} 162 | 163 | # If false, no module index is generated. 164 | #html_domain_indices = True 165 | 166 | # If false, no index is generated. 167 | #html_use_index = True 168 | 169 | # If true, the index is split into individual pages for each letter. 170 | #html_split_index = False 171 | 172 | # If true, links to the reST sources are added to the pages. 173 | #html_show_sourcelink = True 174 | 175 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 176 | #html_show_sphinx = True 177 | 178 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 179 | #html_show_copyright = True 180 | 181 | # If true, an OpenSearch description file will be output, and all pages will 182 | # contain a tag referring to it. The value of this option must be the 183 | # base URL from which the finished HTML is served. 184 | #html_use_opensearch = '' 185 | 186 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 187 | #html_file_suffix = None 188 | 189 | # Language to be used for generating the HTML full-text search index. 190 | # Sphinx supports the following languages: 191 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 192 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' 193 | #html_search_language = 'en' 194 | 195 | # A dictionary with options for the search language support, empty by default. 196 | # Now only 'ja' uses this config value 197 | #html_search_options = {'type': 'default'} 198 | 199 | # The name of a javascript file (relative to the configuration directory) that 200 | # implements a search results scorer. If empty, the default will be used. 201 | #html_search_scorer = 'scorer.js' 202 | 203 | # Output file base name for HTML help builder. 204 | htmlhelp_basename = 'hipack-cdoc' 205 | 206 | # -- Options for LaTeX output --------------------------------------------- 207 | 208 | latex_elements = { 209 | # The paper size ('letterpaper' or 'a4paper'). 210 | #'papersize': 'letterpaper', 211 | 212 | # The font size ('10pt', '11pt' or '12pt'). 213 | #'pointsize': '10pt', 214 | 215 | # Additional stuff for the LaTeX preamble. 216 | #'preamble': '', 217 | 218 | # Latex figure (float) alignment 219 | #'figure_align': 'htbp', 220 | } 221 | 222 | # Grouping the document tree into LaTeX files. List of tuples 223 | # (source start file, target name, title, 224 | # author, documentclass [howto, manual, or own class]). 225 | latex_documents = [ 226 | (master_doc, 'hipack-c.tex', 'hipack-c Documentation', 227 | 'Adrian Perez de Castro', 'manual'), 228 | ] 229 | 230 | # The name of an image file (relative to this directory) to place at the top of 231 | # the title page. 232 | #latex_logo = None 233 | 234 | # For "manual" documents, if this is true, then toplevel headings are parts, 235 | # not chapters. 236 | #latex_use_parts = False 237 | 238 | # If true, show page references after internal links. 239 | #latex_show_pagerefs = False 240 | 241 | # If true, show URL addresses after external links. 242 | #latex_show_urls = False 243 | 244 | # Documents to append as an appendix to all manuals. 245 | #latex_appendices = [] 246 | 247 | # If false, no module index is generated. 248 | #latex_domain_indices = True 249 | 250 | 251 | # -- Options for manual page output --------------------------------------- 252 | 253 | # One entry per manual page. List of tuples 254 | # (source start file, name, description, authors, manual section). 255 | man_pages = [ 256 | (master_doc, 'hipack-c', 'hipack-c Documentation', 257 | [author], 1) 258 | ] 259 | 260 | # If true, show URL addresses after external links. 261 | #man_show_urls = False 262 | 263 | 264 | # -- Options for Texinfo output ------------------------------------------- 265 | 266 | # Grouping the document tree into Texinfo files. List of tuples 267 | # (source start file, target name, title, author, 268 | # dir menu entry, description, category) 269 | texinfo_documents = [ 270 | (master_doc, 'hipack-c', 'hipack-c Documentation', 271 | author, 'hipack-c', 'One line description of project.', 272 | 'Miscellaneous'), 273 | ] 274 | 275 | # Documents to append as an appendix to all manuals. 276 | #texinfo_appendices = [] 277 | 278 | # If false, no module index is generated. 279 | #texinfo_domain_indices = True 280 | 281 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 282 | #texinfo_show_urls = 'footnote' 283 | 284 | # If true, do not generate a @detailmenu in the "Top" node's menu. 285 | #texinfo_no_detailmenu = False 286 | -------------------------------------------------------------------------------- /hipack-writer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hipack-writer.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "hipack.h" 9 | 10 | /* 11 | * Define FPCONV_H to avoid fpconv/src/fpconv.h being included. 12 | * By making our own definition of the function here, it can be 13 | * marked as "static inline". 14 | */ 15 | #define FPCONV_H 1 16 | static inline int fpconv_dtoa (double fp, char dest[24]); 17 | #include "fpconv/src/fpconv.c" 18 | 19 | 20 | static inline bool 21 | writechar (hipack_writer_t *writer, int ch) 22 | { 23 | assert (ch != HIPACK_IO_ERROR); 24 | assert (ch != HIPACK_IO_EOF); 25 | 26 | assert (writer->putchar); 27 | 28 | int ret = (*writer->putchar) (writer->putchar_data, ch); 29 | assert (ret != HIPACK_IO_EOF); 30 | return ret == HIPACK_IO_ERROR; 31 | } 32 | 33 | 34 | #define CHECK_IO(statement) \ 35 | do { \ 36 | if (statement) return true; \ 37 | } while (0) 38 | 39 | 40 | static inline void 41 | moreindent (hipack_writer_t *writer) 42 | { 43 | if (writer->indent != HIPACK_WRITER_COMPACT) 44 | writer->indent++; 45 | } 46 | 47 | 48 | static inline void 49 | lessindent (hipack_writer_t *writer) 50 | { 51 | if (writer->indent != HIPACK_WRITER_COMPACT) 52 | writer->indent--; 53 | } 54 | 55 | 56 | static inline bool 57 | writeindent (hipack_writer_t *writer) 58 | { 59 | int32_t num_spaces = (writer->indent != HIPACK_WRITER_COMPACT) 60 | ? writer->indent * 2 : 0; 61 | while (num_spaces--) { 62 | CHECK_IO (writechar (writer, ' ')); 63 | } 64 | return false; 65 | } 66 | 67 | 68 | static inline bool 69 | writedata (hipack_writer_t *writer, 70 | const char *data, 71 | uint32_t length) 72 | { 73 | if (length) { 74 | assert (data); 75 | while (length--) { 76 | CHECK_IO (writechar (writer, *data++)); 77 | } 78 | } 79 | return false; 80 | } 81 | 82 | 83 | static inline int 84 | mapdigit (unsigned n) 85 | { 86 | if (n < 10) { 87 | return '0' + n; 88 | } 89 | if (n < 36) { 90 | return 'A' + (n - 10); 91 | } 92 | assert (false); 93 | return '?'; 94 | } 95 | 96 | 97 | static inline bool 98 | formatint (hipack_writer_t *writer, 99 | int value, 100 | uint8_t base) 101 | { 102 | assert (writer); 103 | if (value >= base) { 104 | CHECK_IO (formatint (writer, value / base, base)); 105 | } 106 | CHECK_IO (writechar (writer, mapdigit (value % base))); 107 | return false; 108 | } 109 | 110 | 111 | bool 112 | hipack_write_bool (hipack_writer_t *writer, 113 | const bool value) 114 | { 115 | assert (writer); 116 | if (value) { 117 | return writedata (writer, "True", 4); 118 | } else { 119 | return writedata (writer, "False", 5); 120 | } 121 | } 122 | 123 | 124 | bool 125 | hipack_write_integer (hipack_writer_t *writer, 126 | const int32_t value) 127 | { 128 | assert (writer); 129 | if (value < 0) { 130 | CHECK_IO (writechar (writer, '-')); 131 | return formatint (writer, -value, 10); 132 | } else { 133 | return formatint (writer, value, 10); 134 | } 135 | } 136 | 137 | 138 | bool 139 | hipack_write_float (hipack_writer_t *writer, 140 | const double value) 141 | { 142 | assert (writer); 143 | 144 | char buf[24]; 145 | int nchars = fpconv_dtoa (value, buf); 146 | bool need_dot = true; 147 | for (int i = 0; i < nchars; i++) { 148 | CHECK_IO (writechar (writer, buf[i])); 149 | if (buf[i] == '.' || buf[i] == 'e' || buf[i] == 'E') { 150 | need_dot = false; 151 | } 152 | } 153 | if (need_dot) { 154 | CHECK_IO (writechar (writer, '.')); 155 | CHECK_IO (writechar (writer, '0')); 156 | } 157 | return false; 158 | } 159 | 160 | 161 | bool 162 | hipack_write_string (hipack_writer_t *writer, 163 | const hipack_string_t *hstr) 164 | { 165 | assert (writer); 166 | assert (hstr); 167 | CHECK_IO (writechar (writer, '"')); 168 | for (uint32_t i = 0; i < hstr->size; i++) { 169 | switch (hstr->data[i]) { 170 | case 0x09: /* Horizontal tab. */ 171 | CHECK_IO (writechar (writer, '\\')); 172 | CHECK_IO (writechar (writer, 't')); 173 | break; 174 | case 0x0A: /* New line. */ 175 | CHECK_IO (writechar (writer, '\\')); 176 | CHECK_IO (writechar (writer, 'n')); 177 | break; 178 | case 0x0D: /* Carriage return. */ 179 | CHECK_IO (writechar (writer, '\\')); 180 | CHECK_IO (writechar (writer, 'r')); 181 | break; 182 | case 0x22: /* Double quote. */ 183 | CHECK_IO (writechar (writer, '\\')); 184 | CHECK_IO (writechar (writer, '"')); 185 | break; 186 | case 0x5C: /* Backslash. */ 187 | CHECK_IO (writechar (writer, '\\')); 188 | CHECK_IO (writechar (writer, '\\')); 189 | break; 190 | default: 191 | if (hstr->data[i] < 0x20) { 192 | /* ASCII non-printable character. */ 193 | CHECK_IO (writechar (writer, '\\')); 194 | if ((uint8_t) hstr->data[i] < 16) { 195 | /* Leading zero. */ 196 | CHECK_IO (writechar (writer, '0')); 197 | } 198 | CHECK_IO (formatint (writer, (uint8_t) hstr->data[i], 16)); 199 | } else { 200 | CHECK_IO (writechar (writer, hstr->data[i])); 201 | } 202 | } 203 | } 204 | CHECK_IO (writechar (writer, '"')); 205 | return false; 206 | } 207 | 208 | 209 | static bool 210 | write_keyval (hipack_writer_t *writer, 211 | const hipack_dict_t *dict) 212 | { 213 | const hipack_string_t *key; 214 | hipack_value_t *value; 215 | 216 | uint32_t pending = hipack_dict_size (dict); 217 | 218 | HIPACK_DICT_FOREACH (dict, key, value) { 219 | writeindent (writer); 220 | /* Key */ 221 | for (uint32_t i = 0; i < key->size; i++) { 222 | CHECK_IO (writechar (writer, key->data[i])); 223 | } 224 | 225 | if (value->annot) { 226 | if (writer->indent == HIPACK_WRITER_COMPACT) { 227 | CHECK_IO (writechar (writer, ':')); 228 | } else { 229 | CHECK_IO (writechar (writer, ' ')); 230 | } 231 | } else { 232 | switch (value->type) { 233 | case HIPACK_INTEGER: 234 | case HIPACK_FLOAT: 235 | case HIPACK_BOOL: 236 | case HIPACK_STRING: 237 | CHECK_IO (writechar (writer, ':')); 238 | break; 239 | case HIPACK_DICT: 240 | case HIPACK_LIST: 241 | /* No colon. */ 242 | break; 243 | default: 244 | assert (false); 245 | } 246 | if (writer->indent != HIPACK_WRITER_COMPACT) { 247 | CHECK_IO (writechar (writer, ' ')); 248 | } 249 | } 250 | 251 | CHECK_IO (hipack_write_value (writer, value)); 252 | 253 | if (writer->indent != HIPACK_WRITER_COMPACT) { 254 | CHECK_IO (writechar (writer, '\n')); 255 | } else if (--pending > 0) { 256 | CHECK_IO (writechar (writer, ',')); 257 | } 258 | } 259 | 260 | return false; 261 | } 262 | 263 | 264 | bool 265 | hipack_write_list (hipack_writer_t *writer, 266 | const hipack_list_t *list) 267 | { 268 | assert (writer); 269 | assert (list); 270 | 271 | CHECK_IO (writechar (writer, '[')); 272 | 273 | if (hipack_list_size (list)) { 274 | if (writer->indent != HIPACK_WRITER_COMPACT) { 275 | CHECK_IO (writechar (writer, '\n')); 276 | } 277 | 278 | moreindent (writer); 279 | for (uint32_t i = 0; i < list->size;) { 280 | CHECK_IO (writeindent (writer)); 281 | CHECK_IO (hipack_write_value (writer, &list->data[i++])); 282 | if (writer->indent != HIPACK_WRITER_COMPACT) { 283 | CHECK_IO (writechar (writer, '\n')); 284 | } else if (i < list->size) { 285 | CHECK_IO (writechar (writer, ',')); 286 | } 287 | } 288 | lessindent (writer); 289 | CHECK_IO (writeindent (writer)); 290 | } 291 | 292 | CHECK_IO (writechar (writer, ']')); 293 | return false; 294 | } 295 | 296 | 297 | bool 298 | hipack_write_dict (hipack_writer_t *writer, 299 | const hipack_dict_t *dict) 300 | { 301 | CHECK_IO (writechar (writer, '{')); 302 | 303 | if (hipack_dict_size (dict)) { 304 | if (writer->indent != HIPACK_WRITER_COMPACT) { 305 | CHECK_IO (writechar (writer, '\n')); 306 | } 307 | 308 | moreindent (writer); 309 | CHECK_IO (write_keyval (writer, dict)); 310 | lessindent (writer); 311 | CHECK_IO (writeindent (writer)); 312 | } 313 | CHECK_IO (writechar (writer, '}')); 314 | return false; 315 | } 316 | 317 | 318 | bool 319 | hipack_write_value (hipack_writer_t *writer, 320 | const hipack_value_t *value) 321 | { 322 | assert (writer); 323 | assert (value); 324 | 325 | if (value->annot) { 326 | const hipack_string_t *key; 327 | hipack_value_t *dummy_value; 328 | HIPACK_DICT_FOREACH (value->annot, key, dummy_value) { 329 | CHECK_IO (writechar (writer, ':')); 330 | for (uint32_t i = 0; i < key->size; i++) { 331 | CHECK_IO (writechar (writer, key->data[i])); 332 | } 333 | } 334 | CHECK_IO (writechar (writer, ' ')); 335 | } 336 | 337 | switch (value->type) { 338 | case HIPACK_INTEGER: 339 | return hipack_write_integer (writer, value->v_integer); 340 | case HIPACK_FLOAT: 341 | return hipack_write_float (writer, value->v_float); 342 | case HIPACK_BOOL: 343 | return hipack_write_bool (writer, value->v_bool); 344 | case HIPACK_STRING: 345 | return hipack_write_string (writer, value->v_string); 346 | case HIPACK_LIST: 347 | return hipack_write_list (writer, value->v_list); 348 | case HIPACK_DICT: 349 | return hipack_write_dict (writer, value->v_dict); 350 | } 351 | 352 | assert (false); /* Never reached. */ 353 | return false; 354 | } 355 | 356 | 357 | bool 358 | hipack_write (hipack_writer_t *writer, 359 | const hipack_dict_t *message) 360 | { 361 | assert (writer); 362 | assert (message); 363 | if (writer->indent != HIPACK_WRITER_COMPACT) { 364 | writer->indent = HIPACK_WRITER_INDENTED; 365 | } 366 | return write_keyval (writer, message); 367 | } 368 | 369 | 370 | int 371 | hipack_stdio_putchar (void* fp, int ch) 372 | { 373 | assert (fp); 374 | int ret = fputc (ch, (FILE*) fp); 375 | return (ret == EOF) ? HIPACK_IO_ERROR : ch; 376 | } 377 | -------------------------------------------------------------------------------- /doc/apiref.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ============= 5 | API Reference 6 | ============= 7 | 8 | Types 9 | ===== 10 | 11 | .. c:type:: hipack_type_t 12 | 13 | 14 | Type of a value. This enumeration takes one of the following values: 15 | 16 | - ``HIPACK_INTEGER``: Integer value. 17 | - ``HIPACK_FLOAT``: Floating point value. 18 | - ``HIPACK_BOOL``: Boolean value. 19 | - ``HIPACK_STRING``: String value. 20 | - ``HIPACK_LIST``: List value. 21 | - ``HIPACK_DICT``: Dictionary value. 22 | 23 | .. c:type:: hipack_value_t 24 | 25 | 26 | Represent any valid HiPack value. 27 | 28 | - :func:`hipack_value_type()` obtains the type of a value. 29 | 30 | .. c:type:: hipack_string_t 31 | 32 | 33 | String value. 34 | 35 | .. c:type:: hipack_list_t 36 | 37 | 38 | List value. 39 | 40 | .. c:type:: hipack_dict_t 41 | 42 | 43 | Dictionary value. 44 | 45 | 46 | 47 | Memory Allocation 48 | ================= 49 | 50 | How ``hipack-c`` allocates memory can be customized by setting 51 | :c:data:`hipack_alloc` to a custom allocation function. 52 | 53 | .. c:var:: hipack_alloc 54 | 55 | 56 | Allocation function. By default it is set to :func:`hipack_alloc_stdlib()`, 57 | which uses the implementations of ``malloc()``, ``realloc()``, and 58 | ``free()`` provided by the C library. 59 | 60 | Allocation functions always have the following prototype: 61 | 62 | .. code-block:: c 63 | 64 | void* func (void *oldptr, size_t size); 65 | 66 | The behavior must be as follows: 67 | 68 | - When invoked with ``oldptr`` set to ``NULL``, and a non-zero ``size``, 69 | the function behaves like ``malloc()``: a memory block of at least 70 | ``size`` bytes is allocated and a pointer to it returned. 71 | - When ``oldptr`` is non-``NULL``, and a non-zero ``size``, the function 72 | behaves like ``realloc()``: the memory area pointed to by ``oldptr`` 73 | is resized to be at least ``size`` bytes, or its contents moved to a 74 | new memory area of at least ``size`` bytes. The returned pointer may 75 | either be ``oldptr``, or a pointer to the new memory area if the data 76 | was relocated. 77 | - When ``oldptr`` is non-``NULL``, and ``size`` is zero, the function 78 | behaves like ``free()``. 79 | 80 | .. c:function:: void* hipack_alloc_stdlib(void*, size_t) 81 | 82 | 83 | Default allocation function. It uses ``malloc()``, ``realloc()``, and 84 | ``free()`` from the C library. By default :any:`hipack_alloc` is set 85 | to use this function. 86 | 87 | .. c:function:: void* hipack_alloc_array_extra (void *oldptr, size_t nmemb, size_t size, size_t extra) 88 | 89 | 90 | Allocates (if `oldptr` is ``NULL``) or reallocates (if `oldptr` is 91 | non-``NULL``) memory for an array which contains `nmemb` elements, each one 92 | of `size` bytes, plus an arbitrary amount of `extra` bytes. 93 | 94 | This function is used internally by the HiPack parser, and it is not likely 95 | to be needed by client code. 96 | 97 | .. c:function:: void* hipack_alloc_array (void *optr, size_t nmemb, size_t size) 98 | 99 | 100 | Same as :c:func:`hipack_alloc_array_extra()`, without allowing to specify 101 | the amount of extra bytes. The following calls are both equivalent: 102 | 103 | .. code-block:: c 104 | 105 | void *a = hipack_alloc_array_extra (NULL, 10, 4, 0); 106 | void *b = hipack_alloc_array (NULL, 10, 4); 107 | 108 | See :c:func:`hipack_alloc_array_extra()` for details. 109 | 110 | .. c:function:: void* hipack_alloc_bzero (size_t size) 111 | 112 | 113 | Allocates an area of memory of `size` bytes, and initializes it to zeroes. 114 | 115 | .. c:function:: void hipack_alloc_free (void *pointer) 116 | 117 | 118 | Frees the memory area referenced by the given `pointer`. 119 | 120 | 121 | 122 | String Functions 123 | ================ 124 | 125 | The following functions are provided as a convenience to operate on values 126 | of type :c:type:`hipack_string_t`. 127 | 128 | .. note:: The hash function used by :c:func:`hipack_string_hash()` is 129 | *not* guaranteed to be cryptographically safe. Please do avoid exposing 130 | values returned by this function to the attack surface of your 131 | applications, in particular *do not expose them to the network*. 132 | 133 | .. c:function:: hipack_string_t* hipack_string_copy (const hipack_string_t *hstr) 134 | 135 | 136 | Returns a new copy of a string. 137 | 138 | The returned value must be freed using :c:func:`hipack_string_free()`. 139 | 140 | .. c:function:: hipack_string_t* hipack_string_new_from_string (const char *str) 141 | 142 | 143 | Creates a new string from a C-style zero terminated string. 144 | 145 | The returned value must be freed using :c:func:`hipack_string_free()`. 146 | 147 | .. c:function:: hipack_string_t* hipack_string_new_from_lstring (const char *str, uint32_t len) 148 | 149 | 150 | Creates a new string from a memory area and its length. 151 | 152 | The returned value must be freed using :c:func:`hipack_string_free()`. 153 | 154 | .. c:function:: uint32_t hipack_string_hash (const hipack_string_t *hstr) 155 | 156 | 157 | Calculates a hash value for a string. 158 | 159 | .. c:function:: bool hipack_string_equal (const hipack_string_t *hstr1, const hipack_string_t *hstr2) 160 | 161 | Compares two strings to check whether their contents are the same. 162 | 163 | .. c:function:: void hipack_string_free (hipack_string_t *hstr) 164 | 165 | Frees the memory used by a string. 166 | 167 | 168 | 169 | List Functions 170 | ============== 171 | 172 | .. c:function:: hipack_list_t* hipack_list_new (uint32_t size) 173 | 174 | Creates a new list for ``size`` elements. 175 | 176 | .. c:function:: void hipack_list_free (hipack_list_t *list) 177 | 178 | Frees the memory used by a list. 179 | 180 | .. c:function:: bool hipack_list_equal (const hipack_list_t *a, const hipack_list_t *b) 181 | 182 | Checks whether two lists contains the same values. 183 | 184 | .. c:function:: uint32_t hipack_list_size (const hipack_list_t *list) 185 | 186 | Obtains the number of elements in a list. 187 | 188 | .. c:macro:: HIPACK_LIST_AT(list, index) 189 | 190 | 191 | Obtains a pointer to the element at a given `index` of a `list`. 192 | 193 | 194 | 195 | .. _dict_funcs: 196 | 197 | Dictionary Functions 198 | ==================== 199 | 200 | .. c:function:: uint32_t hipack_dict_size (const hipack_dict_t *dict) 201 | 202 | 203 | Obtains the number of elements in a dictionary. 204 | 205 | .. c:function:: hipack_dict_t* hipack_dict_new (void) 206 | 207 | 208 | Creates a new, empty dictionary. 209 | 210 | .. c:function:: void hipack_dict_free (hipack_dict_t *dict) 211 | 212 | 213 | Frees the memory used by a dictionary. 214 | 215 | .. c:function:: bool hipack_dict_equal (const hipack_dict_t *a, const hipack_dict_t *b) 216 | 217 | 218 | Checks whether two dictinaries contain the same keys, and their associated 219 | values in each of the dictionaries are equal. 220 | 221 | .. c:function:: void hipack_dict_set (hipack_dict_t *dict, const hipack_string_t *key, const hipack_value_t *value) 222 | 223 | 224 | Adds an association of a `key` to a `value`. 225 | 226 | Note that this function will copy the `key`. If you are not planning to 227 | continue reusing the `key`, it is recommended to use 228 | :c:func:`hipack_dict_set_adopt_key()` instead. 229 | 230 | .. c:function:: void hipack_dict_set_adopt_key (hipack_dict_t *dict, hipack_string_t **key, const hipack_value_t *value) 231 | 232 | 233 | Adds an association of a `key` to a `value`, passing ownership of the 234 | memory using by the `key` to the dictionary (i.e. the string used as key 235 | will be freed by the dictionary). 236 | 237 | Use this function instead of :c:func:`hipack_dict_set()` when the `key` 238 | is not going to be used further afterwards. 239 | 240 | .. c:function:: void hipack_dict_del (hipack_dict_t *dict, const hipack_string_t *key) 241 | 242 | 243 | Removes the element from a dictionary associated to a `key`. 244 | 245 | .. c:function:: hipack_value_t* hipack_dict_get (const hipack_dict_t *dict, const hipack_string_t *key) 246 | 247 | 248 | Obtains the value associated to a `key` from a dictionary. 249 | 250 | The returned value points to memory owned by the dictionary. The value can 251 | be modified in-place, but it shall not be freed. 252 | 253 | .. c:function:: hipack_value_t* hipack_dict_first (const hipack_dict_t *dict, const hipack_string_t **key) 254 | 255 | 256 | Obtains an a *(key, value)* pair, which is considered the *first* in 257 | iteration order. This can be used in combination with 258 | :c:func:`hipack_dict_next()` to enumerate all the *(key, value)* pairs 259 | stored in the dictionary: 260 | 261 | .. code-block:: c 262 | 263 | hipack_dict_t *d = get_dictionary (); 264 | hipack_value_t *v; 265 | hipack_string_t *k; 266 | 267 | for (v = hipack_dict_first (d, &k); 268 | v != NULL; 269 | v = hipack_dict_next (v, &k)) { 270 | // Use "k" and "v" normally. 271 | } 272 | 273 | As a shorthand, consider using :c:macro:`HIPACK_DICT_FOREACH()` instead. 274 | 275 | .. c:function:: hipack_value_t* hipack_dict_next (hipack_value_t *value, const hipack_string_t **key) 276 | 277 | 278 | Iterates to the next *(key, value)* pair of a dictionary. For usage 279 | details, see :c:func:`hipack_dict_first()`. 280 | 281 | .. c:macro:: HIPACK_DICT_FOREACH(dict, key, value) 282 | 283 | 284 | Convenience macro used to iterate over the *(key, value)* pairs contained 285 | in a dictionary. Internally this uses :c:func:`hipack_dict_first()` and 286 | :c:func:`hipack_dict_next()`. 287 | 288 | .. code-block:: c 289 | 290 | hipack_dict_t *d = get_dictionary (); 291 | hipack_string_t *k; 292 | hipack_value_t *v; 293 | HIPACK_DICT_FOREACH (d, k, v) { 294 | // Use "k" and "v" 295 | } 296 | 297 | Using this macro is the recommended way of writing a loop to enumerate 298 | elements from a dictionary. 299 | 300 | 301 | 302 | Value Functions 303 | =============== 304 | 305 | .. c:function:: hipack_type_t hipack_value_type (const hipack_value_t *value) 306 | 307 | 308 | Obtains the type of a value. 309 | 310 | .. c:function:: hipack_value_t hipack_integer (int32_t value) 311 | 312 | Creates a new integer value. 313 | 314 | .. c:function:: hipack_value_t hipack_float (double value) 315 | 316 | Creates a new floating point value. 317 | 318 | .. c:function:: hipack_value_t hipack_bool (bool value) 319 | 320 | Creates a new boolean value. 321 | 322 | .. c:function:: hipack_value_t hipack_string (hipack_string_t *value) 323 | 324 | Creates a new string value. 325 | 326 | .. c:function:: hipack_value_t hipack_list (hipack_list_t *value) 327 | 328 | Creates a new list value. 329 | 330 | .. c:function:: hipack_value_t hipack_dict (hipack_dict_t *value) 331 | 332 | Creates a new dictionary value. 333 | 334 | .. c:function:: bool hipack_value_is_integer (const hipack_value_t *value) 335 | 336 | Checks whether a value is an integer. 337 | 338 | .. c:function:: bool hipack_value_is_float (const hipack_value_t *value) 339 | 340 | Checks whether a value is a floating point number. 341 | 342 | .. c:function:: bool hipack_value_is_bool (const hipack_value_t *value) 343 | 344 | Checks whether a value is a boolean. 345 | 346 | .. c:function:: bool hipack_value_is_string (const hipack_value_t *value) 347 | 348 | Checks whether a value is a string. 349 | 350 | .. c:function:: bool hipack_value_is_list (const hipack_value_t *value) 351 | 352 | Checks whether a value is a list. 353 | 354 | .. c:function:: bool hipack_value_is_dict (const hipack_value_t *value) 355 | 356 | Checks whether a value is a dictionary. 357 | 358 | .. c:function:: const int32_t hipack_value_get_integer (const hipack_value_t *value) 359 | 360 | Obtains a numeric value as an ``int32_t``. 361 | 362 | .. c:function:: const double hipack_value_get_float (const hipack_value_t *value) 363 | 364 | Obtains a floating point value as a ``double``. 365 | 366 | .. c:function:: const bool hipack_value_get_bool (const hipack_value_t *value) 367 | 368 | Obtains a boolean value as a ``bool``. 369 | 370 | .. c:function:: const hipack_string_t* hipack_value_get_string (const hipack_value_t *value) 371 | 372 | Obtains a numeric value as a :c:type:`hipack_string_t`. 373 | 374 | .. c:function:: const hipack_list_t* hipack_value_get_list (const hipack_value_t *value) 375 | 376 | Obtains a numeric value as a :c:type:`hipack_list_t`. 377 | 378 | .. c:function:: const hipack_dict_t* hipack_value_get_dict (const hipack_value_t *value) 379 | 380 | Obtains a numeric value as a :c:type:`hipack_dict_t`. 381 | 382 | .. c:function:: bool hipack_value_equal (const hipack_value_t *a, const hipack_value_t *b) 383 | 384 | 385 | Checks whether two values are equal. 386 | 387 | .. c:function:: void hipack_value_free (hipack_value_t *value) 388 | 389 | 390 | Frees the memory used by a value. 391 | 392 | .. c:function:: void hipack_value_add_annot (hipack_value_t *value, const char *annot) 393 | 394 | 395 | Adds an annotation to a value. If the value already had the annotation, 396 | this function is a no-op. 397 | 398 | .. c:function:: bool hipack_value_has_annot (const hipack_value_t *value, const char *annot) 399 | 400 | 401 | Checks whether a value has a given annotation. 402 | 403 | .. c:function:: void hipack_value_del_annot (hipack_value_t *value, const char *annot) 404 | 405 | 406 | Removes an annotation from a value. If the annotation was not present, this 407 | function is a no-op. 408 | 409 | 410 | 411 | Reader Interface 412 | ================ 413 | 414 | .. c:type:: hipack_reader_t 415 | 416 | 417 | Allows communicating with the parser, instructing it how to read text 418 | input data, and provides a way for the parser to report errors back. 419 | 420 | The following members of the structure are to be used by client code: 421 | 422 | .. c:member:: int (*getchar)(void *data) 423 | 424 | Reader callback function. The function will be called every time the 425 | next character of input is needed. It must return it as an integer, 426 | :any:`HIPACK_IO_EOF` when trying to read pas the end of the input, 427 | or :any:`HIPACK_IO_ERROR` if an input error occurs. 428 | 429 | 430 | .. c:member:: const char *error 431 | 432 | On error, a string describing the issue, suitable to be displayed to 433 | the user. 434 | 435 | .. c:member:: unsigned error_line 436 | 437 | On error, the line number where parsing was stopped. 438 | 439 | .. c:member:: unsigned error_column 440 | 441 | On error, the column where parsing was stopped. 442 | 443 | .. c:macro:: HIPACK_IO_EOF 444 | 445 | Constant returned by reader functions when trying to read past the end of 446 | the input. 447 | 448 | .. c:macro:: HIPACK_IO_ERROR 449 | 450 | Constant returned by reader functions on input errors. 451 | 452 | .. c:macro:: HIPACK_READ_ERROR 453 | 454 | 455 | Constant value used to signal an underlying input error. 456 | 457 | The `error` field of :c:type:`hipack_reader_t` is set to this value when 458 | the reader function returns :any:`HIPACK_IO_ERROR`. This is provided to 459 | allow client code to detect this condition and further query for the 460 | nature of the input error. 461 | 462 | .. c:function:: hipack_dict_t* hipack_read (hipack_reader_t *reader) 463 | 464 | 465 | Reads a HiPack message from a stream `reader` and returns a dictionary. 466 | 467 | On error, ``NULL`` is returned, and the members `error`, `error_line`, 468 | and `error_column` (see :c:type:`hipack_reader_t`) are set accordingly 469 | in the `reader`. 470 | 471 | .. c:function:: int hipack_stdio_getchar (void* fp) 472 | 473 | 474 | Reader function which uses ``FILE*`` objects from the standard C library. 475 | 476 | To use this function to read from a ``FILE*``, first open a file, and 477 | then create a reader using this function and the open file as data to 478 | be passed to it, and then use :c:func:`hipack_read()`: 479 | 480 | .. code-block:: c 481 | 482 | FILE* stream = fopen (HIPACK_FILE_PATH, "rb") 483 | hipack_reader_t reader = { 484 | .getchar = hipack_stdio_getchar, 485 | .getchar_data = stream, 486 | }; 487 | hipack_dict_t *message = hipack_read (&reader); 488 | 489 | The user is responsible for closing the ``FILE*`` after using it. 490 | 491 | 492 | 493 | Writer Interface 494 | ================ 495 | 496 | .. c:type:: hipack_writer_t 497 | 498 | 499 | Allows specifying how to write text output data, and configuring how 500 | the produced HiPack output looks like. 501 | 502 | The following members of the structure are to be used by client code: 503 | 504 | .. c:member:: int (*putchar)(void *data, int ch) 505 | 506 | Writer callback function. The function will be called every time a 507 | character is produced as output. It must return :any:`HIPACK_IO_ERROR` 508 | if an output error occurs, and it is invalid for the callback to 509 | return :any:`HIPACK_IO_EOF`. Any other value is interpreted as 510 | indication of success. 511 | 512 | .. c:member:: void* putchar_data 513 | 514 | Data passed to the writer callback function. 515 | 516 | .. c:member:: int32_t indent 517 | 518 | Either :any:`HIPACK_WRITER_COMPACT` or :any:`HIPACK_WRITER_INDENTED`. 519 | 520 | .. c:macro:: HIPACK_WRITER_COMPACT 521 | 522 | Flag to generate output HiPack messages in their compact representation. 523 | 524 | .. c:macro:: HIPACK_WRITER_INDENTED 525 | 526 | Flag to generate output HiPack messages in “indented” (pretty-printed) 527 | representation. 528 | 529 | .. c:function:: bool hipack_write (hipack_writer_t *writer, const hipack_dict_t *message) 530 | 531 | 532 | Writes a HiPack `message` to a stream `writer`, and returns whether writing 533 | the message was successful. 534 | 535 | .. c:function:: int hipack_stdio_putchar (void* data, int ch) 536 | 537 | 538 | Writer function which uses ``FILE*`` objects from the standard C library. 539 | 540 | To use this function to write a message to a ``FILE*``, first open a file, 541 | then create a writer using this function, and then use 542 | :c:func:`hipack_write()`: 543 | 544 | .. code-block:: c 545 | 546 | FILE* stream = fopen (HIPACK_FILE_PATH, "wb"); 547 | hipack_writer_t writer = { 548 | .putchar = hipack_stdio_putchar, 549 | .putchar_data = stream, 550 | }; 551 | hipack_write (&writer, message); 552 | 553 | The user is responsible for closing the ``FILE*`` after using it. 554 | 555 | -------------------------------------------------------------------------------- /hipack-parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hipack-parser.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "hipack.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | const char* HIPACK_READ_ERROR = "Error reading from input"; 18 | 19 | 20 | enum status { 21 | kStatusOk = 0, 22 | kStatusEof, 23 | kStatusError, 24 | kStatusIoError, 25 | }; 26 | typedef enum status status_t; 27 | 28 | 29 | struct parser { 30 | int (*getchar) (void*); 31 | void *getchar_data; 32 | int look; 33 | unsigned line; 34 | unsigned column; 35 | const char *error; 36 | }; 37 | 38 | #define P struct parser* p 39 | #define S status_t *status 40 | 41 | #define CHECK_OK status); \ 42 | if (*status != kStatusOk) goto error; \ 43 | ((void) 0 44 | #define DUMMY ) /* Makes autoindentation work. */ 45 | #undef DUMMY 46 | 47 | #define DUMMY_VALUE ((hipack_value_t) { .type = HIPACK_BOOL, .annot = NULL }) 48 | 49 | 50 | static hipack_value_t parse_value (P, S); 51 | static void parse_keyval_items (P, hipack_dict_t *result, int eos, S); 52 | 53 | 54 | static inline bool 55 | string_to_intrinsic_annot (const hipack_string_t *hstr, hipack_type_t *type) 56 | { 57 | assert (type); 58 | 59 | static const struct { 60 | const char * const str; 61 | int type; 62 | } annots[] = { 63 | { ".int", HIPACK_INTEGER }, 64 | { ".float", HIPACK_FLOAT }, 65 | { ".bool", HIPACK_BOOL }, 66 | { ".string", HIPACK_STRING }, 67 | { ".list", HIPACK_LIST }, 68 | { ".dict", HIPACK_DICT }, 69 | }; 70 | 71 | if (hstr->size < 4) 72 | return false; 73 | 74 | for (uint8_t i = 0; i < sizeof (annots) / sizeof (annots[0]); i++) { 75 | if (strncmp (annots[i].str, (const char*) hstr->data, hstr->size) == 0) { 76 | *type = annots[i].type; 77 | return true; 78 | } 79 | } 80 | return false; 81 | } 82 | 83 | 84 | static inline bool 85 | is_hipack_whitespace (int ch) 86 | { 87 | switch (ch) { 88 | case 0x09: /* Horizontal tab. */ 89 | case 0x0A: /* New line. */ 90 | case 0x0D: /* Carriage return. */ 91 | case 0x20: /* Space. */ 92 | return true; 93 | default: 94 | return false; 95 | } 96 | } 97 | 98 | 99 | static inline bool 100 | is_hipack_key_character (int ch) 101 | { 102 | switch (ch) { 103 | /* Keys do not contain whitespace */ 104 | case 0x09: /* Horizontal tab. */ 105 | case 0x0A: /* New line. */ 106 | case 0x0D: /* Carriage return. */ 107 | case 0x20: /* Space. */ 108 | /* Characters are forbidden in keys by the spec. */ 109 | case '[': 110 | case ']': 111 | case '{': 112 | case '}': 113 | case ':': 114 | case ',': 115 | return false; 116 | default: 117 | return true; 118 | } 119 | } 120 | 121 | 122 | static inline bool 123 | is_number_char (int ch) 124 | { 125 | switch (ch) { 126 | case '.': return true; 127 | case '+': return true; 128 | case '-': return true; 129 | case '0': return true; 130 | case '1': return true; 131 | case '2': return true; 132 | case '3': return true; 133 | case '4': return true; 134 | case '5': return true; 135 | case '6': return true; 136 | case '7': return true; 137 | case '8': return true; 138 | case '9': return true; 139 | case 'a': case 'A': return true; 140 | case 'b': case 'B': return true; 141 | case 'c': case 'C': return true; 142 | case 'd': case 'D': return true; 143 | case 'e': case 'E': return true; 144 | case 'f': case 'F': return true; 145 | default: 146 | return false; 147 | } 148 | } 149 | 150 | 151 | static inline bool 152 | is_octal_nonzero_digit (int ch) 153 | { 154 | return (ch > '0') && (ch < '8'); 155 | } 156 | 157 | 158 | static inline int 159 | xdigit_to_int (int xdigit) 160 | { 161 | assert ((xdigit >= '0' && xdigit <= '9') || 162 | (xdigit >= 'A' && xdigit <= 'F') || 163 | (xdigit >= 'a' && xdigit <= 'f')); 164 | 165 | switch (xdigit) { 166 | case '0': return 0; 167 | case '1': return 1; 168 | case '2': return 2; 169 | case '3': return 3; 170 | case '4': return 4; 171 | case '5': return 5; 172 | case '6': return 6; 173 | case '7': return 7; 174 | case '8': return 8; 175 | case '9': return 9; 176 | case 'a': case 'A': return 0xA; 177 | case 'b': case 'B': return 0xB; 178 | case 'c': case 'C': return 0xC; 179 | case 'd': case 'D': return 0xD; 180 | case 'e': case 'E': return 0xE; 181 | case 'f': case 'F': return 0xF; 182 | default: abort (); 183 | } 184 | } 185 | 186 | 187 | static inline int 188 | nextchar_raw (P, S) 189 | { 190 | int ch = (*p->getchar) (p->getchar_data); 191 | switch (ch) { 192 | case HIPACK_IO_ERROR: 193 | *status = kStatusIoError; 194 | /* fall-through */ 195 | case HIPACK_IO_EOF: 196 | break; 197 | 198 | case '\n': 199 | p->column = 0; 200 | p->line++; 201 | /* fall-through */ 202 | default: 203 | p->column++; 204 | } 205 | return ch; 206 | } 207 | 208 | 209 | static inline void 210 | nextchar (P, S) 211 | { 212 | do { 213 | p->look = nextchar_raw (p, CHECK_OK); 214 | 215 | if (p->look == '#') { 216 | while (p->look != '\n' && p->look != HIPACK_IO_EOF) { 217 | p->look = nextchar_raw (p, CHECK_OK); 218 | } 219 | } 220 | } while (p->look != HIPACK_IO_EOF && p->look == '#'); 221 | 222 | error: 223 | /* noop */; 224 | } 225 | 226 | 227 | static inline void 228 | skipwhite (P, S) 229 | { 230 | while (p->look != HIPACK_IO_EOF && is_hipack_whitespace (p->look)) 231 | nextchar (p, status); 232 | } 233 | 234 | 235 | static inline void 236 | matchchar (P, int ch, const char *errmsg, S) 237 | { 238 | if (p->look == ch) { 239 | nextchar (p, CHECK_OK); 240 | return; 241 | } 242 | 243 | p->error = errmsg ? errmsg : "unexpected input"; 244 | *status = kStatusError; 245 | 246 | error: 247 | return; 248 | } 249 | 250 | 251 | #ifndef HIPACK_STRING_CHUNK_SIZE 252 | #define HIPACK_STRING_CHUNK_SIZE 32 253 | #endif /* !HIPACK_STRING_CHUNK_SIZE */ 254 | 255 | #ifndef HIPACK_STRING_POW_SIZE 256 | #define HIPACK_STRING_POW_SIZE 512 257 | #endif /* !HIPACK_STRING_POW_SIZE */ 258 | 259 | #ifndef HIPACK_LIST_CHUNK_SIZE 260 | #define HIPACK_LIST_CHUNK_SIZE HIPACK_STRING_CHUNK_SIZE 261 | #endif /* !HIPACK_LIST_CHUNK_SIZE */ 262 | 263 | #ifndef HIPACK_LIST_POW_SIZE 264 | #define HIPACK_LIST_POW_SIZE HIPACK_STRING_POW_SIZE 265 | #endif /* !HIPACK_LIST_POW_SIZE */ 266 | 267 | 268 | static hipack_string_t* 269 | string_resize (hipack_string_t *hstr, uint32_t *alloc, uint32_t size) 270 | { 271 | /* TODO: Use HIPACK_STRING_POW_SIZE. */ 272 | if (size) { 273 | uint32_t new_size = HIPACK_STRING_CHUNK_SIZE * 274 | ((size / HIPACK_STRING_CHUNK_SIZE) + 1); 275 | if (new_size < size) { 276 | new_size = size; 277 | } 278 | if (new_size != *alloc) { 279 | *alloc = new_size; 280 | new_size = sizeof (hipack_string_t) + new_size * sizeof (uint8_t); 281 | hstr = hipack_alloc_array_extra (hstr, new_size, 282 | sizeof (uint8_t), 283 | sizeof (hipack_string_t)); 284 | } 285 | hstr->size = size; 286 | } else { 287 | hipack_alloc_free (hstr); 288 | hstr = NULL; 289 | *alloc = 0; 290 | } 291 | return hstr; 292 | } 293 | 294 | 295 | static hipack_list_t* 296 | list_resize (hipack_list_t *list, uint32_t *alloc, uint32_t size) 297 | { 298 | /* TODO: Use HIPACK_LIST_POW_SIZE. */ 299 | if (size) { 300 | uint32_t new_size = HIPACK_LIST_CHUNK_SIZE * 301 | ((size / HIPACK_LIST_CHUNK_SIZE) + 1); 302 | if (new_size < size) { 303 | new_size = size; 304 | } 305 | if (new_size != *alloc) { 306 | *alloc = new_size; 307 | list = hipack_alloc_array_extra (list, new_size, 308 | sizeof (hipack_value_t), 309 | sizeof (hipack_list_t)); 310 | } 311 | list->size = size; 312 | } else { 313 | hipack_alloc_free (list); 314 | list = NULL; 315 | *alloc = 0; 316 | } 317 | return list; 318 | } 319 | 320 | 321 | /* On empty (missing) keys, NULL is returned. */ 322 | static hipack_string_t* 323 | parse_key (P, S) 324 | { 325 | hipack_string_t *hstr = NULL; 326 | uint32_t alloc_size = 0; 327 | uint32_t size = 0; 328 | 329 | while (p->look != HIPACK_IO_EOF && is_hipack_key_character (p->look)) { 330 | hstr = string_resize (hstr, &alloc_size, size + 1); 331 | hstr->data[size++] = p->look; 332 | nextchar (p, CHECK_OK); 333 | } 334 | 335 | return hstr; 336 | 337 | error: 338 | hipack_string_free (hstr); 339 | return NULL; 340 | } 341 | 342 | 343 | static void 344 | parse_string (P, hipack_value_t *result, S) 345 | { 346 | hipack_string_t *hstr = NULL; 347 | uint32_t alloc_size = 0; 348 | uint32_t size = 0; 349 | 350 | matchchar (p, '"', NULL, CHECK_OK); 351 | 352 | while (p->look != '"' && p->look != HIPACK_IO_EOF) { 353 | /* Handle escapes. */ 354 | if (p->look == '\\') { 355 | int extra; 356 | 357 | p->look = nextchar_raw (p, CHECK_OK); 358 | switch (p->look) { 359 | case '"' : p->look = '"' ; break; 360 | case 'n' : p->look = '\n'; break; 361 | case 'r' : p->look = '\r'; break; 362 | case 't' : p->look = '\t'; break; 363 | case '\\': p->look = '\\'; break; 364 | default: 365 | /* Hex number. */ 366 | extra = nextchar_raw (p, CHECK_OK); 367 | if (!isxdigit (extra) || !isxdigit (p->look)) { 368 | p->error = "invalid escape sequence"; 369 | *status = kStatusError; 370 | goto error; 371 | } 372 | p->look = (xdigit_to_int (p->look) * 16) + 373 | xdigit_to_int (extra); 374 | break; 375 | } 376 | } 377 | 378 | hstr = string_resize (hstr, &alloc_size, size + 1); 379 | hstr->data[size++] = p->look; 380 | 381 | /* Read next character from the string. */ 382 | p->look = nextchar_raw (p, CHECK_OK); 383 | } 384 | 385 | matchchar (p, '"', "unterminated string value", CHECK_OK); 386 | result->type = HIPACK_STRING; 387 | result->v_string = hstr ? hstr : hipack_string_new_from_lstring ("", 0); 388 | return; 389 | 390 | error: 391 | hipack_string_free (hstr); 392 | return; 393 | } 394 | 395 | 396 | static void 397 | parse_list (P, hipack_value_t *result, S) 398 | { 399 | hipack_list_t *list = NULL; 400 | uint32_t alloc_size = 0; 401 | uint32_t size = 0; 402 | 403 | matchchar (p, '[', NULL, CHECK_OK); 404 | skipwhite (p, CHECK_OK); 405 | 406 | while (p->look != ']') { 407 | hipack_value_t value = parse_value (p, CHECK_OK); 408 | list = list_resize (list, &alloc_size, size + 1); 409 | list->data[size++] = value; 410 | 411 | bool got_whitespace = is_hipack_whitespace (p->look); 412 | skipwhite (p, CHECK_OK); 413 | 414 | /* There must either a comma or whitespace after the value. */ 415 | if (p->look == ',') { 416 | nextchar (p, CHECK_OK); 417 | } else if (!got_whitespace && !is_hipack_whitespace (p->look)) { 418 | break; 419 | } 420 | skipwhite (p, CHECK_OK); 421 | } 422 | 423 | matchchar (p, ']', "unterminated list value", CHECK_OK); 424 | result->type = HIPACK_LIST; 425 | result->v_list = list ? list : hipack_list_new (0); 426 | return; 427 | 428 | error: 429 | hipack_list_free (list); 430 | return; 431 | } 432 | 433 | 434 | static void 435 | parse_dict (P, hipack_value_t *result, S) 436 | { 437 | hipack_dict_t *dict = hipack_dict_new (); 438 | matchchar (p, '{', NULL, CHECK_OK); 439 | skipwhite (p, CHECK_OK); 440 | parse_keyval_items (p, dict, '}', CHECK_OK); 441 | matchchar (p, '}', "unterminated dict value", CHECK_OK); 442 | result->type = HIPACK_DICT; 443 | result->v_dict = dict; 444 | return; 445 | 446 | error: 447 | hipack_dict_free (dict); 448 | return; 449 | } 450 | 451 | 452 | static void 453 | parse_bool (P, hipack_value_t *result, S) 454 | { 455 | result->type = HIPACK_BOOL; 456 | if (p->look == 'T' || p->look == 't') { 457 | nextchar (p, CHECK_OK); 458 | matchchar (p, 'r', NULL, CHECK_OK); 459 | matchchar (p, 'u', NULL, CHECK_OK); 460 | matchchar (p, 'e', NULL, CHECK_OK); 461 | result->v_bool = true; 462 | } else if (p->look == 'F' || p->look == 'f') { 463 | nextchar (p, CHECK_OK); 464 | matchchar (p, 'a', NULL, CHECK_OK); 465 | matchchar (p, 'l', NULL, CHECK_OK); 466 | matchchar (p, 's', NULL, CHECK_OK); 467 | matchchar (p, 'e', NULL, CHECK_OK); 468 | result->v_bool = false; 469 | } 470 | return; 471 | 472 | error: 473 | p->error = "invalid boolean value"; 474 | } 475 | 476 | 477 | static void 478 | parse_number (P, hipack_value_t *result, S) 479 | { 480 | hipack_string_t *hstr = NULL; 481 | uint32_t alloc_size = 0; 482 | uint32_t size = 0; 483 | 484 | #define SAVE_LOOK( ) \ 485 | hstr = string_resize (hstr, &alloc_size, size + 1); \ 486 | hstr->data[size++] = p->look 487 | 488 | /* Optional sign. */ 489 | if (p->look == '-' || p->look == '+') { 490 | SAVE_LOOK (); 491 | nextchar (p, CHECK_OK); 492 | } 493 | 494 | /* Octal/hexadecimal numbers. */ 495 | bool is_octal = false; 496 | bool is_hex = false; 497 | if (p->look == '0') { 498 | SAVE_LOOK (); 499 | nextchar (p, CHECK_OK); 500 | if (p->look == 'x' || p->look == 'X') { 501 | SAVE_LOOK (); 502 | nextchar (p, CHECK_OK); 503 | is_hex = true; 504 | } else if (is_octal_nonzero_digit (p->look)) { 505 | is_octal = true; 506 | } 507 | } 508 | 509 | /* Read the rest of the number. */ 510 | bool dot_seen = false; 511 | bool exp_seen = false; 512 | while (p->look != HIPACK_IO_EOF && is_number_char (p->look)) { 513 | if (!is_hex && (p->look == 'e' || p->look == 'E')) { 514 | if (exp_seen || is_octal) { 515 | *status = kStatusError; 516 | goto error; 517 | } 518 | exp_seen = true; 519 | /* Handle the optional sign of the exponent. */ 520 | SAVE_LOOK (); 521 | nextchar (p, CHECK_OK); 522 | if (p->look == '-' || p->look == '+') { 523 | SAVE_LOOK (); 524 | nextchar (p, CHECK_OK); 525 | } 526 | } else { 527 | if (p->look == '.') { 528 | if (dot_seen || is_hex || is_octal) { 529 | *status = kStatusError; 530 | goto error; 531 | } 532 | dot_seen = true; 533 | } 534 | if (p->look == '-' || p->look == '+') { 535 | *status = kStatusError; 536 | goto error; 537 | } 538 | SAVE_LOOK (); 539 | nextchar (p, CHECK_OK); 540 | } 541 | } 542 | 543 | if (!size) { 544 | *status = kStatusError; 545 | goto error; 546 | } 547 | 548 | /* Zero-terminate, to use with the libc conversion functions. */ 549 | hstr = string_resize (hstr, &alloc_size, size + 1); 550 | hstr->data[size++] = '\0'; 551 | 552 | char *endptr = NULL; 553 | if (is_hex) { 554 | assert (!is_octal); 555 | assert (!exp_seen); 556 | assert (!dot_seen); 557 | char *endptr = NULL; 558 | long v = strtol ((const char*) hstr->data, &endptr, 16); 559 | /* TODO: Check for overflow. */ 560 | result->type = HIPACK_INTEGER; 561 | result->v_integer = (int32_t) v; 562 | } else if (is_octal) { 563 | assert (!is_hex); 564 | assert (!exp_seen); 565 | assert (!dot_seen); 566 | long v = strtol ((const char*) hstr->data, &endptr, 8); 567 | /* TODO: Check for overflow. */ 568 | result->type = HIPACK_INTEGER; 569 | result->v_integer = (int32_t) v; 570 | } else if (dot_seen || exp_seen) { 571 | assert (!is_hex); 572 | assert (!is_octal); 573 | result->type = HIPACK_FLOAT; 574 | result->v_float = strtod ((const char*) hstr->data, &endptr); 575 | } else { 576 | assert (!is_hex); 577 | assert (!is_octal); 578 | assert (!exp_seen); 579 | assert (!dot_seen); 580 | long v = strtol ((const char*) hstr->data, &endptr, 10); 581 | /* TODO: Check for overflow. */ 582 | result->type = HIPACK_INTEGER; 583 | result->v_integer = (int32_t) v; 584 | } 585 | 586 | if (endptr && *endptr != '\0') { 587 | *status = kStatusError; 588 | goto error; 589 | } 590 | 591 | hipack_string_free (hstr); 592 | return; 593 | 594 | error: 595 | p->error = "invalid numeric value"; 596 | hipack_string_free (hstr); 597 | } 598 | 599 | 600 | static bool 601 | parse_annotations (P, hipack_value_t *result, S) 602 | { 603 | hipack_string_t *key = NULL; 604 | bool type_annot = false; 605 | 606 | while (p->look == ':') { 607 | p->look = nextchar_raw (p, CHECK_OK); 608 | key = parse_key (p, CHECK_OK); 609 | skipwhite (p, CHECK_OK); /* TODO: Move after checking duplicates. */ 610 | 611 | /* Check for intrinsic type annotations. */ 612 | assert (key->size > 0); 613 | if (key->data[0] == '.') { 614 | hipack_type_t annot_type; 615 | bool found = string_to_intrinsic_annot (key, &annot_type); 616 | if (found) { 617 | if (type_annot && annot_type != result->type) { 618 | p->error = "multiple intrinsic type annotations"; 619 | goto error; 620 | } 621 | result->type = annot_type; 622 | type_annot = true; 623 | } else { 624 | p->error = "invalid intrinsic annotation"; 625 | goto error; 626 | } 627 | } else { 628 | /* Check if the annotation is already in the set. */ 629 | if (result->annot && hipack_dict_get (result->annot, key)) { 630 | p->error = "duplicate annotation"; 631 | goto error; 632 | } 633 | /* Add the annotation to the set. */ 634 | if (!result->annot) 635 | result->annot = hipack_dict_new (); 636 | 637 | static const hipack_value_t annot_present = { 638 | .type = HIPACK_BOOL, 639 | .v_bool = true, 640 | }; 641 | hipack_dict_set_adopt_key (result->annot, &key, &annot_present); 642 | } 643 | } 644 | return type_annot; 645 | 646 | error: 647 | if (key) 648 | hipack_string_free (key); 649 | *status = kStatusError; 650 | return false; 651 | } 652 | 653 | 654 | static hipack_value_t 655 | parse_value (P, S) 656 | { 657 | hipack_value_t result = DUMMY_VALUE; 658 | 659 | bool type_annot = parse_annotations (p, &result, CHECK_OK); 660 | const hipack_type_t expected_type = result.type; 661 | 662 | switch (p->look) { 663 | case '"': /* String */ 664 | parse_string (p, &result, CHECK_OK); 665 | break; 666 | 667 | case '[': /* List */ 668 | parse_list (p, &result, CHECK_OK); 669 | break; 670 | 671 | case '{': /* Dict */ 672 | parse_dict (p, &result, CHECK_OK); 673 | break; 674 | 675 | case 'T': /* Bool */ 676 | case 't': 677 | case 'F': 678 | case 'f': 679 | parse_bool (p, &result, CHECK_OK); 680 | break; 681 | 682 | default: /* Integer or Float */ 683 | parse_number (p, &result, CHECK_OK); 684 | break; 685 | } 686 | 687 | if (type_annot && expected_type != result.type) { 688 | p->error = "annotated type does not match value type"; 689 | *status = kStatusError; 690 | goto error; 691 | } 692 | 693 | return result; 694 | 695 | error: 696 | hipack_value_free (&result); 697 | return DUMMY_VALUE; 698 | } 699 | 700 | 701 | static void 702 | parse_keyval_items (P, hipack_dict_t *result, int eos, S) 703 | { 704 | hipack_value_t value = DUMMY_VALUE; 705 | hipack_string_t *key = NULL; 706 | 707 | while (p->look != eos) { 708 | key = parse_key (p, CHECK_OK); 709 | if (!key) { 710 | p->error = "missing dictionary key"; 711 | *status = kStatusError; 712 | goto error; 713 | } 714 | 715 | bool got_separator = false; 716 | 717 | if (is_hipack_whitespace (p->look)) { 718 | got_separator = true; 719 | skipwhite (p, CHECK_OK); 720 | } else switch (p->look) { 721 | case ':': 722 | nextchar (p, CHECK_OK); 723 | skipwhite (p, CHECK_OK); 724 | /* fall-through */ 725 | case '{': 726 | case '[': 727 | got_separator = true; 728 | break; 729 | } 730 | 731 | if (!got_separator) { 732 | p->error = "missing separator"; 733 | *status = kStatusError; 734 | goto error; 735 | } 736 | 737 | value = parse_value (p, CHECK_OK); 738 | hipack_dict_set_adopt_key (result, &key, &value); 739 | 740 | /* 741 | * There must be either a comma or a whitespace after the value, 742 | * or the end-of-sequence character. 743 | */ 744 | if (p->look == ',') { 745 | nextchar (p, CHECK_OK); 746 | } else if (p->look != eos && !is_hipack_whitespace (p->look)) { 747 | break; 748 | } 749 | skipwhite (p, CHECK_OK); 750 | } 751 | return; 752 | 753 | error: 754 | hipack_string_free (key); 755 | hipack_value_free (&value); 756 | } 757 | 758 | 759 | static hipack_dict_t* 760 | parse_message (P, S) 761 | { 762 | hipack_dict_t *result = hipack_dict_new (); 763 | 764 | nextchar (p, CHECK_OK); 765 | skipwhite (p, CHECK_OK); 766 | 767 | if (p->look == HIPACK_IO_ERROR) { 768 | *status = kStatusIoError; 769 | } else if (p->look == '{') { 770 | /* Input starts with a Dict marker. */ 771 | nextchar (p, CHECK_OK); 772 | skipwhite (p, CHECK_OK); 773 | parse_keyval_items (p, result, '}', CHECK_OK); 774 | matchchar (p, '}', "unterminated message", CHECK_OK); 775 | } else { 776 | parse_keyval_items (p, result, HIPACK_IO_EOF, CHECK_OK); 777 | } 778 | return result; 779 | 780 | error: 781 | hipack_dict_free (result); 782 | return NULL; 783 | } 784 | 785 | 786 | hipack_dict_t* 787 | hipack_read (hipack_reader_t *reader) 788 | { 789 | assert (reader); 790 | 791 | /* 792 | * Copy the reader function (and its data pointer) into the parser 793 | * structure. The rest of the fields are used as results, so the 794 | * reader structure can be cleaned up right after. 795 | */ 796 | struct parser p = { 797 | .getchar = reader->getchar, 798 | .getchar_data = reader->getchar_data, 799 | .line = 1, 800 | 0, 801 | }; 802 | memset (reader, 0x00, sizeof (hipack_reader_t)); 803 | 804 | status_t status = kStatusOk; 805 | hipack_dict_t *result = parse_message (&p, &status); 806 | switch (status) { 807 | case kStatusOk: 808 | assert (result); 809 | break; 810 | case kStatusError: 811 | assert (!result); 812 | assert (p.error); 813 | break; 814 | case kStatusIoError: 815 | p.error = HIPACK_READ_ERROR; 816 | hipack_dict_free (result); 817 | result = NULL; 818 | break; 819 | case kStatusEof: 820 | break; 821 | } 822 | 823 | reader->error = p.error; 824 | reader->error_line = p.line; 825 | reader->error_column = p.column; 826 | 827 | return result; 828 | } 829 | 830 | 831 | int 832 | hipack_stdio_getchar (void *fp) 833 | { 834 | assert (fp); 835 | int ch = fgetc ((FILE*) fp); 836 | if (ch == EOF) { 837 | return ferror ((FILE*) fp) ? HIPACK_IO_ERROR : HIPACK_IO_EOF; 838 | } 839 | return ch; 840 | } 841 | 842 | -------------------------------------------------------------------------------- /hipack.h: -------------------------------------------------------------------------------- 1 | /* 2 | * hipack.h 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef HIPACK_H 9 | #define HIPACK_H 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | /** 18 | * ============= 19 | * API Reference 20 | * ============= 21 | * 22 | * Types 23 | * ===== 24 | */ 25 | 26 | /*~t hipack_type_t 27 | * 28 | * Type of a value. This enumeration takes one of the following values: 29 | * 30 | * - ``HIPACK_INTEGER``: Integer value. 31 | * - ``HIPACK_FLOAT``: Floating point value. 32 | * - ``HIPACK_BOOL``: Boolean value. 33 | * - ``HIPACK_STRING``: String value. 34 | * - ``HIPACK_LIST``: List value. 35 | * - ``HIPACK_DICT``: Dictionary value. 36 | */ 37 | typedef enum { 38 | HIPACK_INTEGER, 39 | HIPACK_FLOAT, 40 | HIPACK_BOOL, 41 | HIPACK_STRING, 42 | HIPACK_LIST, 43 | HIPACK_DICT, 44 | } hipack_type_t; 45 | 46 | 47 | /* Forward declarations. */ 48 | typedef struct hipack_value hipack_value_t; 49 | typedef struct hipack_string hipack_string_t; 50 | typedef struct hipack_dict hipack_dict_t; 51 | typedef struct hipack_dict_node hipack_dict_node_t; 52 | typedef struct hipack_list hipack_list_t; 53 | 54 | 55 | /*~t hipack_value_t 56 | * 57 | * Represent any valid HiPack value. 58 | * 59 | * - :func:`hipack_value_type()` obtains the type of a value. 60 | */ 61 | struct hipack_value { 62 | hipack_type_t type; 63 | hipack_dict_t *annot; 64 | union { 65 | int32_t v_integer; 66 | double v_float; 67 | bool v_bool; 68 | hipack_string_t *v_string; 69 | hipack_list_t *v_list; 70 | hipack_dict_t *v_dict; 71 | }; 72 | }; 73 | 74 | 75 | /*~t hipack_string_t 76 | * 77 | * String value. 78 | */ 79 | struct hipack_string { 80 | uint32_t size; 81 | uint8_t data[]; /* C99 flexible array. */ 82 | }; 83 | 84 | /*~t hipack_list_t 85 | * 86 | * List value. 87 | */ 88 | struct hipack_list { 89 | uint32_t size; 90 | hipack_value_t data[]; /* C99 flexible array. */ 91 | }; 92 | 93 | /*~t hipack_dict_t 94 | * 95 | * Dictionary value. 96 | */ 97 | struct hipack_dict { 98 | hipack_dict_node_t **nodes; 99 | hipack_dict_node_t *first; 100 | uint32_t count; 101 | uint32_t size; 102 | }; 103 | 104 | 105 | /** 106 | * Memory Allocation 107 | * ================= 108 | * 109 | * How ``hipack-c`` allocates memory can be customized by setting 110 | * :c:data:`hipack_alloc` to a custom allocation function. 111 | */ 112 | 113 | /*~v hipack_alloc 114 | * 115 | * Allocation function. By default it is set to :func:`hipack_alloc_stdlib()`, 116 | * which uses the implementations of ``malloc()``, ``realloc()``, and 117 | * ``free()`` provided by the C library. 118 | * 119 | * Allocation functions always have the following prototype: 120 | * 121 | * .. code-block:: c 122 | * 123 | * void* func (void *oldptr, size_t size); 124 | * 125 | * The behavior must be as follows: 126 | * 127 | * - When invoked with ``oldptr`` set to ``NULL``, and a non-zero ``size``, 128 | * the function behaves like ``malloc()``: a memory block of at least 129 | * ``size`` bytes is allocated and a pointer to it returned. 130 | * - When ``oldptr`` is non-``NULL``, and a non-zero ``size``, the function 131 | * behaves like ``realloc()``: the memory area pointed to by ``oldptr`` 132 | * is resized to be at least ``size`` bytes, or its contents moved to a 133 | * new memory area of at least ``size`` bytes. The returned pointer may 134 | * either be ``oldptr``, or a pointer to the new memory area if the data 135 | * was relocated. 136 | * - When ``oldptr`` is non-``NULL``, and ``size`` is zero, the function 137 | * behaves like ``free()``. 138 | */ 139 | extern void* (*hipack_alloc) (void*, size_t); 140 | 141 | /*~f void* hipack_alloc_stdlib(void*, size_t) 142 | * 143 | * Default allocation function. It uses ``malloc()``, ``realloc()``, and 144 | * ``free()`` from the C library. By default :any:`hipack_alloc` is set 145 | * to use this function. 146 | */ 147 | extern void* hipack_alloc_stdlib (void*, size_t); 148 | 149 | /*~f void* hipack_alloc_array_extra (void *oldptr, size_t nmemb, size_t size, size_t extra) 150 | * 151 | * Allocates (if `oldptr` is ``NULL``) or reallocates (if `oldptr` is 152 | * non-``NULL``) memory for an array which contains `nmemb` elements, each one 153 | * of `size` bytes, plus an arbitrary amount of `extra` bytes. 154 | * 155 | * This function is used internally by the HiPack parser, and it is not likely 156 | * to be needed by client code. 157 | */ 158 | extern void* hipack_alloc_array_extra (void*, size_t nmemb, size_t size, size_t extra); 159 | 160 | /*~f void* hipack_alloc_array (void *optr, size_t nmemb, size_t size) 161 | * 162 | * Same as :c:func:`hipack_alloc_array_extra()`, without allowing to specify 163 | * the amount of extra bytes. The following calls are both equivalent: 164 | * 165 | * .. code-block:: c 166 | * 167 | * void *a = hipack_alloc_array_extra (NULL, 10, 4, 0); 168 | * void *b = hipack_alloc_array (NULL, 10, 4); 169 | * 170 | * See :c:func:`hipack_alloc_array_extra()` for details. 171 | */ 172 | static inline void* 173 | hipack_alloc_array (void *optr, size_t nmemb, size_t size) 174 | { 175 | return hipack_alloc_array_extra (optr, nmemb, size, 0); 176 | } 177 | 178 | /*~f void* hipack_alloc_bzero (size_t size) 179 | * 180 | * Allocates an area of memory of `size` bytes, and initializes it to zeroes. 181 | */ 182 | static inline void* 183 | hipack_alloc_bzero (size_t size) 184 | { 185 | assert (size > 0); 186 | return memset ((*hipack_alloc) (NULL, size), 0, size); 187 | } 188 | 189 | /*~f void hipack_alloc_free (void *pointer) 190 | * 191 | * Frees the memory area referenced by the given `pointer`. 192 | */ 193 | static inline void 194 | hipack_alloc_free (void *optr) 195 | { 196 | (*hipack_alloc) (optr, 0); 197 | } 198 | 199 | 200 | /** 201 | * String Functions 202 | * ================ 203 | * 204 | * The following functions are provided as a convenience to operate on values 205 | * of type :c:type:`hipack_string_t`. 206 | * 207 | * .. note:: The hash function used by :c:func:`hipack_string_hash()` is 208 | * *not* guaranteed to be cryptographically safe. Please do avoid exposing 209 | * values returned by this function to the attack surface of your 210 | * applications, in particular *do not expose them to the network*. 211 | */ 212 | 213 | /*~f hipack_string_t* hipack_string_copy (const hipack_string_t *hstr) 214 | * 215 | * Returns a new copy of a string. 216 | * 217 | * The returned value must be freed using :c:func:`hipack_string_free()`. 218 | */ 219 | extern hipack_string_t* hipack_string_copy (const hipack_string_t *hstr); 220 | 221 | /*~f hipack_string_t* hipack_string_new_from_string (const char *str) 222 | * 223 | * Creates a new string from a C-style zero terminated string. 224 | * 225 | * The returned value must be freed using :c:func:`hipack_string_free()`. 226 | */ 227 | extern hipack_string_t* hipack_string_new_from_string (const char *str); 228 | 229 | /*~f hipack_string_t* hipack_string_new_from_lstring (const char *str, uint32_t len) 230 | * 231 | * Creates a new string from a memory area and its length. 232 | * 233 | * The returned value must be freed using :c:func:`hipack_string_free()`. 234 | */ 235 | extern hipack_string_t* hipack_string_new_from_lstring (const char *str, uint32_t len); 236 | 237 | /*~f uint32_t hipack_string_hash (const hipack_string_t *hstr) 238 | * 239 | * Calculates a hash value for a string. 240 | */ 241 | extern uint32_t hipack_string_hash (const hipack_string_t *hstr); 242 | 243 | /*~f bool hipack_string_equal (const hipack_string_t *hstr1, const hipack_string_t *hstr2) 244 | * Compares two strings to check whether their contents are the same. 245 | */ 246 | extern bool hipack_string_equal (const hipack_string_t *hstr1, 247 | const hipack_string_t *hstr2); 248 | 249 | /*~f void hipack_string_free (hipack_string_t *hstr) 250 | * Frees the memory used by a string. 251 | */ 252 | extern void hipack_string_free (hipack_string_t *hstr); 253 | 254 | 255 | /** 256 | * List Functions 257 | * ============== 258 | */ 259 | 260 | /*~f hipack_list_t* hipack_list_new (uint32_t size) 261 | * Creates a new list for ``size`` elements. 262 | */ 263 | extern hipack_list_t* hipack_list_new (uint32_t size); 264 | 265 | /*~f void hipack_list_free (hipack_list_t *list) 266 | * Frees the memory used by a list. 267 | */ 268 | extern void hipack_list_free (hipack_list_t *list); 269 | 270 | /*~f bool hipack_list_equal (const hipack_list_t *a, const hipack_list_t *b) 271 | * Checks whether two lists contains the same values. 272 | */ 273 | extern bool hipack_list_equal (const hipack_list_t *a, 274 | const hipack_list_t *b); 275 | 276 | /*~f uint32_t hipack_list_size (const hipack_list_t *list) 277 | * Obtains the number of elements in a list. 278 | */ 279 | static inline uint32_t 280 | hipack_list_size (const hipack_list_t *list) 281 | { 282 | assert (list); 283 | return list->size; 284 | } 285 | 286 | /*~M HIPACK_LIST_AT(list, index) 287 | * 288 | * Obtains a pointer to the element at a given `index` of a `list`. 289 | */ 290 | #define HIPACK_LIST_AT(_list, _index) \ 291 | (assert ((_index) < (_list)->size), &((_list)->data[_index])) 292 | 293 | 294 | /** 295 | * .. _dict_funcs: 296 | * 297 | * Dictionary Functions 298 | * ==================== 299 | */ 300 | 301 | /*~f uint32_t hipack_dict_size (const hipack_dict_t *dict) 302 | * 303 | * Obtains the number of elements in a dictionary. 304 | */ 305 | static inline uint32_t 306 | hipack_dict_size (const hipack_dict_t *dict) 307 | { 308 | assert (dict); 309 | return dict->count; 310 | } 311 | 312 | /*~f hipack_dict_t* hipack_dict_new (void) 313 | * 314 | * Creates a new, empty dictionary. 315 | */ 316 | extern hipack_dict_t* hipack_dict_new (void); 317 | 318 | /*~f void hipack_dict_free (hipack_dict_t *dict) 319 | * 320 | * Frees the memory used by a dictionary. 321 | */ 322 | extern void hipack_dict_free (hipack_dict_t *dict); 323 | 324 | /*~f bool hipack_dict_equal (const hipack_dict_t *a, const hipack_dict_t *b) 325 | * 326 | * Checks whether two dictinaries contain the same keys, and their associated 327 | * values in each of the dictionaries are equal. 328 | */ 329 | extern bool hipack_dict_equal (const hipack_dict_t *a, 330 | const hipack_dict_t *b); 331 | 332 | /*~f void hipack_dict_set (hipack_dict_t *dict, const hipack_string_t *key, const hipack_value_t *value) 333 | * 334 | * Adds an association of a `key` to a `value`. 335 | * 336 | * Note that this function will copy the `key`. If you are not planning to 337 | * continue reusing the `key`, it is recommended to use 338 | * :c:func:`hipack_dict_set_adopt_key()` instead. 339 | */ 340 | extern void hipack_dict_set (hipack_dict_t *dict, 341 | const hipack_string_t *key, 342 | const hipack_value_t *value); 343 | 344 | /*~f void hipack_dict_set_adopt_key (hipack_dict_t *dict, hipack_string_t **key, const hipack_value_t *value) 345 | * 346 | * Adds an association of a `key` to a `value`, passing ownership of the 347 | * memory using by the `key` to the dictionary (i.e. the string used as key 348 | * will be freed by the dictionary). 349 | * 350 | * Use this function instead of :c:func:`hipack_dict_set()` when the `key` 351 | * is not going to be used further afterwards. 352 | */ 353 | extern void hipack_dict_set_adopt_key (hipack_dict_t *dict, 354 | hipack_string_t **key, 355 | const hipack_value_t *value); 356 | 357 | /*~f void hipack_dict_del (hipack_dict_t *dict, const hipack_string_t *key) 358 | * 359 | * Removes the element from a dictionary associated to a `key`. 360 | */ 361 | extern void hipack_dict_del (hipack_dict_t *dict, 362 | const hipack_string_t *key); 363 | 364 | /*~f hipack_value_t* hipack_dict_get (const hipack_dict_t *dict, const hipack_string_t *key) 365 | * 366 | * Obtains the value associated to a `key` from a dictionary. 367 | * 368 | * The returned value points to memory owned by the dictionary. The value can 369 | * be modified in-place, but it shall not be freed. 370 | */ 371 | extern hipack_value_t* hipack_dict_get (const hipack_dict_t *dict, 372 | const hipack_string_t *key); 373 | 374 | /*~f hipack_value_t* hipack_dict_first (const hipack_dict_t *dict, const hipack_string_t **key) 375 | * 376 | * Obtains an a *(key, value)* pair, which is considered the *first* in 377 | * iteration order. This can be used in combination with 378 | * :c:func:`hipack_dict_next()` to enumerate all the *(key, value)* pairs 379 | * stored in the dictionary: 380 | * 381 | * .. code-block:: c 382 | * 383 | * hipack_dict_t *d = get_dictionary (); 384 | * hipack_value_t *v; 385 | * hipack_string_t *k; 386 | * 387 | * for (v = hipack_dict_first (d, &k); 388 | * v != NULL; 389 | * v = hipack_dict_next (v, &k)) { 390 | * // Use "k" and "v" normally. 391 | * } 392 | * 393 | * As a shorthand, consider using :c:macro:`HIPACK_DICT_FOREACH()` instead. 394 | */ 395 | extern hipack_value_t* hipack_dict_first (const hipack_dict_t *dict, 396 | const hipack_string_t **key); 397 | 398 | /*~f hipack_value_t* hipack_dict_next (hipack_value_t *value, const hipack_string_t **key) 399 | * 400 | * Iterates to the next *(key, value)* pair of a dictionary. For usage 401 | * details, see :c:func:`hipack_dict_first()`. 402 | */ 403 | extern hipack_value_t* hipack_dict_next (hipack_value_t *value, 404 | const hipack_string_t **key); 405 | 406 | /*~M HIPACK_DICT_FOREACH(dict, key, value) 407 | * 408 | * Convenience macro used to iterate over the *(key, value)* pairs contained 409 | * in a dictionary. Internally this uses :c:func:`hipack_dict_first()` and 410 | * :c:func:`hipack_dict_next()`. 411 | * 412 | * .. code-block:: c 413 | * 414 | * hipack_dict_t *d = get_dictionary (); 415 | * hipack_string_t *k; 416 | * hipack_value_t *v; 417 | * HIPACK_DICT_FOREACH (d, k, v) { 418 | * // Use "k" and "v" 419 | * } 420 | * 421 | * Using this macro is the recommended way of writing a loop to enumerate 422 | * elements from a dictionary. 423 | */ 424 | #define HIPACK_DICT_FOREACH(_d, _k, _v) \ 425 | for ((_v) = hipack_dict_first ((_d), &(_k)); \ 426 | (_v) != NULL; \ 427 | (_v) = hipack_dict_next ((_v), &(_k))) 428 | 429 | /** 430 | * Value Functions 431 | * =============== 432 | */ 433 | 434 | /*~f hipack_type_t hipack_value_type (const hipack_value_t *value) 435 | * 436 | * Obtains the type of a value. 437 | */ 438 | static inline hipack_type_t 439 | hipack_value_type (const hipack_value_t *value) 440 | { 441 | return value->type; 442 | } 443 | 444 | /*~f hipack_value_t hipack_integer (int32_t value) 445 | * Creates a new integer value. 446 | */ 447 | /*~f hipack_value_t hipack_float (double value) 448 | * Creates a new floating point value. 449 | */ 450 | /*~f hipack_value_t hipack_bool (bool value) 451 | * Creates a new boolean value. 452 | */ 453 | /*~f hipack_value_t hipack_string (hipack_string_t *value) 454 | * Creates a new string value. 455 | */ 456 | /*~f hipack_value_t hipack_list (hipack_list_t *value) 457 | * Creates a new list value. 458 | */ 459 | /*~f hipack_value_t hipack_dict (hipack_dict_t *value) 460 | * Creates a new dictionary value. 461 | */ 462 | 463 | /*~f bool hipack_value_is_integer (const hipack_value_t *value) 464 | * Checks whether a value is an integer. 465 | */ 466 | /*~f bool hipack_value_is_float (const hipack_value_t *value) 467 | * Checks whether a value is a floating point number. 468 | */ 469 | /*~f bool hipack_value_is_bool (const hipack_value_t *value) 470 | * Checks whether a value is a boolean. 471 | */ 472 | /*~f bool hipack_value_is_string (const hipack_value_t *value) 473 | * Checks whether a value is a string. 474 | */ 475 | /*~f bool hipack_value_is_list (const hipack_value_t *value) 476 | * Checks whether a value is a list. 477 | */ 478 | /*~f bool hipack_value_is_dict (const hipack_value_t *value) 479 | * Checks whether a value is a dictionary. 480 | */ 481 | 482 | /*~f const int32_t hipack_value_get_integer (const hipack_value_t *value) 483 | * Obtains a numeric value as an ``int32_t``. 484 | */ 485 | /*~f const double hipack_value_get_float (const hipack_value_t *value) 486 | * Obtains a floating point value as a ``double``. 487 | */ 488 | /*~f const bool hipack_value_get_bool (const hipack_value_t *value) 489 | * Obtains a boolean value as a ``bool``. 490 | */ 491 | /*~f const hipack_string_t* hipack_value_get_string (const hipack_value_t *value) 492 | * Obtains a numeric value as a :c:type:`hipack_string_t`. 493 | */ 494 | /*~f const hipack_list_t* hipack_value_get_list (const hipack_value_t *value) 495 | * Obtains a numeric value as a :c:type:`hipack_list_t`. 496 | */ 497 | /*~f const hipack_dict_t* hipack_value_get_dict (const hipack_value_t *value) 498 | * Obtains a numeric value as a :c:type:`hipack_dict_t`. 499 | */ 500 | 501 | #define HIPACK_TYPES(F) \ 502 | F (int32_t, integer, HIPACK_INTEGER) \ 503 | F (double, float, HIPACK_FLOAT ) \ 504 | F (bool, bool, HIPACK_BOOL ) \ 505 | F (hipack_string_t*, string, HIPACK_STRING ) \ 506 | F (hipack_list_t*, list, HIPACK_LIST ) \ 507 | F (hipack_dict_t*, dict, HIPACK_DICT ) 508 | 509 | #define HIPACK_DEFINE_IS_TYPE(_type, name, type_tag) \ 510 | static inline bool \ 511 | hipack_value_is_ ## name (const hipack_value_t *value) { \ 512 | return value->type == type_tag; \ 513 | } 514 | 515 | #define HIPACK_DEFINE_GET_VALUE(_type, name, type_tag) \ 516 | static inline const _type \ 517 | hipack_value_get_ ## name (const hipack_value_t *value) { \ 518 | assert (value->type == type_tag); \ 519 | return value->v_ ## name; \ 520 | } 521 | 522 | #define HIPACK_DEFINE_MAKE_VALUE(_type, name, type_tag) \ 523 | static inline hipack_value_t \ 524 | hipack_ ## name (_type value) { \ 525 | hipack_value_t v = { type_tag, NULL, { .v_ ## name = value } }; \ 526 | return v; \ 527 | } 528 | 529 | HIPACK_TYPES (HIPACK_DEFINE_IS_TYPE) 530 | HIPACK_TYPES (HIPACK_DEFINE_GET_VALUE) 531 | HIPACK_TYPES (HIPACK_DEFINE_MAKE_VALUE) 532 | 533 | #undef HIPACK_DEFINE_IS_TYPE 534 | #undef HIPACK_DEFINE_GET_VALUE 535 | #undef HIPACK_DEFINE_MAKE_VALUE 536 | 537 | 538 | /*~f bool hipack_value_equal (const hipack_value_t *a, const hipack_value_t *b) 539 | * 540 | * Checks whether two values are equal. 541 | */ 542 | extern bool hipack_value_equal (const hipack_value_t *a, 543 | const hipack_value_t *b); 544 | 545 | /*~f void hipack_value_free (hipack_value_t *value) 546 | * 547 | * Frees the memory used by a value. 548 | */ 549 | static inline void 550 | hipack_value_free (hipack_value_t *value) 551 | { 552 | assert (value); 553 | 554 | if (value->annot) 555 | hipack_dict_free (value->annot); 556 | 557 | switch (value->type) { 558 | case HIPACK_INTEGER: 559 | case HIPACK_FLOAT: 560 | case HIPACK_BOOL: 561 | /* Nothing to free. */ 562 | break; 563 | 564 | case HIPACK_STRING: 565 | hipack_string_free (value->v_string); 566 | break; 567 | 568 | case HIPACK_LIST: 569 | hipack_list_free (value->v_list); 570 | break; 571 | 572 | case HIPACK_DICT: 573 | hipack_dict_free (value->v_dict); 574 | break; 575 | } 576 | } 577 | 578 | /*~f void hipack_value_add_annot (hipack_value_t *value, const char *annot) 579 | * 580 | * Adds an annotation to a value. If the value already had the annotation, 581 | * this function is a no-op. 582 | */ 583 | static inline void 584 | hipack_value_add_annot (hipack_value_t *value, 585 | const char *annot) 586 | { 587 | assert (value); 588 | assert (annot); 589 | 590 | if (!value->annot) { 591 | value->annot = hipack_dict_new (); 592 | } 593 | 594 | static const hipack_value_t bool_true = { 595 | .type = HIPACK_BOOL, 596 | .v_bool = true, 597 | }; 598 | hipack_string_t *key = hipack_string_new_from_string (annot); 599 | hipack_dict_set_adopt_key (value->annot, &key, &bool_true); 600 | } 601 | 602 | /*~f bool hipack_value_has_annot (const hipack_value_t *value, const char *annot) 603 | * 604 | * Checks whether a value has a given annotation. 605 | */ 606 | static inline bool 607 | hipack_value_has_annot (const hipack_value_t *value, 608 | const char *annot) 609 | { 610 | assert (value); 611 | assert (annot); 612 | 613 | /* TODO: Try to avoid the string copy. */ 614 | hipack_string_t *key = hipack_string_new_from_string (annot); 615 | bool result = (value->annot) && hipack_dict_get (value->annot, key); 616 | hipack_string_free (key); 617 | return result; 618 | } 619 | 620 | /*~f void hipack_value_del_annot (hipack_value_t *value, const char *annot) 621 | * 622 | * Removes an annotation from a value. If the annotation was not present, this 623 | * function is a no-op. 624 | */ 625 | static inline void 626 | hipack_value_del_annot (hipack_value_t *value, 627 | const char *annot) 628 | { 629 | assert (value); 630 | assert (annot); 631 | 632 | if (value->annot) { 633 | hipack_string_t *key = hipack_string_new_from_string (annot); 634 | hipack_dict_del (value->annot, key); 635 | } 636 | } 637 | 638 | 639 | /** 640 | * Reader Interface 641 | * ================ 642 | */ 643 | 644 | /*~t hipack_reader_t 645 | * 646 | * Allows communicating with the parser, instructing it how to read text 647 | * input data, and provides a way for the parser to report errors back. 648 | * 649 | * The following members of the structure are to be used by client code: 650 | */ 651 | typedef struct { 652 | /*~m int (*getchar)(void *data) 653 | * Reader callback function. The function will be called every time the 654 | * next character of input is needed. It must return it as an integer, 655 | * :any:`HIPACK_IO_EOF` when trying to read pas the end of the input, 656 | * or :any:`HIPACK_IO_ERROR` if an input error occurs. 657 | */ 658 | int (*getchar) (void*); 659 | 660 | /*m void *getchar_data 661 | * Data passed to the reader callback function. 662 | */ 663 | void *getchar_data; 664 | 665 | /*~m const char *error 666 | * On error, a string describing the issue, suitable to be displayed to 667 | * the user. 668 | */ 669 | const char *error; 670 | 671 | /*~m unsigned error_line 672 | * On error, the line number where parsing was stopped. 673 | */ 674 | unsigned error_line; 675 | 676 | /*~m unsigned error_column 677 | * On error, the column where parsing was stopped. 678 | */ 679 | unsigned error_column; 680 | } hipack_reader_t; 681 | 682 | 683 | enum { 684 | /*~M HIPACK_IO_EOF 685 | * Constant returned by reader functions when trying to read past the end of 686 | * the input. 687 | */ 688 | HIPACK_IO_EOF = -1, 689 | 690 | /*~M HIPACK_IO_ERROR 691 | * Constant returned by reader functions on input errors. 692 | */ 693 | HIPACK_IO_ERROR = -2, 694 | }; 695 | 696 | /*~M HIPACK_READ_ERROR 697 | * 698 | * Constant value used to signal an underlying input error. 699 | * 700 | * The `error` field of :c:type:`hipack_reader_t` is set to this value when 701 | * the reader function returns :any:`HIPACK_IO_ERROR`. This is provided to 702 | * allow client code to detect this condition and further query for the 703 | * nature of the input error. 704 | */ 705 | extern const char* HIPACK_READ_ERROR; 706 | 707 | /*~f hipack_dict_t* hipack_read (hipack_reader_t *reader) 708 | * 709 | * Reads a HiPack message from a stream `reader` and returns a dictionary. 710 | * 711 | * On error, ``NULL`` is returned, and the members `error`, `error_line`, 712 | * and `error_column` (see :c:type:`hipack_reader_t`) are set accordingly 713 | * in the `reader`. 714 | */ 715 | extern hipack_dict_t* hipack_read (hipack_reader_t *reader); 716 | 717 | /*~f int hipack_stdio_getchar (void* fp) 718 | * 719 | * Reader function which uses ``FILE*`` objects from the standard C library. 720 | * 721 | * To use this function to read from a ``FILE*``, first open a file, and 722 | * then create a reader using this function and the open file as data to 723 | * be passed to it, and then use :c:func:`hipack_read()`: 724 | * 725 | * .. code-block:: c 726 | * 727 | * FILE* stream = fopen (HIPACK_FILE_PATH, "rb") 728 | * hipack_reader_t reader = { 729 | * .getchar = hipack_stdio_getchar, 730 | * .getchar_data = stream, 731 | * }; 732 | * hipack_dict_t *message = hipack_read (&reader); 733 | * 734 | * The user is responsible for closing the ``FILE*`` after using it. 735 | */ 736 | extern int hipack_stdio_getchar (void* fp); 737 | 738 | 739 | /** 740 | * Writer Interface 741 | * ================ 742 | */ 743 | 744 | /*~t hipack_writer_t 745 | * 746 | * Allows specifying how to write text output data, and configuring how 747 | * the produced HiPack output looks like. 748 | * 749 | * The following members of the structure are to be used by client code: 750 | */ 751 | typedef struct { 752 | /*~m int (*putchar)(void *data, int ch) 753 | * Writer callback function. The function will be called every time a 754 | * character is produced as output. It must return :any:`HIPACK_IO_ERROR` 755 | * if an output error occurs, and it is invalid for the callback to 756 | * return :any:`HIPACK_IO_EOF`. Any other value is interpreted as 757 | * indication of success. 758 | */ 759 | int (*putchar) (void*, int); 760 | 761 | /*~m void* putchar_data 762 | * Data passed to the writer callback function. 763 | */ 764 | void *putchar_data; 765 | 766 | /*~m int32_t indent 767 | * Either :any:`HIPACK_WRITER_COMPACT` or :any:`HIPACK_WRITER_INDENTED`. 768 | */ 769 | int32_t indent; 770 | } hipack_writer_t; 771 | 772 | 773 | enum { 774 | /*~M HIPACK_WRITER_COMPACT 775 | * Flag to generate output HiPack messages in their compact representation. 776 | */ 777 | HIPACK_WRITER_COMPACT = -1, 778 | /*~M HIPACK_WRITER_INDENTED 779 | * Flag to generate output HiPack messages in “indented” (pretty-printed) 780 | * representation. 781 | */ 782 | HIPACK_WRITER_INDENTED = 0, 783 | }; 784 | 785 | #define HIPACK_DEFINE_WRITE_VALUE(_type, name, type_tag) \ 786 | extern bool hipack_write_ ## name (hipack_writer_t *writer, \ 787 | const _type value); 788 | 789 | HIPACK_TYPES (HIPACK_DEFINE_WRITE_VALUE) 790 | 791 | #undef HIPACK_DEFINE_WRITE_VALUE 792 | 793 | extern bool hipack_write_value (hipack_writer_t *writer, 794 | const hipack_value_t *value); 795 | 796 | /*~f bool hipack_write (hipack_writer_t *writer, const hipack_dict_t *message) 797 | * 798 | * Writes a HiPack `message` to a stream `writer`, and returns whether writing 799 | * the message was successful. 800 | */ 801 | extern bool hipack_write (hipack_writer_t *writer, 802 | const hipack_dict_t *message); 803 | 804 | /*~f int hipack_stdio_putchar (void* data, int ch) 805 | * 806 | * Writer function which uses ``FILE*`` objects from the standard C library. 807 | * 808 | * To use this function to write a message to a ``FILE*``, first open a file, 809 | * then create a writer using this function, and then use 810 | * :c:func:`hipack_write()`: 811 | * 812 | * .. code-block:: c 813 | * 814 | * FILE* stream = fopen (HIPACK_FILE_PATH, "wb"); 815 | * hipack_writer_t writer = { 816 | * .putchar = hipack_stdio_putchar, 817 | * .putchar_data = stream, 818 | * }; 819 | * hipack_write (&writer, message); 820 | * 821 | * The user is responsible for closing the ``FILE*`` after using it. 822 | */ 823 | extern int hipack_stdio_putchar (void* fp, int ch); 824 | 825 | #endif /* !HIPACK_H */ 826 | --------------------------------------------------------------------------------